diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2a869fcb..1f081dcd 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -24,9 +24,9 @@ Fixes #??? -- [ ] I have read the [Contributing Guide](CONTRIBUTING.md) -- [ ] I have added tests that prove my fix is effective or that my feature works -- [ ] I have added documentation of new methods and any new behavior or changes to existing behavior +- [ ] I have read the [Contributing Guide](../CONTRIBUTING.md#your-first-code-contribution) +- [ ] I have added tests that prove my fix is effective or that my feature works. See [GUIDELINES.md#testing](../GUIDELINES.md#testing) for more information. +- [ ] I have added documentation for new methods or changes to existing method behavior. See [GUIDELINES.md#documentation](../GUIDELINES.md#documentation) for more information. - [ ] CI Workflows Are Passing #### Further comments diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 55b77153..5819d7bf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -88,7 +88,7 @@ Explain the problem and include additional details to help maintainers reproduce * **Provide specific examples to demonstrate the steps**. Include links to files or GitHub projects, or copy/pasteable snippets, which you use in those examples. If you're providing snippets in the issue, use [Markdown code blocks](https://help.github.com/articles/markdown-basics/#multiple-lines). * **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior. * **Explain which behavior you expected to see instead and why.** -* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. +* **Include screenshots and animated GIFs** which show you following the described steps and clearly demonstrate the problem. Include details about your configuration and environment: diff --git a/GUIDELINES.md b/GUIDELINES.md index c80fe1ca..c831ce49 100644 --- a/GUIDELINES.md +++ b/GUIDELINES.md @@ -27,6 +27,10 @@ TypeScript code should be written in a consistent format enforced by the project ## Documentation +The online version of the Compact contracts documentation is maintained at [OpenZeppelin/docs](https://github.com/OpenZeppelin/docs). +It should mirror all documentation embedded within the contracts themselves with additional context and usage examples if applicable. +Pull requests in [OpenZeppelin/docs](https://github.com/OpenZeppelin/docs) should be linked to the corresponding pull request in this repo. + For contributors, project guidelines and processes must be documented publicly. For users, features must be abundantly documented. Documentation should include answers to common questions, solutions to common problems, and recommendations for critical decisions that the user may face. diff --git a/docs/.github/pull_request_template.md b/docs/.github/pull_request_template.md new file mode 100644 index 00000000..df582dbb --- /dev/null +++ b/docs/.github/pull_request_template.md @@ -0,0 +1,27 @@ +## Documentation Pull Request + +### Summary + + +### Type of Change + +- [ ] New documentation +- [ ] Documentation update/revision +- [ ] Fix typos or grammar +- [ ] Restructure/reorganize content +- [ ] Add examples or tutorials +- [ ] Update API documentation +- [ ] Other: ___________ + +### Related Issues + +Fixes # +Relates to # + +### Checklist +- [ ] Build succeeds locally with `pnpm run build` +- [ ] Lint is successful when running `pnpm run check` +- [ ] Docs follow [OpenZeppelin Documentation Standards](https://github.com/OpenZeppelin/docs/blob/main/STANDARDS.md) + +### Additional Notes + diff --git a/docs/.github/workflows/lint.yml b/docs/.github/workflows/lint.yml new file mode 100644 index 00000000..d1f9d6bb --- /dev/null +++ b/docs/.github/workflows/lint.yml @@ -0,0 +1,40 @@ +name: Lint & Format + +on: + push: + branches: [main] + pull_request: + branches: [main] + +env: + NEXT_TELEMETRY_DISABLED: 1 + +jobs: + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [22] + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v5 + with: + node-version: ${{ matrix.node-version }} + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run lint and format checks + run: pnpm run check + + - name: Build check + run: pnpm run build diff --git a/docs/.github/workflows/sync-search.yml b/docs/.github/workflows/sync-search.yml new file mode 100644 index 00000000..d3bd3391 --- /dev/null +++ b/docs/.github/workflows/sync-search.yml @@ -0,0 +1,27 @@ +name: Sync Search Content + +on: + push: + branches: [main] + +jobs: + sync-search: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Run sync search content + run: bun run scripts/sync-search-content.ts + env: + NEXT_PUBLIC_ALGOLIA_ID: ${{ secrets.NEXT_PUBLIC_ALGOLIA_ID }} + ALGOLIA_PRIVATE_KEY: ${{ secrets.ALGOLIA_PRIVATE_KEY }} diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..cee20a5c --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,35 @@ +# deps +/node_modules + +# generated content +.contentlayer +.content-collections +.source + +# test & build +/coverage +/.next/ +/out/ +/build +*.tsbuildinfo + +# misc +.DS_Store +*.pem +/.pnp +.pnp.js +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# others +.env*.local +.vercel +next-env.d.ts +old-docs +.claude +TODO.md +broken-links.md + +# Local Netlify folder +.netlify diff --git a/docs/AGENTS.md b/docs/AGENTS.md new file mode 100644 index 00000000..2895004c --- /dev/null +++ b/docs/AGENTS.md @@ -0,0 +1,118 @@ +# AGENTS.md - OpenZeppelin Docs Codebase Guide + +## Build/Lint/Test Commands +- `pnpm run build` - Build the Next.js application +- `pnpm run dev` - Start development server with turbo +- `pnpm run start` - Start production server +- `pnpm run postinstall` - Process MDX files with fumadocs-mdx +- `pnpm run lint` - Lint code with Biome +- `pnpm run lint:fix` - Lint and auto-fix issues with Biome +- `pnpm run format` - Check code formatting with Biome +- `pnpm run format:fix` - Format code with Biome +- `pnpm run check` - Run both linting and formatting checks +- `pnpm run check:fix` - Run both linting and formatting with auto-fix +- No test commands configured - this is a documentation site + +## Navigation Management +The site uses a **modular JSON navigation system** instead of fumadocs meta.json files: + +### Navigation Structure +- `src/navigation/` - Modular navigation configuration + - `types.ts` - TypeScript interfaces for navigation + - `contracts.json` - All contract-related navigation + - `tools.json` - Tools section navigation + - `ecosystems.json` - Ecosystem-specific sections + - `index.ts` - Combines all sections into navigationTree + +### Editing Navigation +- **Add new pages**: Edit the relevant JSON file (contracts/tools/ecosystems) +- **Reorder sections**: Modify array order in respective JSON files +- **Add new sections**: Create new JSON file and import in `index.ts` +- **Do NOT use meta.json files** - they have been removed and are not supported + +## Code Style Guidelines + +### TypeScript/JavaScript +- Use TypeScript strict mode (enabled in tsconfig.json) +- Prefer named imports: `import { RootProvider } from 'fumadocs-ui/provider'` +- Use path aliases: `@/*` for src/, `@/.source` for .source/ +- Function components with explicit return types when needed +- Use React 19+ features and patterns + +### File Structure +- Source files in `src/` directory +- Documentation content in `content/` with nested version folders (v2.x, v3.x, etc.) +- Examples in `examples/` directory organized by feature +- Public assets in `public/` directory +- Navigation config in `src/navigation/` directory + +### Naming Conventions +- PascalCase for React components and interfaces +- camelCase for variables and functions +- kebab-case for file names in content directories + +## Markdown Link Cleanup Strategy + +### Problem +Legacy markdown files often contain complex, unreadable link syntax that creates URL-encoded URLs like: +``` +http://localhost:3000/api/access#has_role(role:-felt252,-account:-contractaddress)-%E2%86%92-bool-external +``` + +### Solution Approach +When cleaning up markdown files with complex link syntax, follow this systematic approach: + +#### 1. Identify Complex Link Patterns +Look for these problematic patterns: +- **Function list links**: `[`+function_name+`](#`[.contract-item-name]#`function_name`#`(params)``-[.item-kind]#external#)` +- **Event list links**: `[`+EventName+`](#`[.contract-item-name]#`eventname`#`(params)``-[.item-kind]#event#)` +- **Function headings**: `#### `[.contract-item-name]#`function_name`#`(params)`` [.item-kind]#external#` + +#### 2. Create Clean Link Strategy +Replace with simple, readable format: +- **Clean list links**: `[`function_name`](#prefix-function_name)` +- **Clean headings with anchors**: + ```markdown + + #### `function_name(params) → return_type` + ``` + +#### 3. Anchor ID Naming Convention +Use descriptive prefixes to avoid conflicts: +- `iaccesscontrol-` for interface functions/events +- `component-` for component functions/events +- `extension-` for extension functions/events + +#### 4. Implementation Process +1. **Use Task agent** for systematic fixes across large files +2. **Fix by section** - group related functions/events together +3. **Update both links and targets** - ensure table of contents links point to correct anchors +4. **Verify no complex patterns remain** - search for `[.contract-item-name]#` and `[.item-kind]#` + +#### 5. Benefits +- ✅ Clean, readable URLs +- ✅ Better SEO and user experience +- ✅ Easier maintenance and debugging +- ✅ Consistent markdown formatting + +### Example Before/After + +**Before (Complex):** +```markdown +* [`+has_role(role, account)+`](#`[.contract-item-name]#`has_role`#`(role:-felt252,-account:-contractaddress)-→-bool``-[.item-kind]#external#) + +#### `[.contract-item-name]#`has_role`#`(role: felt252, account: ContractAddress) → bool`` [.item-kind]#external# +``` + +**After (Clean):** +```markdown +* [`has_role(role, account)`](#iaccesscontrol-has_role) + + +#### `has_role(role: felt252, account: ContractAddress) → bool` +``` + +### Framework Compatibility Notes +- **DO NOT use `{#anchor}` syntax** - breaks the framework parser +- **USE HTML anchor tags** - `` format is safe +- **Test locally** to ensure links work properly after changes diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 00000000..f6006a18 --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing + +Thank you for your interest in contributing to the OpenZeppelin Docs! We welcome contributions from everyone. + +## How to Contribute + +Please follow this guide if you have something to contribute. + +### Reporting Issues + +If you find a bug or have a suggestion for improvement: + +1. Check if the issue already exists in [GitHub issues](https://github.com/OpenZeppelin/docs/issues) +2. If not, create a new issue with a clear description +3. Wait for a team member to comment before moving forward with a pull request + +### Making Changes + +1. Fork the repository +2. Create a new branch for your change +3. Follow the local development guide in the [README](README.md) or with the steps [below](#development-setup) +4. Use [conventional commits](https://www.conventionalcommits.org/) when making changes +5. Make sure your changes follow the [OpenZeppelin Docs Standards](STANDARDS.md) +6. Submit a pull request + +### Pull Request Guidelines + +- Keep pull requests focused on a single feature or fix +- Write clear commit messages +- Spelling fixes should be grouped as much as possible (i.e. fixing multiple errors instead of just one) + +### Development Setup + +1. Make sure [pnpm](https://pnpm.io) is installed + +```bash +pnpm --version +``` + +2. Clone the repo and install dependencies + +```bash +git clone https://github.com/OpenZeppelin/docs +cd docs +pnpm install +``` + +3. Run the `dev` server to see a live preview and have your changes reflected at `http://localhost:3000` + +```bash +pnpm dev +``` + +4. After making changes run the lint command to make formatting rules are applied + +```bash +pnpm run check:fix +``` + +## Code of Conduct + +Please be respectful and constructive in all interactions. We strive to maintain a welcoming and inclusive environment for all contributors. + +## Questions? + +If you have questions about contributing, feel free to open an issue or reach out to the maintainers. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..4236219e --- /dev/null +++ b/docs/README.md @@ -0,0 +1,208 @@ +# OpenZeppelin Docs + +![cover](public/social.png) + +Welcome to the OpenZeppelin Docs repo! Before opening an issue or creating a PR please consult our [contribution guide](CONTRIBUTING.md) as well as the [OpenZeppelin Documentation Standards](STANDARDS.md) + +## Development + +This is a Next.js application generated with [Fumadocs](https://github.com/fuma-nama/fumadocs). + +To start local development follow the steps below + +**1. Make sure [pnpm](https://pnpm.io) is installed** + +```bash +pnpm --version +``` + +**2. Clone the repo and install dependencies** + +```bash +git clone https://github.com/OpenZeppelin/docs +cd docs +pnpm install +``` + +**3. Run the `dev` server to see a live preview and have your changes reflected at `http://localhost:3000`** + +```bash +pnpm dev +``` + +**4. Run `build` and `lint`** + +```bash +pnpm run build +pnpm run check +``` + +## Project Structure + +### Content Organization + +The documentation content is organized in the `content/` directory with the following structure: + +``` +content/ +├── community-contracts/ # Community-contributed contracts +├── confidential-contracts/ # Confidential/privacy-focused contracts +├── contracts/ # Core OpenZeppelin Contracts documentation +├── contracts-cairo/ # Cairo contracts for StarkNet +├── contracts-compact/ # Compact contract implementations +├── contracts-stylus/ # Stylus contracts for Arbitrum +├── ui-builder/ # UI Builder documentation +├── defender/ # Defender platform documentation +├── monitor/ # Monitoring tools documentation +├── relayer/ # Relayer service documentation +├── stellar-contracts/ # Stellar blockchain contracts +├── substrate-runtimes/ # Substrate runtime documentation +├── uniswap-hooks/ # Uniswap v4 hooks +├── upgrades-plugins/ # Upgrade plugins documentation +├── upgrades.mdx # General upgrades guide +└── wizard.mdx # Contract wizard documentation +``` + +Each product directory contains: +- `index.mdx` - Main documentation entry point +- `changelog.mdx` - Version history and changes +- Subdirectories for specific features/modules +- API reference files + +### Application Structure + +| Path | Description | +| --------------------------------------- | -------------------------------------------------------------- | +| `src/app/(docs)/` | Documentation pages route group | +| `src/app/(docs)/layout.tsx` | Docs layout wrapper | +| `src/app/(docs)/[...slug]/page.tsx` | Dynamic documentation pages | +| `src/app/page.tsx` | Homepage | +| `src/app/layout.tsx` | Root application layout | +| `src/app/layout.config.tsx` | Shared layout configuration and navigation | +| `src/components/` | Reusable React components | +| `src/components/layout/` | Layout-specific components | +| `src/components/icons/` | Custom SVG icons for products | +| `src/components/ui/` | UI component library | +| `src/lib/source.ts` | Content source adapter with Fumadocs loader | + +### Configuration Files + +- `source.config.ts` - Fumadocs MDX configuration with math, mermaid, and code highlighting +- `next.config.mjs` - Next.js configuration +- `postcss.config.mjs` - PostCSS configuration for styling + +## Navigation & Components + +### Navigation Structure + +The top navigation is configured in `src/app/layout.config.tsx` and includes: + +- **Main Navigation**: Home, Forum, Website links +- **Product Categories**: Auto-generated from content structure +- **Search**: Full-text search across all documentation +- **Theme Toggle**: Light/dark mode switching + +Sidebar navigation is handled in `src/navigation/` where multiple navigation JSON trees are exported and used inside `src/components/layout/docs-layout-client.tsx` + +### Key Components + +#### Layout Components +- `DocsLayoutClient` - Client-side docs layout with sidebar +- `BaseLayoutProps` - Shared layout configuration +- `PageClient` - Individual page wrapper + +#### UI Components +- `Card` & `SmallCard` - Content cards for homepage +- `TOC` - Table of contents with scrollspy +- `Search` - Search interface with custom results +- `ThemeToggle` - Theme switching +- `VersionBanner` - Version-specific messaging + +#### Custom Icons +Product-specific icons located in `src/components/icons/`: +- Ethereum, Arbitrum, StarkNet, Stellar, Polkadot chains +- Product icons for Contracts, Defender, Monitor, etc. +- Tool icons for Wizard, Ethernaut, etc. + +### Content Features + +#### MDX Enhancements +- **Math Support**: LaTeX math rendering with KaTeX +- **Mermaid Diagrams**: Flowcharts and diagrams +- **Code Highlighting**: Multi-theme syntax highlighting +- **OpenAPI Integration**: Automatic API documentation generation + +#### Interactive Elements +- **OpenZeppelin Wizard**: Embedded contract generation tool +- **Code Examples**: Copy-to-clipboard functionality +- **Version Switching**: Multi-version documentation support +- **Responsive Design**: Mobile-optimized navigation and content + +## Solidity Docgen + +Any library using Solidity Docgen can utilize the `docgen` templates and config file in their repo to generate markdown API references for the docs. To get started follow the instructions below: + +### 1. Add the templates to your repo + +Inside this docs repo is the [`docgen`](https://github.com/OpenZeppelin/docs/tree/main/docgen) folder which contains [`templates-md`](https://github.com/OpenZeppelin/docs/tree/main/docgen/templates-md) and [`config-md.js`](https://github.com/OpenZeppelin/docs/blob/main/docgen/config-md.js). Copy both of these items into your `docs` folder in your repo. Once there open the [`templates-md/helpers.js`](https://github.com/OpenZeppelin/docs/blob/main/docgen/templates-md/helpers.js) file and update the `API_DOCS_PATH` constant to match your export path. + +```js +const API_DOCS_PATH = 'contracts/5.x/api'; +// const API_DOCS_PATH = 'community-contracts/api'; +// const API_DOCS_PATH = 'confidential-contracts/api'; +// const API_DOCS_PATH = 'uniswap-hooks/api'; +``` + +### 2. Update the `hardhat.config.js` file + +With the `config-md.js` file now in the `docs` folder, update your `hardhat.config.js` to use the new config file. + +```js +{ + // other config options + docgen: require('./docs/config-md'), +} +``` + +Once added make sure these are accessible in your branches going forward. If you are generating an API reference for previous branches you will need to repeat steps 1 and 2 for those branches. + +### 3. Run the `generate-api-docs.js` script + +With your remote repo setup with the new template files you can run the `scripts/generate-api-docs.js` script. Be sure to pass in the correct arguements for your docs + +```bash +node scripts/generate-api-docs.js \ + --repo https://github.com/OpenZeppelin/openzeppelin-community-contracts.git \ + --branch release-v5.5 \ + --api-output content/contracts/5.x/api \ + --examples-output examples +``` + +This wil lexport the contents to + +### Automated Setup + +In the case you want to setup an automated GitHub workflow to create these API docs visit the [docs-api-generation-workflows](https://github.com/OpenZeppelin/docs-api-generation-workflows) for more info. This repo (`OpenZeppelin/docs`) is the `Docs Receiver` side of the equation. + +## Content Management + +### Adding New Content + +1. Create `.mdx` files in appropriate `content/` subdirectories +2. Use frontmatter for metadata (title, description, etc.) +3. Follow existing directory structure for consistency +4. Update navigation if adding new product categories + +### Versioning + +- Version-specific content in numbered subdirectories (e.g., `contracts/4.x/`) +- Latest content at root level of each product directory +- Automatic version detection and routing + +## Learn More + +To learn more about the technologies used: + +- [Next.js Documentation](https://nextjs.org/docs) - React framework features and API +- [Fumadocs](https://fumadocs.vercel.app) - Documentation framework +- [MDX](https://mdxjs.com/) - Markdown with JSX components diff --git a/docs/STANDARDS.md b/docs/STANDARDS.md new file mode 100644 index 00000000..b5ef85a2 --- /dev/null +++ b/docs/STANDARDS.md @@ -0,0 +1,80 @@ +## OpenZeppelin Documentation Standards + +OpenZeppelin has implemented a categorization standard to be used across all ecosystems to help ensure our contributors cover the bases for the ideal documentation: + +![From [`https://docs.divio.com/documentation-system/`](https://docs.divio.com/documentation-system/)](https://docs.divio.com/assets/images/overview-8b21327c9a55ca08c6712f26bda2113c.png) + +From [`https://docs.divio.com/documentation-system/`](https://docs.divio.com/documentation-system/) + +The OpenZeppelin approach to achieve this goal will include the following sections + +### Quickstart + +*Zero to working project in one page* + +👤 Primary Owner: Library Developers + +⊞ Quadrant: Problem-Oriented/How-To Guides + +📖 [Quickstart Example](https://docs.openzeppelin.com/relayer/quickstart) + + +### Learn + +*Conceptual learning about the library or tool that include example usage code snippets* + +👤 Primary Owner: Library Developers + +⊞ Quadrant: Understanding-Oriented/Explanation + +📖 [Learn Example](https://docs.openzeppelin.com/contracts/5.x/utilities) + + +### Guides / Tutorials + +*Step by step guides to achieve a particular goal or solve a specific problem* + +👤 Primary Owner: Devrel + +⊞ Quadrants: Problem-Oriented/How-To Guides & Learning-Oriented/Tutorials + +📖 [Guide Example](https://docs.openzeppelin.com/contracts/5.x/learn/webauthn-smart-accounts) + + +### Reference + +*Easy to access answers for a specific implementation* + +👤 Primary Owner: Library Developers + +⊞ Quadrants: Information-Oriented/Reference + +📖 [Reference Example](https://docs.openzeppelin.com/contracts-cairo/alpha/api/access) + + +It should be noted that not every library or tool may have every section, and that’s ok. This is purely a guide to help kickstart helpful documentation, not a strict pattern to follow. + + +## Implementation + +- All developers should keep this pattern in mind as they build out their documentation +- Devrel will step in to help create tutorials for releases, contributions from primary developers welcome if time allows +- Devrel will review all documentation PRs for QA and provide feedback + +## FAQ + +- **Who should my Quickstart target?** + + Write your quickstart as if the reader has basic dev knowledge (node.js, some solidity, CLI tools, etc) but knows nothing about your library or how to get it started with particular ecosystem tools. By all means link out to other instructions that help new users get their dev environment setup + +- **What if my tool or library has documentation that doesn’t fit into these categories?** + + That’s ok! Ideally everything should fit into these categories, but if not then craft your docs for the optimal user experience first and foremost. If you’re unsure then contact @Steve Simkins + +- **What if I don’t have enough content to fill a category (ie there’s just one guide)** + + If you find yourself in a case where the categorization doesn’t fit because there isn’t enough content to fill those sections, refactor them to fit your use-case. @Steve Simkins will review changes to make sure they align. + +- **What if I don’t know how to communicate the concepts or ideals behind what I’m building?** + + This is normal! Writing and communication is a skill that takes time. If you find yourself stuck or need assistance with writing docs just let @Steve Simkins know! diff --git a/docs/antora.yml b/docs/antora.yml deleted file mode 100644 index c1059d1e..00000000 --- a/docs/antora.yml +++ /dev/null @@ -1,8 +0,0 @@ -name: contracts-compact -title: Contracts for Compact -version: 0.0.1 -nav: - - modules/ROOT/nav.adoc -asciidoc: - attributes: - page-sidebar-collapse-default: 'Access,Security,FungibleToken,NonFungibleToken,MultiToken,Utils' diff --git a/docs/biome.json b/docs/biome.json new file mode 100644 index 00000000..8575558f --- /dev/null +++ b/docs/biome.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "suspicious": { + "noUnknownAtRules": "off" + }, + "security": { + "noDangerouslySetInnerHtml": "off" + }, + "correctness": { + "useUniqueElementIds": "off" + }, + "a11y": { + "useKeyWithClickEvents": "off", + "noStaticElementInteractions": "off" + } + } + }, + "css": { + "linter": { + "enabled": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } +} diff --git a/docs/cli.json b/docs/cli.json new file mode 100644 index 00000000..8a9e3361 --- /dev/null +++ b/docs/cli.json @@ -0,0 +1,11 @@ +{ + "aliases": { + "uiDir": "./components/ui", + "componentsDir": "./components", + "blockDir": "./components", + "cssDir": "./styles", + "libDir": "./lib" + }, + "baseDir": "src", + "commands": {} +} \ No newline at end of file diff --git a/docs/components.json b/docs/components.json new file mode 100644 index 00000000..e81dc038 --- /dev/null +++ b/docs/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/app/global.css", + "baseColor": "slate", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/docs/content/community-contracts/account-modules.mdx b/docs/content/community-contracts/account-modules.mdx new file mode 100644 index 00000000..e00cd757 --- /dev/null +++ b/docs/content/community-contracts/account-modules.mdx @@ -0,0 +1,120 @@ +--- +title: Account Modules +--- + +Smart accounts built with [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579) provide a standardized way to extend account functionality through modules (i.e. smart contract instances). This architecture allows accounts to support various features that are compatible with a wide variety of account implementations. See [compatible modules](https://erc7579.com/modules). + +## ERC-7579 + +ERC-7579 defines a standardized interface for modular smart accounts. This standard enables accounts to install, uninstall, and interact with modules that extend their capabilities in a composable manner with different account implementations. + +### Accounts + +OpenZeppelin offers an implementation of an [`AccountERC7579`](/contracts/5.x/api/account#AccountERC7579) contract that allows installing modules compliant with this standard. There’s also an [`AccountERC7579Hooked`](/contracts/5.x/api/account#AccountERC7579Hooked) variant that supports installation of hooks. Like [most accounts](/contracts/5.x/accounts#handling-initialization), an instance should define an initializer function where the first module that controls the account will be set: + +./examples/account/MyAccountERC7579.sol + + +For simplicity, the [`AccountERC7579Hooked`](/contracts/5.x/api/account#AccountERC7579Hooked) only supports a single hook. A common workaround is to install a [single hook with a multiplexer pattern](https://github.com/rhinestonewtf/core-modules/blob/7afffccb44d73dbaca2481e7b92bce0621ea6449/src/HookMultiPlexer/HookMultiPlexer.sol) to extend the functionality to multiple hooks. + + +### Modules + +Functionality is added to accounts through encapsulated functionality deployed as smart contracts called _modules_. The standard defines four primary module types: + +* **Validator modules (type 1)**: Handle signature verification and user operation validation +* **Executor modules (type 2)**: Execute operations on behalf of the account +* **Fallback modules (type 3)**: Handle fallback calls for specific function selectors +* **Hook modules (type 4)**: Execute logic before and after operations + +Modules can implement multiple types simultaneously, which means you could combine an executor module with hooks to enforce behaviors on an account, such as maintaining ERC-20 approvals or preventing the removal of certain permissions. + +See [popular module implementations](https://erc7579.com/modules). + +#### Building Custom Modules + +The library provides _standard composable modules_ as building blocks with an internal API for developers. By combining these components, you can create a rich set of variants without including unnecessary features. + +A good starting point is the [`ERC7579Executor`](/community-contracts/api/account#ERC7579Executor) or [`ERC7579Validator`](/community-contracts/api/account#ERC7579Validator), which include an opinionated base layer easily combined with other abstract modules. Hooks and fallback handlers are more straightforward to implement directly from interfaces: + +./examples/account/modules/MyERC7579Modules.sol + + +Explore these abstract ERC-7579 modules in the [API Reference](/community-contracts/api/account#modules). + + +#### Execution Modes + +ERC-7579 supports various execution modes, which are encoded as a `bytes32` value. The [`ERC7579Utils`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/account/utils/draft-ERC7579Utils.sol) library provides utility functions to work with these modes: + +```solidity +// Parts of an execution mode +type Mode is bytes32; +type CallType is bytes1; +type ExecType is bytes1; +type ModeSelector is bytes4; +type ModePayload is bytes22; +``` + +##### Call Types + +Call types determine the kind of execution: + +| Type | Value | Description | +| --- | --- | --- | +| `CALLTYPE_SINGLE` | `0x00` | A single `call` execution | +| `CALLTYPE_BATCH` | `0x01` | A batch of `call` executions | +| `CALLTYPE_DELEGATECALL` | `0xFF` | A `delegatecall` execution | + +##### Execution Types + +Execution types determine how failures are handled: + +| Type | Value | Description | +| --- | --- | --- | +| `EXECTYPE_DEFAULT` | `0x00` | Reverts on failure | +| `EXECTYPE_TRY` | `0x01` | Does not revert on failure, emits an event instead | + +#### Execution Data Format + +The execution data format varies depending on the call type: + +* For single calls: `abi.encodePacked(target, value, callData)` +* For batched calls: `abi.encode(Execution[])` where `Execution` is a struct containing `target`, `value`, and `callData` +* For delegate calls: `abi.encodePacked(target, callData)` + +## Examples + +### Social Recovery + +Social recovery allows an account to be recovered when access is lost by relying on trusted parties ("guardians") who verify the user’s identity and help restore access. + +Social recovery is not a single solution but a design space with multiple configuration options: + +* Delay configuration +* Expiration settings +* Different guardian types +* Cancellation windows +* Confirmation requirements + +To support _different guardian types_, we can leverage ERC-7913 as discussed in the [multisig](/contracts/5.x/multisig#beyond-standard-signature-verification) section. For ERC-7579 modules, this is implemented through the [`ERC7579Multisig`](/community-contracts/api/account#ERC7579Multisig) validator. + +Combined with an [`ERC7579Executor`](/community-contracts/api/account#ERC7579Executor), it provides a basic foundation that can be extended with more sophisticated features: + +./examples/account/modules/MyERC7579SocialRecovery.sol + +For enhanced security, you can extend this foundation with scheduling, delays, and cancellations using [`ERC7579DelayedExecutor`](/community-contracts/api/account#ERC7579DelayedExecutor). This allows guardians to schedule recovery operations with a time delay, providing a security window to detect and cancel suspicious recovery attempts before they execute: + +./examples/account/modules/MyERC7579DelayedSocialRecovery.sol + + +The delayed executor’s signature validation doesn’t require a nonce since operations are uniquely identified by their [operation id](/community-contracts/api/account#ERC7579DelayedExecutor-hashOperation-address-bytes32-bytes32-bytes-) and cannot be scheduled twice. + + +These implementations demonstrate how to build progressively more secure social recovery mechanisms, from basic multi-signature recovery to time-delayed recovery with cancellation capabilities. + +For additional functionality, developers can use: + +* [`ERC7579MultisigWeighted`](/community-contracts/api/account#ERC7579MultisigWeighted) to assign different weights to signers +* [`ERC7579MultisigConfirmation`](/community-contracts/api/account#ERC7579MultisigConfirmation) to implement a confirmation system that verifies signatures when adding signers +* [`ERC7579MultisigStorage`](/community-contracts/api/account#ERC7579MultisigStorage) to allow guardians to presign recovery operations for more flexible coordination diff --git a/docs/content/community-contracts/api/access.mdx b/docs/content/community-contracts/api/access.mdx new file mode 100644 index 00000000..1ab5cae1 --- /dev/null +++ b/docs/content/community-contracts/api/access.mdx @@ -0,0 +1,463 @@ +--- +title: "Access" +description: "Smart contract access utilities and implementations" +--- + +This directory contains utility contracts to restrict access control in smart contracts. These include: + +* [`AccessManagerLight`](#AccessManagerLight): A simpler version of an AccessManager that uses `bytes8` roles to allow function calls identified by their 4-bytes selector. + +## AccessManager + +[`AccessManagerLight`](#AccessManagerLight) + + + +
+ +## `AccessManagerLight` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/access/manager/AccessManagerLight.sol"; +``` + +Light version of an AccessManager contract that defines `bytes8` roles +that are stored as requirements (see [`AccessManagerLight.getRequirements`](#AccessManagerLight-getRequirements-address-bytes4-)) for each function. + +Each requirement is a bitmask of roles that are allowed to call a function +identified by its `bytes4` selector. Users have their permissioned stored +as a bitmask of roles they belong to. + +The admin role is a special role that has access to all functions and can +manage the roles of other users. + +
+

Modifiers

+
+- [onlyRole(requirement)](#AccessManagerLight-onlyRole-Masks-Mask-) +
+
+ +
+

Functions

+
+- [constructor(admin)](#AccessManagerLight-constructor-address-) +- [canCall(caller, target, selector)](#AccessManagerLight-canCall-address-address-bytes4-) +- [getGroups(user)](#AccessManagerLight-getGroups-address-) +- [getGroupAdmins(group)](#AccessManagerLight-getGroupAdmins-uint8-) +- [getRequirements(target, selector)](#AccessManagerLight-getRequirements-address-bytes4-) +- [addGroup(user, group)](#AccessManagerLight-addGroup-address-uint8-) +- [remGroup(user, group)](#AccessManagerLight-remGroup-address-uint8-) +- [_addGroup(user, group)](#AccessManagerLight-_addGroup-address-uint8-) +- [_remGroup(user, group)](#AccessManagerLight-_remGroup-address-uint8-) +- [setGroupAdmins(group, admins)](#AccessManagerLight-setGroupAdmins-uint8-uint8---) +- [_setGroupAdmins(group, admins)](#AccessManagerLight-_setGroupAdmins-uint8-Masks-Mask-) +- [setRequirements(target, selectors, groups)](#AccessManagerLight-setRequirements-address-bytes4---uint8---) +- [_setRequirements(target, selector, groups)](#AccessManagerLight-_setRequirements-address-bytes4-Masks-Mask-) +- [ADMIN_ROLE()](#AccessManagerLight-ADMIN_ROLE-uint8) +- [PUBLIC_ROLE()](#AccessManagerLight-PUBLIC_ROLE-uint8) +- [ADMIN_MASK()](#AccessManagerLight-ADMIN_MASK-Masks-Mask) +- [PUBLIC_MASK()](#AccessManagerLight-PUBLIC_MASK-Masks-Mask) +#### IAuthority [!toc] +
+
+ +
+

Events

+
+- [GroupAdded(user, group)](#AccessManagerLight-GroupAdded-address-uint8-) +- [GroupRemoved(user, group)](#AccessManagerLight-GroupRemoved-address-uint8-) +- [GroupAdmins(group, admins)](#AccessManagerLight-GroupAdmins-uint8-Masks-Mask-) +- [RequirementsSet(target, selector, groups)](#AccessManagerLight-RequirementsSet-address-bytes4-Masks-Mask-) +#### IAuthority [!toc] +
+
+ +
+

Errors

+
+- [MissingPermissions(user, permissions, requirement)](#AccessManagerLight-MissingPermissions-address-Masks-Mask-Masks-Mask-) +#### IAuthority [!toc] +
+
+ + + +
+
+

onlyRole(Masks.Mask requirement)

+
+

internal

+# +
+
+ +
+ +Throws if the specified requirement is not met by the caller's permissions (see [`AccessManagerLight.getGroups`](#AccessManagerLight-getGroups-address-)). + +
+
+ + + +
+
+

constructor(address admin)

+
+

public

+# +
+
+
+ +Initializes the contract with the `admin` as the first member of the admin group. + +
+
+ + + +
+
+

canCall(address caller, address target, bytes4 selector) → bool

+
+

public

+# +
+
+
+ +Returns whether the `caller` has the required permissions to call the `target` with the `selector`. + +
+
+ + + +
+
+

getGroups(address user) → Masks.Mask

+
+

public

+# +
+
+
+ +Returns the groups that the `user` belongs to. + +
+
+ + + +
+
+

getGroupAdmins(uint8 group) → Masks.Mask

+
+

public

+# +
+
+
+ +Returns the admins of the `group`. + +
+
+ + + +
+
+

getRequirements(address target, bytes4 selector) → Masks.Mask

+
+

public

+# +
+
+
+ +Returns the requirements for the `target` and `selector`. + +
+
+ + + +
+
+

addGroup(address user, uint8 group)

+
+

public

+# +
+
+
+ +Adds the `user` to the `group`. Emits [`AccessManagerLight.GroupAdded`](#AccessManagerLight-GroupAdded-address-uint8-) event. + +
+
+ + + +
+
+

remGroup(address user, uint8 group)

+
+

public

+# +
+
+
+ +Removes the `user` from the `group`. Emits [`AccessManagerLight.GroupRemoved`](#AccessManagerLight-GroupRemoved-address-uint8-) event. + +
+
+ + + +
+
+

_addGroup(address user, uint8 group)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManagerLight.addGroup`](#AccessManagerLight-addGroup-address-uint8-) without access control. + +
+
+ + + +
+
+

_remGroup(address user, uint8 group)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManagerLight.remGroup`](#AccessManagerLight-remGroup-address-uint8-) without access control. + +
+
+ + + +
+
+

setGroupAdmins(uint8 group, uint8[] admins)

+
+

public

+# +
+
+
+ +Sets the `admins` of the `group`. Emits [`AccessManagerLight.GroupAdmins`](#AccessManagerLight-GroupAdmins-uint8-Masks-Mask-) event. + +
+
+ + + +
+
+

_setGroupAdmins(uint8 group, Masks.Mask admins)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManagerLight._setGroupAdmins`](#AccessManagerLight-_setGroupAdmins-uint8-Masks-Mask-) without access control. + +
+
+ + + +
+
+

setRequirements(address target, bytes4[] selectors, uint8[] groups)

+
+

public

+# +
+
+
+ +Sets the `groups` requirements for the `selectors` of the `target`. + +
+
+ + + +
+
+

_setRequirements(address target, bytes4 selector, Masks.Mask groups)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManagerLight._setRequirements`](#AccessManagerLight-_setRequirements-address-bytes4-Masks-Mask-) without access control. + +
+
+ + + +
+
+

ADMIN_ROLE() → uint8

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

PUBLIC_ROLE() → uint8

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

ADMIN_MASK() → Masks.Mask

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

PUBLIC_MASK() → Masks.Mask

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

GroupAdded(address indexed user, uint8 indexed group)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

GroupRemoved(address indexed user, uint8 indexed group)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

GroupAdmins(uint8 indexed group, Masks.Mask admins)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

RequirementsSet(address indexed target, bytes4 indexed selector, Masks.Mask groups)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

MissingPermissions(address user, Masks.Mask permissions, Masks.Mask requirement)

+
+

error

+# +
+
+
+ +
+
diff --git a/docs/content/community-contracts/api/account.mdx b/docs/content/community-contracts/api/account.mdx new file mode 100644 index 00000000..adab75d5 --- /dev/null +++ b/docs/content/community-contracts/api/account.mdx @@ -0,0 +1,4041 @@ +--- +title: "Account" +description: "Smart contract account utilities and implementations" +--- + +This directory includes contracts to build accounts for ERC-4337. These include: + +* [`ERC7579Executor`](#ERC7579Executor): An executor module that enables executing calls from accounts where the it’s installed. +* [`ERC7579DelayedExecutor`](#ERC7579DelayedExecutor): An executor module that adds a delay before executing an account operation. +* [`ERC7579SelectorExecutor`](#ERC7579SelectorExecutor): An executor module that restricts execution to specific function selectors. +* [`ERC7579Validator`](#ERC7579Validator): Abstract validator module for ERC-7579 accounts that provides base implementation for signature validation. +* [`ERC7579Signature`](#ERC7579Signature): Implementation of [`ERC7579Validator`](#ERC7579Validator) using ERC-7913 signature verification for address-less cryptographic keys and account signatures. +* [`ERC7579Multisig`](#ERC7579Multisig): An extension of [`ERC7579Validator`](#ERC7579Validator) that enables validation using ERC-7913 signer keys. +* [`ERC7579MultisigWeighted`](#ERC7579MultisigWeighted): An extension of [`ERC7579Multisig`](#ERC7579Multisig) that allows different weights to be assigned to signers. +* [`ERC7579MultisigConfirmation`](#ERC7579MultisigConfirmation): An extension of [`ERC7579Multisig`](#ERC7579Multisig) that requires each signer to provide a confirmation signature. +* [`ERC7579MultisigStorage`](#ERC7579MultisigStorage): An extension of [`ERC7579Multisig`](#ERC7579Multisig) that allows storing presigned approvals in storage. +* [`PaymasterCore`](#PaymasterCore): An ERC-4337 paymaster implementation that includes the core logic to validate and pay for user operations. +* [`PaymasterERC20`](#PaymasterERC20): A paymaster that allows users to pay for user operations using ERC-20 tokens. +* [`PaymasterERC20Guarantor`](#PaymasterERC20Guarantor): A paymaster that enables third parties to guarantee user operations by pre-funding gas costs, with the option for users to repay or for guarantors to absorb the cost. +* [`PaymasterERC721Owner`](#PaymasterERC721Owner): A paymaster that allows users to pay for user operations based on ERC-721 ownership. +* [`PaymasterSigner`](#PaymasterSigner): A paymaster that allows users to pay for user operations using an authorized signature. + +## Modules + +### Executors + +[`ERC7579Executor`](#ERC7579Executor) + +[`ERC7579DelayedExecutor`](#ERC7579DelayedExecutor) + +[`ERC7579SelectorExecutor`](#ERC7579SelectorExecutor) + +### Validators + +[`ERC7579Validator`](#ERC7579Validator) + +[`ERC7579Signature`](#ERC7579Signature) + +[`ERC7579Multisig`](#ERC7579Multisig) + +[`ERC7579MultisigWeighted`](#ERC7579MultisigWeighted) + +[`ERC7579MultisigConfirmation`](#ERC7579MultisigConfirmation) + +[`ERC7579MultisigStorage`](#ERC7579MultisigStorage) + +## Paymaster + +[`PaymasterCore`](#PaymasterCore) + +[`PaymasterERC20`](#PaymasterERC20) + +[`PaymasterERC20Guarantor`](#PaymasterERC20Guarantor) + +[`PaymasterERC721Owner`](#PaymasterERC721Owner) + +[`PaymasterSigner`](#PaymasterSigner) + + + +
+ +## `ERC7579DelayedExecutor` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579DelayedExecutor.sol"; +``` + +Extension of [`ERC7579Executor`](#ERC7579Executor) that allows scheduling and executing delayed operations +with expiration. This module enables time-delayed execution patterns for smart accounts. + +==== Operation Lifecycle + +1. Scheduling: Operations are scheduled via [`ERC7579DelayedExecutor.schedule`](#ERC7579DelayedExecutor-schedule-address-bytes32-bytes32-bytes-) with a specified delay period. +The delay period is set during [`ERC7579DelayedExecutor.onInstall`](#ERC7579DelayedExecutor-onInstall-bytes-) and can be customized via [`ERC7579DelayedExecutor.setDelay`](#ERC7579DelayedExecutor-setDelay-uint32-). Each +operation enters a `Scheduled` state and must wait for its delay period to elapse. + +2. Security Window: During the delay period, operations remain in `Scheduled` state but +cannot be executed. Through this period, suspicious operations can be monitored and +canceled via [`ERC7579DelayedExecutor.cancel`](#ERC7579DelayedExecutor-cancel-address-bytes32-bytes32-bytes-) if appropriate. + +3. Execution & Expiration: Once the delay period elapses, operations transition to `Ready` state. +Operations can be executed via [`ERC7579Executor.execute`](#ERC7579Executor-execute-address-bytes32-bytes32-bytes-) and have an expiration period after becoming +executable. If an operation is not executed within the expiration period, it becomes `Expired` +and can't be executed. Expired operations must be rescheduled with a different salt. + +==== Delay Management + +Accounts can set their own delay periods during installation or via [`ERC7579DelayedExecutor.setDelay`](#ERC7579DelayedExecutor-setDelay-uint32-). +The delay period is enforced even between installas and uninstalls to prevent +immediate downgrades. When setting a new delay period, the new delay takes effect +after a transition period defined by the current delay or [`ERC7579DelayedExecutor.minSetback`](#ERC7579DelayedExecutor-minSetback--), whichever +is longer. + +==== Authorization + +Authorization for scheduling and canceling operations is controlled through the [`ERC7579DelayedExecutor._validateSchedule`](#ERC7579DelayedExecutor-_validateSchedule-address-bytes32-bytes32-bytes-) +and [`ERC7579DelayedExecutor._validateCancel`](#ERC7579DelayedExecutor-_validateCancel-address-bytes32-bytes32-bytes-) functions. These functions can be overridden to implement custom +authorization logic, such as requiring specific signers or roles. + + +Use [`ERC7579DelayedExecutor._scheduleAt`](#ERC7579DelayedExecutor-_scheduleAt-address-bytes32-bytes32-bytes-uint48-uint32-) to schedule operations at a specific points in time. This is +useful to pre-schedule operations for non-deployed accounts (e.g. subscriptions). + + +
+

Functions

+
+- [state(account, salt, mode, executionCalldata)](#ERC7579DelayedExecutor-state-address-bytes32-bytes32-bytes-) +- [state(operationId)](#ERC7579DelayedExecutor-state-bytes32-) +- [minSetback()](#ERC7579DelayedExecutor-minSetback--) +- [getDelay(account)](#ERC7579DelayedExecutor-getDelay-address-) +- [getExpiration(account)](#ERC7579DelayedExecutor-getExpiration-address-) +- [getSchedule(account, salt, mode, executionCalldata)](#ERC7579DelayedExecutor-getSchedule-address-bytes32-bytes32-bytes-) +- [getSchedule(operationId)](#ERC7579DelayedExecutor-getSchedule-bytes32-) +- [hashOperation(account, salt, mode, executionCalldata)](#ERC7579DelayedExecutor-hashOperation-address-bytes32-bytes32-bytes-) +- [defaultExpiration()](#ERC7579DelayedExecutor-defaultExpiration--) +- [onInstall(initData)](#ERC7579DelayedExecutor-onInstall-bytes-) +- [setDelay(newDelay)](#ERC7579DelayedExecutor-setDelay-uint32-) +- [setExpiration(newExpiration)](#ERC7579DelayedExecutor-setExpiration-uint32-) +- [schedule(account, salt, mode, data)](#ERC7579DelayedExecutor-schedule-address-bytes32-bytes32-bytes-) +- [cancel(account, salt, mode, data)](#ERC7579DelayedExecutor-cancel-address-bytes32-bytes32-bytes-) +- [onUninstall()](#ERC7579DelayedExecutor-onUninstall-bytes-) +- [_validateExecution(, , , data)](#ERC7579DelayedExecutor-_validateExecution-address-bytes32-bytes32-bytes-) +- [_validateCancel(account, , , )](#ERC7579DelayedExecutor-_validateCancel-address-bytes32-bytes32-bytes-) +- [_validateSchedule(account, , , )](#ERC7579DelayedExecutor-_validateSchedule-address-bytes32-bytes32-bytes-) +- [_setDelay(account, newDelay, minimumSetback)](#ERC7579DelayedExecutor-_setDelay-address-uint32-uint32-) +- [_setExpiration(account, newExpiration)](#ERC7579DelayedExecutor-_setExpiration-address-uint32-) +- [_scheduleAt(account, salt, mode, executionCalldata, timepoint, delay)](#ERC7579DelayedExecutor-_scheduleAt-address-bytes32-bytes32-bytes-uint48-uint32-) +- [_execute(account, salt, mode, executionCalldata)](#ERC7579DelayedExecutor-_execute-address-bytes32-bytes32-bytes-) +- [_cancel(account, mode, executionCalldata, salt)](#ERC7579DelayedExecutor-_cancel-address-bytes32-bytes-bytes32-) +- [_validateStateBitmap(operationId, allowedStates)](#ERC7579DelayedExecutor-_validateStateBitmap-bytes32-bytes32-) +- [_encodeStateBitmap(operationState)](#ERC7579DelayedExecutor-_encodeStateBitmap-enum-ERC7579DelayedExecutor-OperationState-) +#### ERC7579Executor [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Executor-isModuleType-uint256-) +- [execute(account, salt, mode, data)](#ERC7579Executor-execute-address-bytes32-bytes32-bytes-) +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+- [ERC7579ExecutorOperationScheduled(account, operationId, salt, mode, executionCalldata, schedule)](#ERC7579DelayedExecutor-ERC7579ExecutorOperationScheduled-address-bytes32-bytes32-bytes32-bytes-uint48-) +- [ERC7579ExecutorOperationCanceled(account, operationId)](#ERC7579DelayedExecutor-ERC7579ExecutorOperationCanceled-address-bytes32-) +- [ERC7579ExecutorDelayUpdated(account, newDelay, effectTime)](#ERC7579DelayedExecutor-ERC7579ExecutorDelayUpdated-address-uint32-uint48-) +- [ERC7579ExecutorExpirationUpdated(account, newExpiration)](#ERC7579DelayedExecutor-ERC7579ExecutorExpirationUpdated-address-uint32-) +#### ERC7579Executor [!toc] +- [ERC7579ExecutorOperationExecuted(account, salt, mode, executionCalldata)](#ERC7579Executor-ERC7579ExecutorOperationExecuted-address-bytes32-bytes32-bytes-) +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+- [ERC7579ExecutorUnexpectedOperationState(operationId, currentState, allowedStates)](#ERC7579DelayedExecutor-ERC7579ExecutorUnexpectedOperationState-bytes32-enum-ERC7579DelayedExecutor-OperationState-bytes32-) +- [ERC7579ExecutorModuleNotInstalled()](#ERC7579DelayedExecutor-ERC7579ExecutorModuleNotInstalled--) +#### ERC7579Executor [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

state(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → enum ERC7579DelayedExecutor.OperationState

+
+

public

+# +
+
+
+ +Current state of an operation. + +
+
+ + + +
+
+

state(bytes32 operationId) → enum ERC7579DelayedExecutor.OperationState

+
+

public

+# +
+
+
+ +Same as [`ERC7579DelayedExecutor.state`](#ERC7579DelayedExecutor-state-bytes32-), but for a specific operation id. + +
+
+ + + +
+
+

minSetback() → uint32

+
+

public

+# +
+
+
+ +Minimum delay after which [`ERC7579DelayedExecutor.setDelay`](#ERC7579DelayedExecutor-setDelay-uint32-) takes effect. +Set as default delay if not provided during [`ERC7579DelayedExecutor.onInstall`](#ERC7579DelayedExecutor-onInstall-bytes-). + +
+
+ + + +
+
+

getDelay(address account) → uint32 delay, uint32 pendingDelay, uint48 effectTime

+
+

public

+# +
+
+
+ +Delay for a specific account. + +
+
+ + + +
+
+

getExpiration(address account) → uint32 expiration

+
+

public

+# +
+
+
+ +Expiration delay for account operations. + +
+
+ + + +
+
+

getSchedule(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → uint48 scheduledAt, uint48 executableAt, uint48 expiresAt

+
+

public

+# +
+
+
+ +Schedule for an operation. Returns default values if not set (i.e. `uint48(0)`, `uint48(0)`, `uint48(0)`). + +
+
+ + + +
+
+

getSchedule(bytes32 operationId) → uint48 scheduledAt, uint48 executableAt, uint48 expiresAt

+
+

public

+# +
+
+
+ +Same as [`ERC7579DelayedExecutor.getSchedule`](#ERC7579DelayedExecutor-getSchedule-bytes32-) but with the operation id. + +
+
+ + + +
+
+

hashOperation(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → bytes32

+
+

public

+# +
+
+
+ +Returns the operation id. + +
+
+ + + +
+
+

defaultExpiration() → uint32

+
+

public

+# +
+
+
+ +Default expiration for account operations. Set if not provided during [`ERC7579DelayedExecutor.onInstall`](#ERC7579DelayedExecutor-onInstall-bytes-). + +
+
+ + + +
+
+

onInstall(bytes initData)

+
+

public

+# +
+
+
+ +Sets up the module's initial configuration when installed by an account. +The account calling this function becomes registered with the module. + +The `initData` may be `abi.encode(uint32(initialDelay), uint32(initialExpiration))`. +The delay will be set to the maximum of this value and the minimum delay if provided. +Otherwise, the delay will be set to [`ERC7579DelayedExecutor.minSetback`](#ERC7579DelayedExecutor-minSetback--) and [`ERC7579DelayedExecutor.defaultExpiration`](#ERC7579DelayedExecutor-defaultExpiration--) respectively. + +Behaves as a no-op if the module is already installed. + +Requirements: + +* The account (i.e `msg.sender`) must implement the `IERC7579ModuleConfig` interface. +* `initData` must be empty or decode correctly to `(uint32, uint32)`. + +
+
+ + + +
+
+

setDelay(uint32 newDelay)

+
+

public

+# +
+
+
+ +Allows an account to update its execution delay (see [`ERC7579DelayedExecutor.getDelay`](#ERC7579DelayedExecutor-getDelay-address-)). + +The new delay will take effect after a transition period defined by the current delay +or [`ERC7579DelayedExecutor.minSetback`](#ERC7579DelayedExecutor-minSetback--), whichever is longer. This prevents immediate security downgrades. +Can only be called by the account itself. + +
+
+ + + +
+
+

setExpiration(uint32 newExpiration)

+
+

public

+# +
+
+
+ +Allows an account to update its execution expiration (see [`ERC7579DelayedExecutor.getExpiration`](#ERC7579DelayedExecutor-getExpiration-address-)). + +
+
+ + + +
+
+

schedule(address account, bytes32 salt, bytes32 mode, bytes data)

+
+

public

+# +
+
+
+ +Schedules an operation to be executed after the account's delay period (see [`ERC7579DelayedExecutor.getDelay`](#ERC7579DelayedExecutor-getDelay-address-)). +Operations are uniquely identified by the combination of `salt`, `mode`, and `data`. +See [`ERC7579DelayedExecutor._validateSchedule`](#ERC7579DelayedExecutor-_validateSchedule-address-bytes32-bytes32-bytes-) for authorization checks. + +
+
+ + + +
+
+

cancel(address account, bytes32 salt, bytes32 mode, bytes data)

+
+

public

+# +
+
+
+ +Cancels a previously scheduled operation. Can only be called by the account that +scheduled the operation. See [`ERC7579DelayedExecutor._cancel`](#ERC7579DelayedExecutor-_cancel-address-bytes32-bytes-bytes32-). + +
+
+ + + +
+
+

onUninstall(bytes)

+
+

public

+# +
+
+
+ +Cleans up the [`ERC7579DelayedExecutor.getDelay`](#ERC7579DelayedExecutor-getDelay-address-) and [`ERC7579DelayedExecutor.getExpiration`](#ERC7579DelayedExecutor-getExpiration-address-) values by scheduling them to `0` +and respecting the previous delay and expiration values. + + +This function does not clean up scheduled operations. This means operations +could potentially be re-executed if the module is reinstalled later. This is a deliberate +design choice for efficiency, but module implementations may want to override this behavior +to clear scheduled operations during uninstallation for their specific use cases. + + + +Calling this function directly will remove the expiration ([`ERC7579DelayedExecutor.getExpiration`](#ERC7579DelayedExecutor-getExpiration-address-)) value and +will schedule a reset of the delay ([`ERC7579DelayedExecutor.getDelay`](#ERC7579DelayedExecutor-getDelay-address-)) to `0` for the account. Reinstalling the +module will not immediately reset the delay if the delay reset hasn't taken effect yet. + + +
+
+ + + +
+
+

_validateExecution(address, bytes32, bytes32, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Returns `data` as the execution calldata. See [`ERC7579Executor._execute`](#ERC7579Executor-_execute-address-bytes32-bytes32-bytes-). + + +This function relies on the operation state validation in [`ERC7579DelayedExecutor._execute`](#ERC7579DelayedExecutor-_execute-address-bytes32-bytes32-bytes-) for +authorization. Extensions of this module should override this function to implement +additional validation logic if needed. + + +
+
+ + + +
+
+

_validateCancel(address account, bytes32, bytes32, bytes)

+
+

internal

+# +
+
+
+ +Validates whether an operation can be canceled. + +Example extension: + +```solidity + function _validateCancel(address account, bytes32 salt, bytes32 mode, bytes calldata data) internal override { + // e.g. require(msg.sender == account); + } +``` + +
+
+ + + +
+
+

_validateSchedule(address account, bytes32, bytes32, bytes)

+
+

internal

+# +
+
+
+ +Validates whether an operation can be scheduled. + +Example extension: + +```solidity + function _validateSchedule(address account, bytes32 salt, bytes32 mode, bytes calldata data) internal override { + // e.g. require(msg.sender == account); + } +``` + +
+
+ + + +
+
+

_setDelay(address account, uint32 newDelay, uint32 minimumSetback)

+
+

internal

+# +
+
+
+ +Internal implementation for setting an account's delay. See [`ERC7579DelayedExecutor.getDelay`](#ERC7579DelayedExecutor-getDelay-address-). + +Emits an [`ERC7579DelayedExecutor.ERC7579ExecutorDelayUpdated`](#ERC7579DelayedExecutor-ERC7579ExecutorDelayUpdated-address-uint32-uint48-) event. + +
+
+ + + +
+
+

_setExpiration(address account, uint32 newExpiration)

+
+

internal

+# +
+
+
+ +Internal implementation for setting an account's expiration. See [`ERC7579DelayedExecutor.getExpiration`](#ERC7579DelayedExecutor-getExpiration-address-). + +Emits an [`ERC7579DelayedExecutor.ERC7579ExecutorExpirationUpdated`](#ERC7579DelayedExecutor-ERC7579ExecutorExpirationUpdated-address-uint32-) event. + +
+
+ + + +
+
+

_scheduleAt(address account, bytes32 salt, bytes32 mode, bytes executionCalldata, uint48 timepoint, uint32 delay) → bytes32 operationId, struct ERC7579DelayedExecutor.Schedule schedule_

+
+

internal

+# +
+
+
+ +Internal version of [`ERC7579DelayedExecutor.schedule`](#ERC7579DelayedExecutor-schedule-address-bytes32-bytes32-bytes-) that takes an `account` address to schedule +an operation that starts its security window at `at` and expires after `delay`. + +Requirements: + +* The operation must be `Unknown`. + +Emits an [`ERC7579DelayedExecutor.ERC7579ExecutorOperationScheduled`](#ERC7579DelayedExecutor-ERC7579ExecutorOperationScheduled-address-bytes32-bytes32-bytes32-bytes-uint48-) event. + +
+
+ + + +
+
+

_execute(address account, bytes32 salt, bytes32 mode, bytes executionCalldata) → bytes[] returnData

+
+

internal

+# +
+
+
+ +See [`ERC7579Executor._execute`](#ERC7579Executor-_execute-address-bytes32-bytes32-bytes-). + +Requirements: + +* The operation must be `Ready`. + +
+
+ + + +
+
+

_cancel(address account, bytes32 mode, bytes executionCalldata, bytes32 salt)

+
+

internal

+# +
+
+
+ +Internal version of [`ERC7579DelayedExecutor.cancel`](#ERC7579DelayedExecutor-cancel-address-bytes32-bytes32-bytes-) that takes an `account` address as an argument. + +Requirements: + +* The operation must be `Scheduled` or `Ready`. + +Canceled operations can't be rescheduled. Emits an [`ERC7579DelayedExecutor.ERC7579ExecutorOperationCanceled`](#ERC7579DelayedExecutor-ERC7579ExecutorOperationCanceled-address-bytes32-) event. + +
+
+ + + +
+
+

_validateStateBitmap(bytes32 operationId, bytes32 allowedStates) → enum ERC7579DelayedExecutor.OperationState

+
+

internal

+# +
+
+
+ +Check that the current state of a operation matches the requirements described by the `allowedStates` bitmap. +This bitmap should be built using [`ERC7579DelayedExecutor._encodeStateBitmap`](#ERC7579DelayedExecutor-_encodeStateBitmap-enum-ERC7579DelayedExecutor-OperationState-). + +If requirements are not met, reverts with a [`ERC7579DelayedExecutor.ERC7579ExecutorUnexpectedOperationState`](#ERC7579DelayedExecutor-ERC7579ExecutorUnexpectedOperationState-bytes32-enum-ERC7579DelayedExecutor-OperationState-bytes32-) error. + +
+
+ + + +
+
+

_encodeStateBitmap(enum ERC7579DelayedExecutor.OperationState operationState) → bytes32

+
+

internal

+# +
+
+
+ +Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to +the underlying position in the `OperationState` enum. For example: + +``` +0x000...10000 + ^^^^^^------ ... + ^----- Canceled + ^---- Executed + ^--- Ready + ^-- Scheduled + ^- Unknown +``` + +
+
+ + + +
+
+

ERC7579ExecutorOperationScheduled(address indexed account, bytes32 indexed operationId, bytes32 salt, bytes32 mode, bytes executionCalldata, uint48 schedule)

+
+

event

+# +
+
+ +
+ +Emitted when a new operation is scheduled. + +
+
+ + +
+
+

ERC7579ExecutorOperationCanceled(address indexed account, bytes32 indexed operationId)

+
+

event

+# +
+
+ +
+ +Emitted when a new operation is canceled. + +
+
+ + +
+
+

ERC7579ExecutorDelayUpdated(address indexed account, uint32 newDelay, uint48 effectTime)

+
+

event

+# +
+
+ +
+ +Emitted when the execution delay is updated. + +
+
+ + +
+
+

ERC7579ExecutorExpirationUpdated(address indexed account, uint32 newExpiration)

+
+

event

+# +
+
+ +
+ +Emitted when the expiration delay is updated. + +
+
+ + + +
+
+

ERC7579ExecutorUnexpectedOperationState(bytes32 operationId, enum ERC7579DelayedExecutor.OperationState currentState, bytes32 allowedStates)

+
+

error

+# +
+
+
+ +The current state of a operation is not the expected. The `expectedStates` is a bitmap with the +bits enabled for each OperationState enum position counting from right to left. See [`ERC7579DelayedExecutor._encodeStateBitmap`](#ERC7579DelayedExecutor-_encodeStateBitmap-enum-ERC7579DelayedExecutor-OperationState-). + + +If `expectedState` is `bytes32(0)`, the operation is expected to not be in any state (i.e. not exist). + + +
+
+ + + +
+
+

ERC7579ExecutorModuleNotInstalled()

+
+

error

+# +
+
+
+ +The module is not installed on the account. + +
+
+ + + +
+ +## `ERC7579Executor` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579Executor.sol"; +``` + +Basic implementation for ERC-7579 executor modules that provides execution functionality +for smart accounts. + +The module enables accounts to execute arbitrary operations, leveraging the execution +capabilities defined in the ERC-7579 standard. Developers can customize whether an operation +can be executed with custom rules by implementing the [`ERC7579DelayedExecutor._validateExecution`](#ERC7579DelayedExecutor-_validateExecution-address-bytes32-bytes32-bytes-) function in +derived contracts. + + +This is a simplified executor that directly executes operations without delay or expiration +mechanisms. For a more advanced implementation with time-delayed execution patterns and +security features, see [`ERC7579DelayedExecutor`](#ERC7579DelayedExecutor). + + +
+

Functions

+
+- [isModuleType(moduleTypeId)](#ERC7579Executor-isModuleType-uint256-) +- [execute(account, salt, mode, data)](#ERC7579Executor-execute-address-bytes32-bytes32-bytes-) +- [_validateExecution(account, salt, mode, data)](#ERC7579Executor-_validateExecution-address-bytes32-bytes32-bytes-) +- [_execute(account, mode, salt, executionCalldata)](#ERC7579Executor-_execute-address-bytes32-bytes32-bytes-) +#### IERC7579Module [!toc] +- [onInstall(data)](#IERC7579Module-onInstall-bytes-) +- [onUninstall(data)](#IERC7579Module-onUninstall-bytes-) +
+
+ +
+

Events

+
+- [ERC7579ExecutorOperationExecuted(account, salt, mode, executionCalldata)](#ERC7579Executor-ERC7579ExecutorOperationExecuted-address-bytes32-bytes32-bytes-) +#### IERC7579Module [!toc] +
+
+ + + +
+
+

isModuleType(uint256 moduleTypeId) → bool

+
+

public

+# +
+
+
+ +Returns boolean value if module is a certain type + +
+
+ + + +
+
+

execute(address account, bytes32 salt, bytes32 mode, bytes data) → bytes[] returnData

+
+

public

+# +
+
+
+ +Executes an operation and returns the result data from the executed operation. +Restricted to the account itself by default. See [`ERC7579DelayedExecutor._execute`](#ERC7579DelayedExecutor-_execute-address-bytes32-bytes32-bytes-) for requirements and +[`ERC7579DelayedExecutor._validateExecution`](#ERC7579DelayedExecutor-_validateExecution-address-bytes32-bytes32-bytes-) for authorization checks. + +
+
+ + + +
+
+

_validateExecution(address account, bytes32 salt, bytes32 mode, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Validates whether the execution can proceed. This function is called before executing +the operation and returns the execution calldata to be used. + +Example extension: + +```solidity + function _validateExecution(address account, bytes32 salt, bytes32 mode, bytes calldata data) + internal + override + returns (bytes calldata) + { + // custom logic + return data; + } +``` + + +Pack extra data in the `data` arguments (e.g. a signature) to be used in the +validation process. Calldata can be sliced to extract it and return only the +execution calldata. + + +
+
+ + + +
+
+

_execute(address account, bytes32 mode, bytes32 salt, bytes executionCalldata) → bytes[] returnData

+
+

internal

+# +
+
+
+ +Internal version of [`ERC7579Executor.execute`](#ERC7579Executor-execute-address-bytes32-bytes32-bytes-). Emits [`ERC7579Executor.ERC7579ExecutorOperationExecuted`](#ERC7579Executor-ERC7579ExecutorOperationExecuted-address-bytes32-bytes32-bytes-) event. + +Requirements: + +* The `account` must implement the `IERC7579Execution-executeFromExecutor` function. + +
+
+ + + +
+
+

ERC7579ExecutorOperationExecuted(address indexed account, bytes32 salt, bytes32 mode, bytes executionCalldata)

+
+

event

+# +
+
+ +
+ +Emitted when an operation is executed. + +
+
+ + + +
+ +## `ERC7579Multisig` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579Multisig.sol"; +``` + +Implementation of an [`ERC7579Validator`](#ERC7579Validator) that uses ERC-7913 signers for multisignature +validation. + +This module provides a base implementation for multisignature validation that can be +attached to any function through the [`ERC7579Multisig._rawERC7579Validation`](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-) internal function. The signers +are represented using the ERC-7913 format, which concatenates a verifier address and +a key: `verifier || key`. + +A smart account with this module installed can require multiple signers to approve +operations before they are executed, such as requiring 3-of-5 guardians to approve +a social recovery operation. + +
+

Functions

+
+- [onInstall(initData)](#ERC7579Multisig-onInstall-bytes-) +- [onUninstall()](#ERC7579Multisig-onUninstall-bytes-) +- [getSigners(account, start, end)](#ERC7579Multisig-getSigners-address-uint64-uint64-) +- [getSignerCount(account)](#ERC7579Multisig-getSignerCount-address-) +- [isSigner(account, signer)](#ERC7579Multisig-isSigner-address-bytes-) +- [threshold(account)](#ERC7579Multisig-threshold-address-) +- [addSigners(newSigners)](#ERC7579Multisig-addSigners-bytes---) +- [removeSigners(oldSigners)](#ERC7579Multisig-removeSigners-bytes---) +- [setThreshold(newThreshold)](#ERC7579Multisig-setThreshold-uint64-) +- [_rawERC7579Validation(account, hash, signature)](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-) +- [_addSigners(account, newSigners)](#ERC7579Multisig-_addSigners-address-bytes---) +- [_removeSigners(account, oldSigners)](#ERC7579Multisig-_removeSigners-address-bytes---) +- [_setThreshold(account, newThreshold)](#ERC7579Multisig-_setThreshold-address-uint64-) +- [_validateReachableThreshold(account)](#ERC7579Multisig-_validateReachableThreshold-address-) +- [_validateSignatures(account, hash, signingSigners, signatures)](#ERC7579Multisig-_validateSignatures-address-bytes32-bytes---bytes---) +- [_validateThreshold(account, validatingSigners)](#ERC7579Multisig-_validateThreshold-address-bytes---) +#### ERC7579Validator [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Validator-isModuleType-uint256-) +- [validateUserOp(userOp, userOpHash)](#ERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(, hash, signature)](#ERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+- [ERC7913SignerAdded(account, signer)](#ERC7579Multisig-ERC7913SignerAdded-address-bytes-) +- [ERC7913SignerRemoved(account, signer)](#ERC7579Multisig-ERC7913SignerRemoved-address-bytes-) +- [ERC7913ThresholdSet(account, threshold)](#ERC7579Multisig-ERC7913ThresholdSet-address-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+- [ERC7579MultisigAlreadyExists(signer)](#ERC7579Multisig-ERC7579MultisigAlreadyExists-bytes-) +- [ERC7579MultisigNonexistentSigner(signer)](#ERC7579Multisig-ERC7579MultisigNonexistentSigner-bytes-) +- [ERC7579MultisigInvalidSigner(signer)](#ERC7579Multisig-ERC7579MultisigInvalidSigner-bytes-) +- [ERC7579MultisigZeroThreshold()](#ERC7579Multisig-ERC7579MultisigZeroThreshold--) +- [ERC7579MultisigUnreachableThreshold(signers, threshold)](#ERC7579Multisig-ERC7579MultisigUnreachableThreshold-uint64-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

onInstall(bytes initData)

+
+

public

+# +
+
+
+ +Sets up the module's initial configuration when installed by an account. +See [`ERC7579DelayedExecutor.onInstall`](#ERC7579DelayedExecutor-onInstall-bytes-). Besides the delay setup, the `initdata` can +include `signers` and `threshold`. + +The initData should be encoded as: +`abi.encode(bytes[] signers, uint64 threshold)` + +If no signers or threshold are provided, the multisignature functionality will be +disabled until they are added later. + + +An account can only call onInstall once. If called directly by the account, +the signer will be set to the provided data. Future installations will behave as a no-op. + + +
+
+ + + +
+
+

onUninstall(bytes)

+
+

public

+# +
+
+
+ +Cleans up module's configuration when uninstalled from an account. +Clears all signers and resets the threshold. + +See [`ERC7579DelayedExecutor.onUninstall`](#ERC7579DelayedExecutor-onUninstall-bytes-). + + +This function has unbounded gas costs and may become uncallable if the set grows too large. +See `EnumerableSet-clear`. + + +
+
+ + + +
+
+

getSigners(address account, uint64 start, uint64 end) → bytes[]

+
+

public

+# +
+
+
+ +Returns a slice of the set of authorized signers for the specified account. + +Using `start = 0` and `end = type(uint64).max` will return the entire set of signers. + + +Depending on the `start` and `end`, this operation can copy a large amount of data to memory, which +can be expensive. This is designed for view accessors queried without gas fees. Using it in state-changing +functions may become uncallable if the slice grows too large. + + +
+
+ + + +
+
+

getSignerCount(address account) → uint256

+
+

public

+# +
+
+
+ +Returns the number of authorized signers for the specified account. + +
+
+ + + +
+
+

isSigner(address account, bytes signer) → bool

+
+

public

+# +
+
+
+ +Returns whether the `signer` is an authorized signer for the specified account. + +
+
+ + + +
+
+

threshold(address account) → uint64

+
+

public

+# +
+
+
+ +Returns the minimum number of signers required to approve a multisignature operation +for the specified account. + +
+
+ + + +
+
+

addSigners(bytes[] newSigners)

+
+

public

+# +
+
+
+ +Adds new signers to the authorized set for the calling account. +Can only be called by the account itself. + +Requirements: + +* Each of `newSigners` must be at least 20 bytes long. +* Each of `newSigners` must not be already authorized. + +
+
+ + + +
+
+

removeSigners(bytes[] oldSigners)

+
+

public

+# +
+
+
+ +Removes signers from the authorized set for the calling account. +Can only be called by the account itself. + +Requirements: + +* Each of `oldSigners` must be authorized. +* After removal, the threshold must still be reachable. + +
+
+ + + +
+
+

setThreshold(uint64 newThreshold)

+
+

public

+# +
+
+
+ +Sets the threshold for the calling account. +Can only be called by the account itself. + +Requirements: + +* The threshold must be reachable with the current number of signers. + +
+
+ + + +
+
+

_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Returns whether the number of valid signatures meets or exceeds the +threshold set for the target account. + +The signature should be encoded as: +`abi.encode(bytes[] signingSigners, bytes[] signatures)` + +Where `signingSigners` are the authorized signers and signatures are their corresponding +signatures of the operation `hash`. + +
+
+ + + +
+
+

_addSigners(address account, bytes[] newSigners)

+
+

internal

+# +
+
+
+ +Adds the `newSigners` to those allowed to sign on behalf of the account. + +Requirements: + +* Each of `newSigners` must be at least 20 bytes long. Reverts with [`ERC7579Multisig.ERC7579MultisigInvalidSigner`](#ERC7579Multisig-ERC7579MultisigInvalidSigner-bytes-) if not. +* Each of `newSigners` must not be authorized. Reverts with [`ERC7579Multisig.ERC7579MultisigAlreadyExists`](#ERC7579Multisig-ERC7579MultisigAlreadyExists-bytes-) if it already exists. + +
+
+ + + +
+
+

_removeSigners(address account, bytes[] oldSigners)

+
+

internal

+# +
+
+
+ +Removes the `oldSigners` from the authorized signers for the account. + +Requirements: + +* Each of `oldSigners` must be authorized. Reverts with [`ERC7579Multisig.ERC7579MultisigNonexistentSigner`](#ERC7579Multisig-ERC7579MultisigNonexistentSigner-bytes-) if not. +* The threshold must remain reachable after removal. See [`ERC7579Multisig._validateReachableThreshold`](#ERC7579Multisig-_validateReachableThreshold-address-) for details. + +
+
+ + + +
+
+

_setThreshold(address account, uint64 newThreshold)

+
+

internal

+# +
+
+
+ +Sets the signatures `threshold` required to approve a multisignature operation. + +Requirements: + +* The threshold must be greater than 0. Reverts with [`ERC7579Multisig.ERC7579MultisigZeroThreshold`](#ERC7579Multisig-ERC7579MultisigZeroThreshold--) if not. +* The threshold must be reachable with the current number of signers. See [`ERC7579Multisig._validateReachableThreshold`](#ERC7579Multisig-_validateReachableThreshold-address-) for details. + +
+
+ + + +
+
+

_validateReachableThreshold(address account)

+
+

internal

+# +
+
+
+ +Validates the current threshold is reachable with the number of [`ERC7579Multisig._signersSetByAccount`](#ERC7579Multisig-_signersSetByAccount-mapping-address----struct-EnumerableSet-BytesSet-). + +Requirements: + +* The number of signers must be >= the threshold. Reverts with [`ERC7579Multisig.ERC7579MultisigUnreachableThreshold`](#ERC7579Multisig-ERC7579MultisigUnreachableThreshold-uint64-uint64-) if not. + +
+
+ + + +
+
+

_validateSignatures(address account, bytes32 hash, bytes[] signingSigners, bytes[] signatures) → bool valid

+
+

internal

+# +
+
+
+ +Validates the signatures using the signers and their corresponding signatures. +Returns whether the signers are authorized and the signatures are valid for the given hash. + +The signers must be ordered by their `keccak256` hash to prevent duplications and to optimize +the verification process. The function will return `false` if any signer is not authorized or +if the signatures are invalid for the given hash. + +Requirements: + +* The `signatures` array must be at least the `signers` array's length. + +
+
+ + + +
+
+

_validateThreshold(address account, bytes[] validatingSigners) → bool

+
+

internal

+# +
+
+
+ +Validates that the number of signers meets the [`ERC7579Multisig.threshold`](#ERC7579Multisig-threshold-address-) requirement. +Assumes the signers were already validated. See [`ERC7579Multisig._validateSignatures`](#ERC7579Multisig-_validateSignatures-address-bytes32-bytes---bytes---) for more details. + +
+
+ + + +
+
+

ERC7913SignerAdded(address indexed account, bytes signer)

+
+

event

+# +
+
+ +
+ +Emitted when signers are added. + +
+
+ + +
+
+

ERC7913SignerRemoved(address indexed account, bytes signer)

+
+

event

+# +
+
+ +
+ +Emitted when signers are removed. + +
+
+ + +
+
+

ERC7913ThresholdSet(address indexed account, uint64 threshold)

+
+

event

+# +
+
+ +
+ +Emitted when the threshold is updated. + +
+
+ + + +
+
+

ERC7579MultisigAlreadyExists(bytes signer)

+
+

error

+# +
+
+
+ +The `signer` already exists. + +
+
+ + + +
+
+

ERC7579MultisigNonexistentSigner(bytes signer)

+
+

error

+# +
+
+
+ +The `signer` does not exist. + +
+
+ + + +
+
+

ERC7579MultisigInvalidSigner(bytes signer)

+
+

error

+# +
+
+
+ +The `signer` is less than 20 bytes long. + +
+
+ + + +
+
+

ERC7579MultisigZeroThreshold()

+
+

error

+# +
+
+
+ +The `threshold` is zero. + +
+
+ + + +
+
+

ERC7579MultisigUnreachableThreshold(uint64 signers, uint64 threshold)

+
+

error

+# +
+
+
+ +The `threshold` is unreachable given the number of `signers`. + +
+
+ + + +
+ +## `ERC7579MultisigConfirmation` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579MultisigConfirmation.sol"; +``` + +Extension of [`ERC7579Multisig`](#ERC7579Multisig) that requires explicit confirmation signatures +from new signers when they are being added to the multisig. + +This module ensures that only willing participants can be added as signers to a +multisig by requiring each new signer to provide a valid signature confirming their +consent to be added. Each signer must sign an EIP-712 message to confirm their addition. + + +Use this module to ensure that all guardians in a social recovery or multisig setup have +explicitly agreed to their roles. + + +
+

Functions

+
+- [_signableConfirmationHash(account, deadline)](#ERC7579MultisigConfirmation-_signableConfirmationHash-address-uint256-) +- [_addSigners(account, newSigners)](#ERC7579MultisigConfirmation-_addSigners-address-bytes---) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC7579Multisig [!toc] +- [onInstall(initData)](#ERC7579Multisig-onInstall-bytes-) +- [onUninstall()](#ERC7579Multisig-onUninstall-bytes-) +- [getSigners(account, start, end)](#ERC7579Multisig-getSigners-address-uint64-uint64-) +- [getSignerCount(account)](#ERC7579Multisig-getSignerCount-address-) +- [isSigner(account, signer)](#ERC7579Multisig-isSigner-address-bytes-) +- [threshold(account)](#ERC7579Multisig-threshold-address-) +- [addSigners(newSigners)](#ERC7579Multisig-addSigners-bytes---) +- [removeSigners(oldSigners)](#ERC7579Multisig-removeSigners-bytes---) +- [setThreshold(newThreshold)](#ERC7579Multisig-setThreshold-uint64-) +- [_rawERC7579Validation(account, hash, signature)](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-) +- [_removeSigners(account, oldSigners)](#ERC7579Multisig-_removeSigners-address-bytes---) +- [_setThreshold(account, newThreshold)](#ERC7579Multisig-_setThreshold-address-uint64-) +- [_validateReachableThreshold(account)](#ERC7579Multisig-_validateReachableThreshold-address-) +- [_validateSignatures(account, hash, signingSigners, signatures)](#ERC7579Multisig-_validateSignatures-address-bytes32-bytes---bytes---) +- [_validateThreshold(account, validatingSigners)](#ERC7579Multisig-_validateThreshold-address-bytes---) +#### ERC7579Validator [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Validator-isModuleType-uint256-) +- [validateUserOp(userOp, userOpHash)](#ERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(, hash, signature)](#ERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC7579Multisig [!toc] +- [ERC7913SignerAdded(account, signer)](#ERC7579Multisig-ERC7913SignerAdded-address-bytes-) +- [ERC7913SignerRemoved(account, signer)](#ERC7579Multisig-ERC7913SignerRemoved-address-bytes-) +- [ERC7913ThresholdSet(account, threshold)](#ERC7579Multisig-ERC7913ThresholdSet-address-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+- [ERC7579MultisigInvalidConfirmationSignature(signer)](#ERC7579MultisigConfirmation-ERC7579MultisigInvalidConfirmationSignature-bytes-) +- [ERC7579MultisigExpiredConfirmation(deadline)](#ERC7579MultisigConfirmation-ERC7579MultisigExpiredConfirmation-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC7579Multisig [!toc] +- [ERC7579MultisigAlreadyExists(signer)](#ERC7579Multisig-ERC7579MultisigAlreadyExists-bytes-) +- [ERC7579MultisigNonexistentSigner(signer)](#ERC7579Multisig-ERC7579MultisigNonexistentSigner-bytes-) +- [ERC7579MultisigInvalidSigner(signer)](#ERC7579Multisig-ERC7579MultisigInvalidSigner-bytes-) +- [ERC7579MultisigZeroThreshold()](#ERC7579Multisig-ERC7579MultisigZeroThreshold--) +- [ERC7579MultisigUnreachableThreshold(signers, threshold)](#ERC7579Multisig-ERC7579MultisigUnreachableThreshold-uint64-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

_signableConfirmationHash(address account, uint256 deadline) → bytes32

+
+

internal

+# +
+
+
+ +Generates a hash that signers must sign to confirm their addition to the multisig of `account`. + +
+
+ + + +
+
+

_addSigners(address account, bytes[] newSigners)

+
+

internal

+# +
+
+
+ +Extends [`ERC7579Multisig._addSigners`](#ERC7579Multisig-_addSigners-address-bytes---) _addSigners to require confirmation signatures +Each entry in newSigners must be ABI-encoded as: + +```solidity +abi.encode(deadline,signer,signature); // uint256, bytes, bytes +``` + +* signer: The ERC-7913 signer to add +* signature: The signature from this signer confirming their addition + +The function verifies each signature before adding the signer. If any signature is invalid, +the function reverts with [`ERC7579MultisigConfirmation.ERC7579MultisigInvalidConfirmationSignature`](#ERC7579MultisigConfirmation-ERC7579MultisigInvalidConfirmationSignature-bytes-). + +
+
+ + + +
+
+

ERC7579MultisigInvalidConfirmationSignature(bytes signer)

+
+

error

+# +
+
+
+ +Error thrown when a `signer`'s confirmation signature is invalid + +
+
+ + + +
+
+

ERC7579MultisigExpiredConfirmation(uint256 deadline)

+
+

error

+# +
+
+
+ +Error thrown when a confirmation signature has expired + +
+
+ + + +
+ +## `ERC7579MultisigStorage` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579MultisigStorage.sol"; +``` + +Extension of [`ERC7579Multisig`](#ERC7579Multisig) that allows storing presigned approvals in storage. + +This module extends the multisignature module to allow signers to presign operations, +which are then stored in a mapping and can be used during validation. This enables +more flexible multisignature workflows where signatures can be collected over time +without requiring all signers to be online simultaneously. + +When validating signatures, if a signature is empty, it indicates a presignature +and the validation will check the storage mapping instead of cryptographic verification. + +
+

Functions

+
+- [presigned(account, signer, hash)](#ERC7579MultisigStorage-presigned-address-bytes-bytes32-) +- [presign(account, signer, hash, signature)](#ERC7579MultisigStorage-presign-address-bytes-bytes32-bytes-) +- [_validateSignatures(account, hash, signingSigners, signatures)](#ERC7579MultisigStorage-_validateSignatures-address-bytes32-bytes---bytes---) +#### ERC7579Multisig [!toc] +- [onInstall(initData)](#ERC7579Multisig-onInstall-bytes-) +- [onUninstall()](#ERC7579Multisig-onUninstall-bytes-) +- [getSigners(account, start, end)](#ERC7579Multisig-getSigners-address-uint64-uint64-) +- [getSignerCount(account)](#ERC7579Multisig-getSignerCount-address-) +- [isSigner(account, signer)](#ERC7579Multisig-isSigner-address-bytes-) +- [threshold(account)](#ERC7579Multisig-threshold-address-) +- [addSigners(newSigners)](#ERC7579Multisig-addSigners-bytes---) +- [removeSigners(oldSigners)](#ERC7579Multisig-removeSigners-bytes---) +- [setThreshold(newThreshold)](#ERC7579Multisig-setThreshold-uint64-) +- [_rawERC7579Validation(account, hash, signature)](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-) +- [_addSigners(account, newSigners)](#ERC7579Multisig-_addSigners-address-bytes---) +- [_removeSigners(account, oldSigners)](#ERC7579Multisig-_removeSigners-address-bytes---) +- [_setThreshold(account, newThreshold)](#ERC7579Multisig-_setThreshold-address-uint64-) +- [_validateReachableThreshold(account)](#ERC7579Multisig-_validateReachableThreshold-address-) +- [_validateThreshold(account, validatingSigners)](#ERC7579Multisig-_validateThreshold-address-bytes---) +#### ERC7579Validator [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Validator-isModuleType-uint256-) +- [validateUserOp(userOp, userOpHash)](#ERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(, hash, signature)](#ERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+- [ERC7579MultisigStoragePresigned(account, hash, signer)](#ERC7579MultisigStorage-ERC7579MultisigStoragePresigned-address-bytes32-bytes-) +#### ERC7579Multisig [!toc] +- [ERC7913SignerAdded(account, signer)](#ERC7579Multisig-ERC7913SignerAdded-address-bytes-) +- [ERC7913SignerRemoved(account, signer)](#ERC7579Multisig-ERC7913SignerRemoved-address-bytes-) +- [ERC7913ThresholdSet(account, threshold)](#ERC7579Multisig-ERC7913ThresholdSet-address-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+#### ERC7579Multisig [!toc] +- [ERC7579MultisigAlreadyExists(signer)](#ERC7579Multisig-ERC7579MultisigAlreadyExists-bytes-) +- [ERC7579MultisigNonexistentSigner(signer)](#ERC7579Multisig-ERC7579MultisigNonexistentSigner-bytes-) +- [ERC7579MultisigInvalidSigner(signer)](#ERC7579Multisig-ERC7579MultisigInvalidSigner-bytes-) +- [ERC7579MultisigZeroThreshold()](#ERC7579Multisig-ERC7579MultisigZeroThreshold--) +- [ERC7579MultisigUnreachableThreshold(signers, threshold)](#ERC7579Multisig-ERC7579MultisigUnreachableThreshold-uint64-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

presigned(address account, bytes signer, bytes32 hash) → bool

+
+

public

+# +
+
+
+ +Returns whether a signer has presigned a specific hash for the account + +
+
+ + + +
+
+

presign(address account, bytes signer, bytes32 hash, bytes signature)

+
+

public

+# +
+
+
+ +Allows a signer to presign a hash by providing a valid signature. +The signature will be verified and if valid, the presignature will be stored. + +Emits [`ERC7579MultisigStorage.ERC7579MultisigStoragePresigned`](#ERC7579MultisigStorage-ERC7579MultisigStoragePresigned-address-bytes32-bytes-) if the signature is valid and the hash is not already +signed, otherwise acts as a no-op. + + +Does not check if the signer is authorized for the account. Valid signatures from +invalid signers won't be executable. See [`ERC7579Multisig._validateSignatures`](#ERC7579Multisig-_validateSignatures-address-bytes32-bytes---bytes---) for more details. + + +
+
+ + + +
+
+

_validateSignatures(address account, bytes32 hash, bytes[] signingSigners, bytes[] signatures) → bool valid

+
+

internal

+# +
+
+
+ +See [`ERC7579Multisig._validateSignatures`](#ERC7579Multisig-_validateSignatures-address-bytes32-bytes---bytes---). + +If a signature is empty, it indicates a presignature and the validation will check the storage mapping +instead of cryptographic verification. See [`ERC7579Multisig._signersSetByAccount`](#ERC7579Multisig-_signersSetByAccount-mapping-address----struct-EnumerableSet-BytesSet-) for more details. + +
+
+ + + +
+
+

ERC7579MultisigStoragePresigned(address indexed account, bytes32 indexed hash, bytes signer)

+
+

event

+# +
+
+ +
+ +Emitted when a signer signs a hash + +
+
+ + + +
+ +## `ERC7579MultisigWeighted` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579MultisigWeighted.sol"; +``` + +Extension of [`ERC7579Multisig`](#ERC7579Multisig) that supports weighted signatures. + +This module extends the multisignature module to allow assigning different weights +to each signer, enabling more flexible governance schemes. For example, some guardians +could have higher weight than others, allowing for weighted voting or prioritized authorization. + +Example use case: + +A smart account with this module installed can schedule social recovery operations +after obtaining approval from guardians with sufficient total weight (e.g., requiring +a total weight of 10, with 3 guardians weighted as 5, 3, and 2), and then execute them +after the time delay has passed. + + +When setting a threshold value, ensure it 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 +signatures with a total weight of at least 4 (e.g., one with weight 1 and one with weight 3). + + +
+

Functions

+
+- [onInstall(initData)](#ERC7579MultisigWeighted-onInstall-bytes-) +- [onUninstall(data)](#ERC7579MultisigWeighted-onUninstall-bytes-) +- [signerWeight(account, signer)](#ERC7579MultisigWeighted-signerWeight-address-bytes-) +- [totalWeight(account)](#ERC7579MultisigWeighted-totalWeight-address-) +- [setSignerWeights(signers, weights)](#ERC7579MultisigWeighted-setSignerWeights-bytes---uint64---) +- [_setSignerWeights(account, signers, weights)](#ERC7579MultisigWeighted-_setSignerWeights-address-bytes---uint64---) +- [_addSigners(account, newSigners)](#ERC7579MultisigWeighted-_addSigners-address-bytes---) +- [_removeSigners(account, oldSigners)](#ERC7579MultisigWeighted-_removeSigners-address-bytes---) +- [_validateReachableThreshold(account)](#ERC7579MultisigWeighted-_validateReachableThreshold-address-) +- [_validateThreshold(account, validatingSigners)](#ERC7579MultisigWeighted-_validateThreshold-address-bytes---) +#### ERC7579Multisig [!toc] +- [getSigners(account, start, end)](#ERC7579Multisig-getSigners-address-uint64-uint64-) +- [getSignerCount(account)](#ERC7579Multisig-getSignerCount-address-) +- [isSigner(account, signer)](#ERC7579Multisig-isSigner-address-bytes-) +- [threshold(account)](#ERC7579Multisig-threshold-address-) +- [addSigners(newSigners)](#ERC7579Multisig-addSigners-bytes---) +- [removeSigners(oldSigners)](#ERC7579Multisig-removeSigners-bytes---) +- [setThreshold(newThreshold)](#ERC7579Multisig-setThreshold-uint64-) +- [_rawERC7579Validation(account, hash, signature)](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-) +- [_setThreshold(account, newThreshold)](#ERC7579Multisig-_setThreshold-address-uint64-) +- [_validateSignatures(account, hash, signingSigners, signatures)](#ERC7579Multisig-_validateSignatures-address-bytes32-bytes---bytes---) +#### ERC7579Validator [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Validator-isModuleType-uint256-) +- [validateUserOp(userOp, userOpHash)](#ERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(, hash, signature)](#ERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+- [ERC7579MultisigWeightChanged(account, signer, weight)](#ERC7579MultisigWeighted-ERC7579MultisigWeightChanged-address-bytes-uint64-) +#### ERC7579Multisig [!toc] +- [ERC7913SignerAdded(account, signer)](#ERC7579Multisig-ERC7913SignerAdded-address-bytes-) +- [ERC7913SignerRemoved(account, signer)](#ERC7579Multisig-ERC7913SignerRemoved-address-bytes-) +- [ERC7913ThresholdSet(account, threshold)](#ERC7579Multisig-ERC7913ThresholdSet-address-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+- [ERC7579MultisigInvalidWeight(signer, weight)](#ERC7579MultisigWeighted-ERC7579MultisigInvalidWeight-bytes-uint64-) +- [ERC7579MultisigMismatchedLength()](#ERC7579MultisigWeighted-ERC7579MultisigMismatchedLength--) +#### ERC7579Multisig [!toc] +- [ERC7579MultisigAlreadyExists(signer)](#ERC7579Multisig-ERC7579MultisigAlreadyExists-bytes-) +- [ERC7579MultisigNonexistentSigner(signer)](#ERC7579Multisig-ERC7579MultisigNonexistentSigner-bytes-) +- [ERC7579MultisigInvalidSigner(signer)](#ERC7579Multisig-ERC7579MultisigInvalidSigner-bytes-) +- [ERC7579MultisigZeroThreshold()](#ERC7579Multisig-ERC7579MultisigZeroThreshold--) +- [ERC7579MultisigUnreachableThreshold(signers, threshold)](#ERC7579Multisig-ERC7579MultisigUnreachableThreshold-uint64-uint64-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

onInstall(bytes initData)

+
+

public

+# +
+
+
+ +Sets up the module's initial configuration when installed by an account. +Besides the standard delay and signer configuration, this can also include +signer weights. + +The initData should be encoded as: +`abi.encode(bytes[] signers, uint64 threshold, uint64[] weights)` + +If weights are not provided but signers are, all signers default to weight 1. + + +An account can only call onInstall once. If called directly by the account, +the signer will be set to the provided data. Future installations will behave as a no-op. + + +
+
+ + + +
+
+

onUninstall(bytes data)

+
+

public

+# +
+
+
+ +Cleans up module's configuration when uninstalled from an account. +Clears all signers, weights, and total weights. + +See [`ERC7579Multisig.onUninstall`](#ERC7579Multisig-onUninstall-bytes-). + +
+
+ + + +
+
+

signerWeight(address account, bytes signer) → uint64

+
+

public

+# +
+
+
+ +Gets the weight of a signer for a specific account. Returns 0 if the signer is not authorized. + +
+
+ + + +
+
+

totalWeight(address account) → uint64

+
+

public

+# +
+
+
+ +Gets the total weight of all signers for a specific account. + +
+
+ + + +
+
+

setSignerWeights(bytes[] signers, uint64[] weights)

+
+

public

+# +
+
+
+ +Sets weights for signers for the calling account. +Can only be called by the account itself. + +
+
+ + + +
+
+

_setSignerWeights(address account, bytes[] signers, uint64[] weights)

+
+

internal

+# +
+
+
+ +Sets weights for multiple signers at once. Internal version without access control. + +Requirements: + +* `signers` and `weights` arrays must have the same length. Reverts with [`ERC7579MultisigWeighted.ERC7579MultisigMismatchedLength`](#ERC7579MultisigWeighted-ERC7579MultisigMismatchedLength--) on mismatch. +* Each signer must exist in the set of authorized signers. Reverts with [`ERC7579Multisig.ERC7579MultisigNonexistentSigner`](#ERC7579Multisig-ERC7579MultisigNonexistentSigner-bytes-) if not. +* Each weight must be greater than 0. Reverts with [`ERC7579MultisigWeighted.ERC7579MultisigInvalidWeight`](#ERC7579MultisigWeighted-ERC7579MultisigInvalidWeight-bytes-uint64-) if not. +* See [`ERC7579Multisig._validateReachableThreshold`](#ERC7579Multisig-_validateReachableThreshold-address-) for the threshold validation. + +Emits [`ERC7579MultisigWeighted.ERC7579MultisigWeightChanged`](#ERC7579MultisigWeighted-ERC7579MultisigWeightChanged-address-bytes-uint64-) for each signer. + +
+
+ + + +
+
+

_addSigners(address account, bytes[] newSigners)

+
+

internal

+# +
+
+
+ +Override to add weight tracking. See [`ERC7579Multisig._addSigners`](#ERC7579Multisig-_addSigners-address-bytes---). +Each new signer has a default weight of 1. + +In cases where [`ERC7579MultisigWeighted.totalWeight`](#ERC7579MultisigWeighted-totalWeight-address-) is almost `type(uint64).max` (due to a large `_totalExtraWeight`), adding new +signers could cause the [`ERC7579MultisigWeighted.totalWeight`](#ERC7579MultisigWeighted-totalWeight-address-) computation to overflow. Adding a [`ERC7579MultisigWeighted.totalWeight`](#ERC7579MultisigWeighted-totalWeight-address-) call after the new +signers are added ensures no such overflow happens. + +
+
+ + + +
+
+

_removeSigners(address account, bytes[] oldSigners)

+
+

internal

+# +
+
+
+ +Override to handle weight tracking during removal. See [`ERC7579Multisig._removeSigners`](#ERC7579Multisig-_removeSigners-address-bytes---). + +Just like [`ERC7579Multisig._addSigners`](#ERC7579Multisig-_addSigners-address-bytes---), this function does not emit [`ERC7579MultisigWeighted.ERC7579MultisigWeightChanged`](#ERC7579MultisigWeighted-ERC7579MultisigWeightChanged-address-bytes-uint64-) events. The +[`ERC7579Multisig.ERC7913SignerRemoved`](#ERC7579Multisig-ERC7913SignerRemoved-address-bytes-) event emitted by [`ERC7579Multisig._removeSigners`](#ERC7579Multisig-_removeSigners-address-bytes---) is enough to track weights here. + +
+
+ + + +
+
+

_validateReachableThreshold(address account)

+
+

internal

+# +
+
+
+ +Override to validate threshold against total weight instead of signer count. + + +This function intentionally does not call `super._validateReachableThreshold` because the base implementation +assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple +implementations of this function may exist in the contract, so important side effects may be missed +depending on the linearization order. + + +
+
+ + + +
+
+

_validateThreshold(address account, bytes[] validatingSigners) → bool

+
+

internal

+# +
+
+
+ +Validates that the total weight of signers meets the [`ERC7579Multisig.threshold`](#ERC7579Multisig-threshold-address-) requirement. +Overrides the base implementation to use weights instead of count. + + +This function intentionally does not call `super._validateThreshold` because the base implementation +assumes each signer has a weight of 1, which is incompatible with this weighted implementation. + + +
+
+ + + +
+
+

ERC7579MultisigWeightChanged(address indexed account, bytes indexed signer, uint64 weight)

+
+

event

+# +
+
+ +
+ +Emitted when a signer's weight is changed. + + +Not emitted in [`ERC7579Multisig._addSigners`](#ERC7579Multisig-_addSigners-address-bytes---) or [`ERC7579Multisig._removeSigners`](#ERC7579Multisig-_removeSigners-address-bytes---). Indexers must rely on [`ERC7579Multisig.ERC7913SignerAdded`](#ERC7579Multisig-ERC7913SignerAdded-address-bytes-) +and [`ERC7579Multisig.ERC7913SignerRemoved`](#ERC7579Multisig-ERC7913SignerRemoved-address-bytes-) to index a default weight of 1. See [`ERC7579MultisigWeighted.signerWeight`](#ERC7579MultisigWeighted-signerWeight-address-bytes-). + + +
+
+ + + +
+
+

ERC7579MultisigInvalidWeight(bytes signer, uint64 weight)

+
+

error

+# +
+
+
+ +Thrown when a signer's weight is invalid. + +
+
+ + + +
+
+

ERC7579MultisigMismatchedLength()

+
+

error

+# +
+
+
+ +Thrown when the arrays lengths don't match. + +
+
+ + + +
+ +## `ERC7579SelectorExecutor` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579SelectorExecutor.sol"; +``` + +Implementation of an [`ERC7579Executor`](#ERC7579Executor) that allows authorizing specific function selectors +that can be executed on the account. + +This module provides a way to restrict which functions can be executed on the account by +maintaining a set of allowed function selectors. Only calls to functions with selectors +in the set will be allowed to execute. + +
+

Functions

+
+- [isAuthorized(account, selector)](#ERC7579SelectorExecutor-isAuthorized-address-bytes4-) +- [selectors(account)](#ERC7579SelectorExecutor-selectors-address-) +- [onInstall(initData)](#ERC7579SelectorExecutor-onInstall-bytes-) +- [onUninstall()](#ERC7579SelectorExecutor-onUninstall-bytes-) +- [addSelectors(newSelectors)](#ERC7579SelectorExecutor-addSelectors-bytes4---) +- [removeSelectors(oldSelectors)](#ERC7579SelectorExecutor-removeSelectors-bytes4---) +- [_addSelectors(account, newSelectors)](#ERC7579SelectorExecutor-_addSelectors-address-bytes4---) +- [_removeSelectors(account, oldSelectors)](#ERC7579SelectorExecutor-_removeSelectors-address-bytes4---) +- [_validateExecution(account, , , data)](#ERC7579SelectorExecutor-_validateExecution-address-bytes32-bytes32-bytes-) +#### ERC7579Executor [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Executor-isModuleType-uint256-) +- [execute(account, salt, mode, data)](#ERC7579Executor-execute-address-bytes32-bytes32-bytes-) +- [_execute(account, mode, salt, executionCalldata)](#ERC7579Executor-_execute-address-bytes32-bytes32-bytes-) +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+- [ERC7579ExecutorSelectorAuthorized(account, selector)](#ERC7579SelectorExecutor-ERC7579ExecutorSelectorAuthorized-address-bytes4-) +- [ERC7579ExecutorSelectorRemoved(account, selector)](#ERC7579SelectorExecutor-ERC7579ExecutorSelectorRemoved-address-bytes4-) +#### ERC7579Executor [!toc] +- [ERC7579ExecutorOperationExecuted(account, salt, mode, executionCalldata)](#ERC7579Executor-ERC7579ExecutorOperationExecuted-address-bytes32-bytes32-bytes-) +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+- [ERC7579ExecutorSelectorNotAuthorized(selector)](#ERC7579SelectorExecutor-ERC7579ExecutorSelectorNotAuthorized-bytes4-) +#### ERC7579Executor [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

isAuthorized(address account, bytes4 selector) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

selectors(address account) → bytes4[]

+
+

public

+# +
+
+
+ +Returns the set of authorized selectors for the specified account. + + +This operation copies the entire selectors set to memory, which +can be expensive or may result in unbounded computation. + + +
+
+ + + +
+
+

onInstall(bytes initData)

+
+

public

+# +
+
+
+ +Sets up the module's initial configuration when installed by an account. +The initData should be encoded as: `abi.encode(bytes4[] selectors)` + +
+
+ + + +
+
+

onUninstall(bytes)

+
+

public

+# +
+
+
+ +Cleans up module's configuration when uninstalled from an account. +Clears all selectors. + + +This function has unbounded gas costs and may become uncallable if the set grows too large. +See [`EnumerableSetExtended.clear`](./utils#EnumerableSetExtended-clear-struct-EnumerableSetExtended-Bytes32x2Set-). + + +
+
+ + + +
+
+

addSelectors(bytes4[] newSelectors)

+
+

public

+# +
+
+
+ +Adds `selectors` to the set for the calling account + +
+
+ + + +
+
+

removeSelectors(bytes4[] oldSelectors)

+
+

public

+# +
+
+
+ +Removes a selector from the set for the calling account + +
+
+ + + +
+
+

_addSelectors(address account, bytes4[] newSelectors)

+
+

internal

+# +
+
+
+ +Internal version of [`ERC7579SelectorExecutor.addSelectors`](#ERC7579SelectorExecutor-addSelectors-bytes4---) that takes an `account` as argument + +
+
+ + + +
+
+

_removeSelectors(address account, bytes4[] oldSelectors)

+
+

internal

+# +
+
+
+ +Internal version of [`ERC7579SelectorExecutor.removeSelectors`](#ERC7579SelectorExecutor-removeSelectors-bytes4---) that takes an `account` as argument + +
+
+ + + +
+
+

_validateExecution(address account, bytes32, bytes32, bytes data) → bytes

+
+

internal

+# +
+
+
+ +See [`ERC7579Executor._validateExecution`](#ERC7579Executor-_validateExecution-address-bytes32-bytes32-bytes-). +Validates that the selector (first 4 bytes of the actual callData) is authorized before execution. + +
+
+ + + +
+
+

ERC7579ExecutorSelectorAuthorized(address indexed account, bytes4 selector)

+
+

event

+# +
+
+ +
+ +Emitted when a selector is added to the set + +
+
+ + +
+
+

ERC7579ExecutorSelectorRemoved(address indexed account, bytes4 selector)

+
+

event

+# +
+
+ +
+ +Emitted when a selector is removed from the set + +
+
+ + + +
+
+

ERC7579ExecutorSelectorNotAuthorized(bytes4 selector)

+
+

error

+# +
+
+
+ +Error thrown when attempting to execute a non-authorized selector + +
+
+ + + +
+ +## `ERC7579Signature` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579Signature.sol"; +``` + +Implementation of [`ERC7579Validator`](#ERC7579Validator) module using ERC-7913 signature verification. + +This validator allows ERC-7579 accounts to integrate with address-less cryptographic keys +and account signatures through the ERC-7913 signature verification system. Each account +can store its own ERC-7913 formatted signer (a concatenation of a verifier address and a +key: `verifier || key`). + +This enables accounts to use signature schemes without requiring each key to have its own +Ethereum address.A smart account with this module installed can keep an emergency key as a +backup. + +
+

Functions

+
+- [signer(account)](#ERC7579Signature-signer-address-) +- [onInstall(data)](#ERC7579Signature-onInstall-bytes-) +- [onUninstall()](#ERC7579Signature-onUninstall-bytes-) +- [setSigner(signer_)](#ERC7579Signature-setSigner-bytes-) +- [_setSigner(account, signer_)](#ERC7579Signature-_setSigner-address-bytes-) +- [_rawERC7579Validation(account, hash, signature)](#ERC7579Signature-_rawERC7579Validation-address-bytes32-bytes-) +#### ERC7579Validator [!toc] +- [isModuleType(moduleTypeId)](#ERC7579Validator-isModuleType-uint256-) +- [validateUserOp(userOp, userOpHash)](#ERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(, hash, signature)](#ERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Events

+
+- [ERC7579SignatureSignerSet(account, signer)](#ERC7579Signature-ERC7579SignatureSignerSet-address-bytes-) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ +
+

Errors

+
+- [ERC7579SignatureInvalidSignerLength()](#ERC7579Signature-ERC7579SignatureInvalidSignerLength--) +#### ERC7579Validator [!toc] +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +
+
+ + + +
+
+

signer(address account) → bytes

+
+

public

+# +
+
+
+ +Return the ERC-7913 signer (i.e. `verifier || key`). + +
+
+ + + +
+
+

onInstall(bytes data)

+
+

public

+# +
+
+
+ +See `IERC7579Module-onInstall`. + + +An account can only call onInstall once. If called directly by the account, +the signer will be set to the provided data. Future installations will behave as a no-op. + + +
+
+ + + +
+
+

onUninstall(bytes)

+
+

public

+# +
+
+
+ +See `IERC7579Module-onUninstall`. + + +The signer's key will be removed if the account calls this function, potentially +making the account unusable. As an account operator, make sure to uninstall to a predefined path +in your account that properly handles side effects of uninstallation. See `AccountERC7579-uninstallModule`. + + +
+
+ + + +
+
+

setSigner(bytes signer_)

+
+

public

+# +
+
+
+ +Sets the ERC-7913 signer (i.e. `verifier || key`) for the calling account. + +
+
+ + + +
+
+

_setSigner(address account, bytes signer_)

+
+

internal

+# +
+
+
+ +Internal version of [`ERC7579Signature.setSigner`](#ERC7579Signature-setSigner-bytes-) that takes an `account` as argument without validating `signer_`. + +
+
+ + + +
+
+

_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +See [`ERC7579Validator._rawERC7579Validation`](#ERC7579Validator-_rawERC7579Validation-address-bytes32-bytes-). + +Validates a `signature` using ERC-7913 verification. + +This base implementation ignores the `sender` parameter and validates using +the account's stored signer. Derived contracts can override this to implement +custom validation logic based on the sender. + +
+
+ + + +
+
+

ERC7579SignatureSignerSet(address indexed account, bytes signer)

+
+

event

+# +
+
+ +
+ +Emitted when the signer is set. + +
+
+ + + +
+
+

ERC7579SignatureInvalidSignerLength()

+
+

error

+# +
+
+
+ +Thrown when the signer length is less than 20 bytes. + +
+
+ + + +
+ +## `ERC7579Validator` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol"; +``` + +Abstract validator module for ERC-7579 accounts. + +This contract provides the base implementation for signature validation in ERC-7579 accounts. +Developers must implement the onInstall, onUninstall, and [`ERC7579Multisig._rawERC7579Validation`](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-) +functions in derived contracts to define the specific signature validation logic. + +Example usage: + +```solidity +contract MyValidatorModule is ERC7579Validator { + function onInstall(bytes calldata data) public { + // Install logic here + } + + function onUninstall(bytes calldata data) public { + // Uninstall logic here + } + + function _rawERC7579Validation( + address account, + bytes32 hash, + bytes calldata signature + ) internal view override returns (bool) { + // Signature validation logic here + } +} +``` + +Developers can restrict other operations by using the internal [`ERC7579Multisig._rawERC7579Validation`](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-). +Example usage: + +```solidity +function execute( + address account, + Mode mode, + bytes calldata executionCalldata, + bytes32 salt, + bytes calldata signature +) public virtual { + require(_rawERC7579Validation(account, hash, signature)); + // ... rest of execute logic +} +``` + +
+

Functions

+
+- [isModuleType(moduleTypeId)](#ERC7579Validator-isModuleType-uint256-) +- [validateUserOp(userOp, userOpHash)](#ERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(, hash, signature)](#ERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +- [_rawERC7579Validation(account, hash, signature)](#ERC7579Validator-_rawERC7579Validation-address-bytes32-bytes-) +#### IERC7579Validator [!toc] +#### IERC7579Module [!toc] +- [onInstall(data)](#IERC7579Module-onInstall-bytes-) +- [onUninstall(data)](#IERC7579Module-onUninstall-bytes-) +
+
+ + + +
+
+

isModuleType(uint256 moduleTypeId) → bool

+
+

public

+# +
+
+
+ +Returns boolean value if module is a certain type + +
+
+ + + +
+
+

validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256

+
+

public

+# +
+
+
+ +Validates a UserOperation + +
+
+ + + +
+
+

isValidSignatureWithSender(address, bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +See `IERC7579Validator-isValidSignatureWithSender`. + +Ignores the `sender` parameter and validates using [`ERC7579Multisig._rawERC7579Validation`](#ERC7579Multisig-_rawERC7579Validation-address-bytes32-bytes-). +Consider overriding this function to implement custom validation logic +based on the original sender. + +
+
+ + + +
+
+

_rawERC7579Validation(address account, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Validation algorithm. + + +Validation is a critical security function. Implementations must carefully +handle cryptographic verification to prevent unauthorized access. + + +
+
+ + + +
+ +## `PaymasterCore` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/paymaster/PaymasterCore.sol"; +``` + +A simple ERC4337 paymaster implementation. This base implementation only includes the minimal logic to validate +and pay for user operations. + +Developers must implement the [`PaymasterCore._validatePaymasterUserOp`](#PaymasterCore-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) function to define the paymaster's validation +and payment logic. The `context` parameter is used to pass data between the validation and execution phases. + +The paymaster includes support to call the `IEntryPointStake` interface to manage the paymaster's deposits and stakes +through the internal functions [`PaymasterCore.deposit`](#PaymasterCore-deposit--), [`PaymasterCore.withdraw`](#PaymasterCore-withdraw-address-payable-uint256-), [`PaymasterCore.addStake`](#PaymasterCore-addStake-uint32-), [`PaymasterCore.unlockStake`](#PaymasterCore-unlockStake--) and [`PaymasterCore.withdrawStake`](#PaymasterCore-withdrawStake-address-payable-). + +* Deposits are used to pay for user operations. +* Stakes are used to guarantee the paymaster's reputation and obtain more flexibility in accessing storage. + + +See [Paymaster's unstaked reputation rules](https://eips.ethereum.org/EIPS/eip-7562#unstaked-paymasters-reputation-rules) + for more details on the paymaster's storage access limitations. + + +
+

Modifiers

+
+- [onlyEntryPoint()](#PaymasterCore-onlyEntryPoint--) +- [onlyWithdrawer()](#PaymasterCore-onlyWithdrawer--) +
+
+ +
+

Functions

+
+- [entryPoint()](#PaymasterCore-entryPoint--) +- [validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterCore-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [postOp(mode, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterCore-postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [_validatePaymasterUserOp(userOp, userOpHash, requiredPreFund)](#PaymasterCore-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_postOp(, , , )](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [deposit()](#PaymasterCore-deposit--) +- [withdraw(to, value)](#PaymasterCore-withdraw-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#PaymasterCore-addStake-uint32-) +- [unlockStake()](#PaymasterCore-unlockStake--) +- [withdrawStake(to)](#PaymasterCore-withdrawStake-address-payable-) +- [_checkEntryPoint()](#PaymasterCore-_checkEntryPoint--) +- [_authorizeWithdraw()](#PaymasterCore-_authorizeWithdraw--) +#### IPaymaster [!toc] +
+
+ +
+

Errors

+
+- [PaymasterUnauthorized(sender)](#PaymasterCore-PaymasterUnauthorized-address-) +#### IPaymaster [!toc] +
+
+ + + +
+
+

onlyEntryPoint()

+
+

internal

+# +
+
+ +
+ +Revert if the caller is not the entry point. + +
+
+ + + +
+
+

onlyWithdrawer()

+
+

internal

+# +
+
+ +
+ +
+
+ + + +
+
+

entryPoint() → contract IEntryPoint

+
+

public

+# +
+
+
+ +Canonical entry point for the account that forwards and validates user operations. + +
+
+ + + +
+
+

validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) → bytes context, uint256 validationData

+
+

public

+# +
+
+
+ +Validates whether the paymaster is willing to pay for the user operation. See +`IAccount-validateUserOp` for additional information on the return value. + + +Bundlers will reject this method if it modifies the state, unless it's whitelisted. + + +
+
+ + + +
+
+

postOp(enum IPaymaster.PostOpMode mode, bytes context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)

+
+

public

+# +
+
+
+ +Verifies the sender is the entrypoint. + +
+
+ + + +
+
+

_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 requiredPreFund) → bytes context, uint256 validationData

+
+

internal

+# +
+
+
+ +Internal validation of whether the paymaster is willing to pay for the user operation. +Returns the context to be passed to postOp and the validation data. + +The `requiredPreFund` is the amount the paymaster has to pay (in native tokens). It's calculated +as `requiredGas * userOp.maxFeePerGas`, where `required` gas can be calculated from the user operation +as `verificationGasLimit + callGasLimit + paymasterVerificationGasLimit + paymasterPostOpGasLimit + preVerificationGas` + +
+
+ + + +
+
+

_postOp(enum IPaymaster.PostOpMode, bytes, uint256, uint256)

+
+

internal

+# +
+
+
+ +Handles post user operation execution logic. The caller must be the entry point. + +It receives the `context` returned by `_validatePaymasterUserOp`. Function is not called if no context +is returned by [`PaymasterCore.validatePaymasterUserOp`](#PaymasterCore-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-). + + +The `actualUserOpFeePerGas` is not `tx.gasprice`. A user operation can be bundled with other transactions +making the gas price of the user operation to differ. + + +
+
+ + + +
+
+

deposit()

+
+

public

+# +
+
+
+ +Calls `IEntryPointStake-depositTo`. + +
+
+ + + +
+
+

withdraw(address payable to, uint256 value)

+
+

public

+# +
+
+
+ +Calls `IEntryPointStake-withdrawTo`. + +
+
+ + + +
+
+

addStake(uint32 unstakeDelaySec)

+
+

public

+# +
+
+
+ +Calls `IEntryPointStake-addStake`. + +
+
+ + + +
+
+

unlockStake()

+
+

public

+# +
+
+
+ +Calls `IEntryPointStake-unlockStake`. + +
+
+ + + +
+
+

withdrawStake(address payable to)

+
+

public

+# +
+
+
+ +Calls `IEntryPointStake-withdrawStake`. + +
+
+ + + +
+
+

_checkEntryPoint()

+
+

internal

+# +
+
+
+ +Ensures the caller is the `entrypoint`. + +
+
+ + + +
+
+

_authorizeWithdraw()

+
+

internal

+# +
+
+
+ +Checks whether `msg.sender` withdraw funds stake or deposit from the entrypoint on paymaster's behalf. + +Use of an [access control](https://docs.openzeppelin.com/contracts/5.x/access-control) +modifier such as `Ownable-onlyOwner` is recommended. + +```solidity +function _authorizeUpgrade() internal onlyOwner {} +``` + +
+
+ + + +
+
+

PaymasterUnauthorized(address sender)

+
+

error

+# +
+
+
+ +Unauthorized call to the paymaster. + +
+
+ + + +
+ +## `PaymasterERC20` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/paymaster/PaymasterERC20.sol"; +``` + +Extension of [`PaymasterCore`](#PaymasterCore) that enables users to pay gas with ERC-20 tokens. + +To enable this feature, developers must implement the [`PaymasterERC20._fetchDetails`](#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-) function: + +```solidity +function _fetchDetails( + PackedUserOperation calldata userOp, + bytes32 userOpHash +) internal view override returns (uint256 validationData, IERC20 token, uint256 tokenPrice) { + // Implement logic to fetch the token, and token price from the userOp +} +``` + +The 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 + +
+

Functions

+
+- [_validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterERC20-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_prefund(userOp, , token, tokenPrice, prefunder_, maxCost)](#PaymasterERC20-_prefund-struct-PackedUserOperation-bytes32-contract-IERC20-uint256-address-uint256-) +- [_postOp(, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterERC20-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [_refund(token, tokenPrice, actualGasCost, actualUserOpFeePerGas, prefunder, prefundAmount, )](#PaymasterERC20-_refund-contract-IERC20-uint256-uint256-uint256-address-uint256-bytes-) +- [_fetchDetails(userOp, userOpHash)](#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-) +- [_postOpCost()](#PaymasterERC20-_postOpCost--) +- [_tokenPriceDenominator()](#PaymasterERC20-_tokenPriceDenominator--) +- [_erc20Cost(cost, feePerGas, tokenPrice)](#PaymasterERC20-_erc20Cost-uint256-uint256-uint256-) +- [withdrawTokens(token, recipient, amount)](#PaymasterERC20-withdrawTokens-contract-IERC20-address-uint256-) +#### PaymasterCore [!toc] +- [entryPoint()](#PaymasterCore-entryPoint--) +- [validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterCore-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [postOp(mode, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterCore-postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [deposit()](#PaymasterCore-deposit--) +- [withdraw(to, value)](#PaymasterCore-withdraw-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#PaymasterCore-addStake-uint32-) +- [unlockStake()](#PaymasterCore-unlockStake--) +- [withdrawStake(to)](#PaymasterCore-withdrawStake-address-payable-) +- [_checkEntryPoint()](#PaymasterCore-_checkEntryPoint--) +- [_authorizeWithdraw()](#PaymasterCore-_authorizeWithdraw--) +#### IPaymaster [!toc] +
+
+ +
+

Events

+
+- [UserOperationSponsored(userOpHash, token, tokenAmount, tokenPrice)](#PaymasterERC20-UserOperationSponsored-bytes32-address-uint256-uint256-) +#### PaymasterCore [!toc] +#### IPaymaster [!toc] +
+
+ +
+

Errors

+
+- [PaymasterERC20FailedRefund(token, prefundAmount, actualAmount, prefundContext)](#PaymasterERC20-PaymasterERC20FailedRefund-contract-IERC20-uint256-uint256-bytes-) +#### PaymasterCore [!toc] +- [PaymasterUnauthorized(sender)](#PaymasterCore-PaymasterUnauthorized-address-) +#### IPaymaster [!toc] +
+
+ + + +
+
+

_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) → bytes context, uint256 validationData

+
+

internal

+# +
+
+
+ +See [`PaymasterCore._validatePaymasterUserOp`](#PaymasterCore-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-). + +Attempts to retrieve the `token` and `tokenPrice` from the user operation (see [`PaymasterERC20._fetchDetails`](#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-)) +and prefund the user operation using these values and the `maxCost` argument (see [`PaymasterERC20._prefund`](#PaymasterERC20-_prefund-struct-PackedUserOperation-bytes32-contract-IERC20-uint256-address-uint256-)). + +Returns `abi.encodePacked(userOpHash, token, tokenPrice, prefundAmount, prefunder, prefundContext)` in +`context` if the prefund is successful. Otherwise, it returns empty bytes. + +
+
+ + + +
+
+

_prefund(struct PackedUserOperation userOp, bytes32, contract IERC20 token, uint256 tokenPrice, address prefunder_, uint256 maxCost) → bool prefunded, uint256 prefundAmount, address prefunder, bytes prefundContext

+
+

internal

+# +
+
+
+ +Prefunds the `userOp` by charging the maximum possible gas cost (`maxCost`) in ERC-20 `token`. + +The `token` and `tokenPrice` is obtained from the [`PaymasterERC20._fetchDetails`](#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-) function and are funded by the `prefunder_`, +which is the user operation sender by default. The `prefundAmount` is calculated using [`PaymasterERC20._erc20Cost`](#PaymasterERC20-_erc20Cost-uint256-uint256-uint256-). + +Returns a `prefundContext` that's passed to the [`PaymasterCore._postOp`](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) function through its `context` return value. + + +Consider not reverting if the prefund fails when overriding this function. This is to avoid reverting +during the validation phase of the user operation, which may penalize the paymaster's reputation according +to ERC-7562 validation rules. + + +
+
+ + + +
+
+

_postOp(enum IPaymaster.PostOpMode, bytes context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)

+
+

internal

+# +
+
+
+ +Attempts to refund the user operation after execution. See [`PaymasterERC20._refund`](#PaymasterERC20-_refund-contract-IERC20-uint256-uint256-uint256-address-uint256-bytes-). + +Reverts with [`PaymasterERC20.PaymasterERC20FailedRefund`](#PaymasterERC20-PaymasterERC20FailedRefund-contract-IERC20-uint256-uint256-bytes-) if the refund fails. + + +This function may revert after the user operation has been executed without +reverting the user operation itself. Consider implementing a mechanism to handle +this case gracefully. + + +
+
+ + + +
+
+

_refund(contract IERC20 token, uint256 tokenPrice, uint256 actualGasCost, uint256 actualUserOpFeePerGas, address prefunder, uint256 prefundAmount, bytes) → bool refunded, uint256 actualAmount

+
+

internal

+# +
+
+
+ +Refunds any unused gas back to the user (i.e. `prefundAmount - actualAmount`) in `token`. + +The `actualAmount` is calculated using [`PaymasterERC20._erc20Cost`](#PaymasterERC20-_erc20Cost-uint256-uint256-uint256-) and the `actualGasCost`, `actualUserOpFeePerGas`, `prefundContext` +and the `tokenPrice` from the [`PaymasterCore._postOp`](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-)'s context. + +
+
+ + + +
+
+

_fetchDetails(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256 validationData, contract IERC20 token, uint256 tokenPrice

+
+

internal

+# +
+
+
+ +Retrieves payment details for a user operation. + +The values returned by this internal function are: + +* `validationData`: ERC-4337 validation data, indicating success/failure and optional time validity (`validAfter`, `validUntil`). +* `token`: Address of the ERC-20 token used for payment to the paymaster. +* `tokenPrice`: Price of the token in native currency, scaled by `_tokenPriceDenominator()`. + +==== Calculating the token price + +Given gas fees are paid in native currency, developers can use the `ERC20 price unit / native price unit` ratio to +calculate the price of an ERC20 token price in native currency. However, the token may have a different number of decimals +than the native currency. For a a generalized formula considering prices in USD and decimals, consider using: + +`( / 10**) / ( / 1e18) * _tokenPriceDenominator()` + +For example, suppose token is USDC ($1 with 6 decimals) and native currency is ETH (assuming $2524.86 with 18 decimals), +then each unit (1e-6) of USDC is worth `(1 / 1e6) / ((252486 / 1e2) / 1e18) = 396061563.8094785` wei. The `_tokenPriceDenominator()` +ensures precision by avoiding fractional value loss. (i.e. the 0.8094785 part). + +
+
+ + + +
+
+

_postOpCost() → uint256

+
+

internal

+# +
+
+
+ +Over-estimates the cost of the post-operation logic. + +
+
+ + + +
+
+

_tokenPriceDenominator() → uint256

+
+

internal

+# +
+
+
+ +Denominator used for interpreting the `tokenPrice` returned by [`PaymasterERC20._fetchDetails`](#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-) as "fixed point" in [`PaymasterERC20._erc20Cost`](#PaymasterERC20-_erc20Cost-uint256-uint256-uint256-). + +
+
+ + + +
+
+

_erc20Cost(uint256 cost, uint256 feePerGas, uint256 tokenPrice) → uint256

+
+

internal

+# +
+
+
+ +Calculates the cost of the user operation in ERC-20 tokens. + +
+
+ + + +
+
+

withdrawTokens(contract IERC20 token, address recipient, uint256 amount)

+
+

public

+# +
+
+
+ +Public function that allows the withdrawer to extract ERC-20 tokens resulting from gas payments. + +
+
+ + + +
+
+

UserOperationSponsored(bytes32 indexed userOpHash, address indexed token, uint256 tokenAmount, uint256 tokenPrice)

+
+

event

+# +
+
+ +
+ +Emitted when a user operation identified by `userOpHash` is sponsored by this paymaster +using the specified ERC-20 `token`. The `tokenAmount` is the amount charged for the operation, +and `tokenPrice` is the price of the token in native currency (e.g., ETH). + +
+
+ + + +
+
+

PaymasterERC20FailedRefund(contract IERC20 token, uint256 prefundAmount, uint256 actualAmount, bytes prefundContext)

+
+

error

+# +
+
+
+ +Throws when the paymaster fails to refund the difference between the `prefundAmount` +and the `actualAmount` of `token`. + +
+
+ + + +
+ +## `PaymasterERC20Guarantor` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/paymaster/PaymasterERC20Guarantor.sol"; +``` + +Extension of [`PaymasterERC20`](#PaymasterERC20) that enables third parties to guarantee user operations. + +This contract allows a guarantor to pre-fund user operations on behalf of users. The guarantor +pays 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 + +A common use case is for guarantors to pay for the operations of users claiming airdrops. In this scenario: + +* The guarantor pays the 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 + +The guarantor is identified through the [`PaymasterERC20Guarantor._fetchGuarantor`](#PaymasterERC20Guarantor-_fetchGuarantor-struct-PackedUserOperation-) function, which must be implemented +by developers to determine who can guarantee operations. This allows for flexible guarantor selection +logic based on the specific requirements of the application. + +
+

Functions

+
+- [_prefund(userOp, userOpHash, token, tokenPrice, prefunder_, maxCost)](#PaymasterERC20Guarantor-_prefund-struct-PackedUserOperation-bytes32-contract-IERC20-uint256-address-uint256-) +- [_refund(token, tokenPrice, actualGasCost, actualUserOpFeePerGas, prefunder, prefundAmount, prefundContext)](#PaymasterERC20Guarantor-_refund-contract-IERC20-uint256-uint256-uint256-address-uint256-bytes-) +- [_fetchGuarantor(userOp)](#PaymasterERC20Guarantor-_fetchGuarantor-struct-PackedUserOperation-) +- [_guaranteedPostOpCost()](#PaymasterERC20Guarantor-_guaranteedPostOpCost--) +#### PaymasterERC20 [!toc] +- [_validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterERC20-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_postOp(, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterERC20-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [_fetchDetails(userOp, userOpHash)](#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-) +- [_postOpCost()](#PaymasterERC20-_postOpCost--) +- [_tokenPriceDenominator()](#PaymasterERC20-_tokenPriceDenominator--) +- [_erc20Cost(cost, feePerGas, tokenPrice)](#PaymasterERC20-_erc20Cost-uint256-uint256-uint256-) +- [withdrawTokens(token, recipient, amount)](#PaymasterERC20-withdrawTokens-contract-IERC20-address-uint256-) +#### PaymasterCore [!toc] +- [entryPoint()](#PaymasterCore-entryPoint--) +- [validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterCore-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [postOp(mode, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterCore-postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [deposit()](#PaymasterCore-deposit--) +- [withdraw(to, value)](#PaymasterCore-withdraw-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#PaymasterCore-addStake-uint32-) +- [unlockStake()](#PaymasterCore-unlockStake--) +- [withdrawStake(to)](#PaymasterCore-withdrawStake-address-payable-) +- [_checkEntryPoint()](#PaymasterCore-_checkEntryPoint--) +- [_authorizeWithdraw()](#PaymasterCore-_authorizeWithdraw--) +#### IPaymaster [!toc] +
+
+ +
+

Events

+
+- [UserOperationGuaranteed(userOpHash, guarantor, prefundAmount)](#PaymasterERC20Guarantor-UserOperationGuaranteed-bytes32-address-uint256-) +#### PaymasterERC20 [!toc] +- [UserOperationSponsored(userOpHash, token, tokenAmount, tokenPrice)](#PaymasterERC20-UserOperationSponsored-bytes32-address-uint256-uint256-) +#### PaymasterCore [!toc] +#### IPaymaster [!toc] +
+
+ +
+

Errors

+
+#### PaymasterERC20 [!toc] +- [PaymasterERC20FailedRefund(token, prefundAmount, actualAmount, prefundContext)](#PaymasterERC20-PaymasterERC20FailedRefund-contract-IERC20-uint256-uint256-bytes-) +#### PaymasterCore [!toc] +- [PaymasterUnauthorized(sender)](#PaymasterCore-PaymasterUnauthorized-address-) +#### IPaymaster [!toc] +
+
+ + + +
+
+

_prefund(struct PackedUserOperation userOp, bytes32 userOpHash, contract IERC20 token, uint256 tokenPrice, address prefunder_, uint256 maxCost) → bool prefunded, uint256 prefundAmount, address prefunder, bytes prefundContext

+
+

internal

+# +
+
+
+ +Prefunds the user operation using either the guarantor or the default prefunder. +See [`PaymasterERC20._prefund`](#PaymasterERC20-_prefund-struct-PackedUserOperation-bytes32-contract-IERC20-uint256-address-uint256-). + +Returns `abi.encodePacked(..., userOp.sender)` in `prefundContext` to allow +the refund process to identify the user operation sender. + +
+
+ + + +
+
+

_refund(contract IERC20 token, uint256 tokenPrice, uint256 actualGasCost, uint256 actualUserOpFeePerGas, address prefunder, uint256 prefundAmount, bytes prefundContext) → bool refunded, uint256 actualAmount

+
+

internal

+# +
+
+
+ +Handles the refund process for guaranteed operations. + +If the operation was guaranteed, it attempts to get repayment from the user first and then refunds the guarantor. +Otherwise, fallback to [`PaymasterERC20._refund`](#PaymasterERC20-_refund-contract-IERC20-uint256-uint256-uint256-address-uint256-bytes-). + + +For guaranteed user operations where the user paid the `actualGasCost` back, this function +doesn't call `super._refund`. Consider whether there are side effects in the parent contract that need to be executed. + + +
+
+ + + +
+
+

_fetchGuarantor(struct PackedUserOperation userOp) → address guarantor

+
+

internal

+# +
+
+
+ +Fetches the guarantor address and validation data from the user operation. + + +Return `address(0)` to disable the guarantor feature. If supported, ensure +explicit consent (e.g., signature verification) to prevent unauthorized use. + + +
+
+ + + +
+
+

_guaranteedPostOpCost() → uint256

+
+

internal

+# +
+
+
+ +Over-estimates the cost of the post-operation logic. Added on top of guaranteed userOps post-operation cost. + +
+
+ + + +
+
+

UserOperationGuaranteed(bytes32 indexed userOpHash, address indexed guarantor, uint256 prefundAmount)

+
+

event

+# +
+
+ +
+ +Emitted when a user operation identified by `userOpHash` is guaranteed by a `guarantor` for `prefundAmount`. + +
+
+ + + +
+ +## `PaymasterERC721Owner` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/paymaster/PaymasterERC721Owner.sol"; +``` + +Extension of [`PaymasterCore`](#PaymasterCore) that supports account based on ownership of an ERC-721 token. + +This paymaster will sponsor user operations if the user has at least 1 token of the token specified +during construction (or via [`PaymasterERC721Owner._setToken`](#PaymasterERC721Owner-_setToken-contract-IERC721-)). + +
+

Functions

+
+- [constructor(token_)](#PaymasterERC721Owner-constructor-contract-IERC721-) +- [token()](#PaymasterERC721Owner-token--) +- [_setToken(token_)](#PaymasterERC721Owner-_setToken-contract-IERC721-) +- [_validatePaymasterUserOp(userOp, , )](#PaymasterERC721Owner-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +#### PaymasterCore [!toc] +- [entryPoint()](#PaymasterCore-entryPoint--) +- [validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterCore-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [postOp(mode, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterCore-postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [_postOp(, , , )](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [deposit()](#PaymasterCore-deposit--) +- [withdraw(to, value)](#PaymasterCore-withdraw-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#PaymasterCore-addStake-uint32-) +- [unlockStake()](#PaymasterCore-unlockStake--) +- [withdrawStake(to)](#PaymasterCore-withdrawStake-address-payable-) +- [_checkEntryPoint()](#PaymasterCore-_checkEntryPoint--) +- [_authorizeWithdraw()](#PaymasterCore-_authorizeWithdraw--) +#### IPaymaster [!toc] +
+
+ +
+

Events

+
+- [PaymasterERC721OwnerTokenSet(token)](#PaymasterERC721Owner-PaymasterERC721OwnerTokenSet-contract-IERC721-) +#### PaymasterCore [!toc] +#### IPaymaster [!toc] +
+
+ +
+

Errors

+
+#### PaymasterCore [!toc] +- [PaymasterUnauthorized(sender)](#PaymasterCore-PaymasterUnauthorized-address-) +#### IPaymaster [!toc] +
+
+ + + +
+
+

constructor(contract IERC721 token_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

token() → contract IERC721

+
+

public

+# +
+
+
+ +ERC-721 token used to validate the user operation. + +
+
+ + + +
+
+

_setToken(contract IERC721 token_)

+
+

internal

+# +
+
+
+ +Sets the ERC-721 token used to validate the user operation. + +
+
+ + + +
+
+

_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32, uint256) → bytes context, uint256 validationData

+
+

internal

+# +
+
+
+ +Internal validation of whether the paymaster is willing to pay for the user operation. +Returns the context to be passed to postOp and the validation data. + + +The default `context` is `bytes(0)`. Developers that add a context when overriding this function MUST +also override [`PaymasterCore._postOp`](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) to process the context passed along. + + +
+
+ + + +
+
+

PaymasterERC721OwnerTokenSet(contract IERC721 token)

+
+

event

+# +
+
+ +
+ +Emitted when the paymaster token is set. + +
+
+ + + +
+ +## `PaymasterSigner` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/account/paymaster/PaymasterSigner.sol"; +``` + +Extension of [`PaymasterCore`](#PaymasterCore) that adds signature validation. See `SignerECDSA`, `SignerP256` or `SignerRSA`. + +Example of usage: + +```solidity +contract MyPaymasterECDSASigner is PaymasterSigner, SignerECDSA { + constructor(address signerAddr) EIP712("MyPaymasterECDSASigner", "1") SignerECDSA(signerAddr) {} +} +``` + +
+

Functions

+
+- [_signableUserOpHash(userOp, validAfter, validUntil)](#PaymasterSigner-_signableUserOpHash-struct-PackedUserOperation-uint48-uint48-) +- [_validatePaymasterUserOp(userOp, , )](#PaymasterSigner-_validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_decodePaymasterUserOp(userOp)](#PaymasterSigner-_decodePaymasterUserOp-struct-PackedUserOperation-) +#### PaymasterCore [!toc] +- [entryPoint()](#PaymasterCore-entryPoint--) +- [validatePaymasterUserOp(userOp, userOpHash, maxCost)](#PaymasterCore-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [postOp(mode, context, actualGasCost, actualUserOpFeePerGas)](#PaymasterCore-postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [_postOp(, , , )](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +- [deposit()](#PaymasterCore-deposit--) +- [withdraw(to, value)](#PaymasterCore-withdraw-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#PaymasterCore-addStake-uint32-) +- [unlockStake()](#PaymasterCore-unlockStake--) +- [withdrawStake(to)](#PaymasterCore-withdrawStake-address-payable-) +- [_checkEntryPoint()](#PaymasterCore-_checkEntryPoint--) +- [_authorizeWithdraw()](#PaymasterCore-_authorizeWithdraw--) +#### IPaymaster [!toc] +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### AbstractSigner [!toc] +- [_rawSignatureValidation(hash, signature)](#AbstractSigner-_rawSignatureValidation-bytes32-bytes-) +
+
+ +
+

Events

+
+#### PaymasterCore [!toc] +#### IPaymaster [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+#### PaymasterCore [!toc] +- [PaymasterUnauthorized(sender)](#PaymasterCore-PaymasterUnauthorized-address-) +#### IPaymaster [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### AbstractSigner [!toc] +
+
+ + + +
+
+

_signableUserOpHash(struct PackedUserOperation userOp, uint48 validAfter, uint48 validUntil) → bytes32

+
+

internal

+# +
+
+
+ +Virtual function that returns the signable hash for a user operations. Given the `userOpHash` +contains the `paymasterAndData` itself, it's not possible to sign that value directly. Instead, +this function must be used to provide a custom mechanism to authorize an user operation. + +
+
+ + + +
+
+

_validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32, uint256) → bytes context, uint256 validationData

+
+

internal

+# +
+
+
+ +Internal validation of whether the paymaster is willing to pay for the user operation. +Returns the context to be passed to postOp and the validation data. + + +The `context` returned is `bytes(0)`. Developers overriding this function MUST +override [`PaymasterCore._postOp`](#PaymasterCore-_postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) to process the context passed along. + + +
+
+ + + +
+
+

_decodePaymasterUserOp(struct PackedUserOperation userOp) → uint48 validAfter, uint48 validUntil, bytes signature

+
+

internal

+# +
+
+
+ +Decodes the user operation's data from `paymasterAndData`. + +
+
diff --git a/docs/content/community-contracts/api/crosschain.mdx b/docs/content/community-contracts/api/crosschain.mdx new file mode 100644 index 00000000..99392b04 --- /dev/null +++ b/docs/content/community-contracts/api/crosschain.mdx @@ -0,0 +1,2013 @@ +--- +title: "Crosschain" +description: "Smart contract crosschain utilities and implementations" +--- + +Gateways are contracts that enable cross-chain communication. These can either be a message source or a destination according to ERC-7786. + +* [`ERC7786Receiver`](#ERC7786Receiver): ERC-7786 cross-chain message receiver. +* [`ERC7786OpenBridge`](#ERC7786OpenBridge): ERC-7786 "N out of M" gateway. Sends a message through M gateways and executes on the destination if N received it. + +Developers can access interoperability protocols through gateway adapters. The library includes the following gateway adapters: + +* [`AxelarGatewayAdapter`](#AxelarGatewayAdapter): ERC-7786 gateway adapter for Axelar. + +## Gateways + +[`ERC7786OpenBridge`](#ERC7786OpenBridge) + +## Clients + +[`ERC7786Receiver`](#ERC7786Receiver) + +## Adapters + +### Axelar + +[`AxelarGatewayAdapter`](#AxelarGatewayAdapter) + + + +
+ +## `ERC7786OpenBridge` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/crosschain/ERC7786OpenBridge.sol"; +``` + +N of M gateway: Sends your message through M independent gateways. It will be delivered to the receiver by an +equivalent bridge on the destination chain if N of the M gateways agree. + +
+

Functions

+
+- [constructor(owner_, gateways_, threshold_)](#ERC7786OpenBridge-constructor-address-address---uint8-) +- [supportsAttribute()](#ERC7786OpenBridge-supportsAttribute-bytes4-) +- [sendMessage(recipient, payload, attributes)](#ERC7786OpenBridge-sendMessage-bytes-bytes-bytes---) +- [receiveMessage(, sender, payload)](#ERC7786OpenBridge-receiveMessage-bytes32-bytes-bytes-) +- [getGateways()](#ERC7786OpenBridge-getGateways--) +- [getThreshold()](#ERC7786OpenBridge-getThreshold--) +- [getRemoteBridge(chain)](#ERC7786OpenBridge-getRemoteBridge-bytes-) +- [getRemoteBridge(chainType, chainReference)](#ERC7786OpenBridge-getRemoteBridge-bytes2-bytes-) +- [addGateway(gateway)](#ERC7786OpenBridge-addGateway-address-) +- [removeGateway(gateway)](#ERC7786OpenBridge-removeGateway-address-) +- [setThreshold(newThreshold)](#ERC7786OpenBridge-setThreshold-uint8-) +- [registerRemoteBridge(bridge)](#ERC7786OpenBridge-registerRemoteBridge-bytes-) +- [pause()](#ERC7786OpenBridge-pause--) +- [unpause()](#ERC7786OpenBridge-unpause--) +- [sweep(to)](#ERC7786OpenBridge-sweep-address-payable-) +- [_addGateway(gateway)](#ERC7786OpenBridge-_addGateway-address-) +- [_removeGateway(gateway)](#ERC7786OpenBridge-_removeGateway-address-) +- [_setThreshold(newThreshold)](#ERC7786OpenBridge-_setThreshold-uint8-) +- [_registerRemoteBridge(bridge)](#ERC7786OpenBridge-_registerRemoteBridge-bytes-) +#### Pausable [!toc] +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +#### IERC7786Receiver [!toc] +#### IERC7786GatewaySource [!toc] +
+
+ +
+

Events

+
+- [OutboxDetails(sendId, outbox)](#ERC7786OpenBridge-OutboxDetails-bytes32-struct-ERC7786OpenBridge-Outbox---) +- [Received(receiveId, gateway)](#ERC7786OpenBridge-Received-bytes32-address-) +- [ExecutionSuccess(receiveId)](#ERC7786OpenBridge-ExecutionSuccess-bytes32-) +- [ExecutionFailed(receiveId)](#ERC7786OpenBridge-ExecutionFailed-bytes32-) +- [GatewayAdded(gateway)](#ERC7786OpenBridge-GatewayAdded-address-) +- [GatewayRemoved(gateway)](#ERC7786OpenBridge-GatewayRemoved-address-) +- [ThresholdUpdated(threshold)](#ERC7786OpenBridge-ThresholdUpdated-uint8-) +- [RemoteRegistered(remote)](#ERC7786OpenBridge-RemoteRegistered-bytes-) +#### Pausable [!toc] +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +#### IERC7786Receiver [!toc] +#### IERC7786GatewaySource [!toc] +- [MessageSent(sendId, sender, receiver, payload, value, attributes)](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) +
+
+ +
+

Errors

+
+- [UnsupportedNativeTransfer()](#ERC7786OpenBridge-UnsupportedNativeTransfer--) +- [ERC7786OpenBridgeInvalidCrosschainSender()](#ERC7786OpenBridge-ERC7786OpenBridgeInvalidCrosschainSender--) +- [ERC7786OpenBridgeAlreadyExecuted()](#ERC7786OpenBridge-ERC7786OpenBridgeAlreadyExecuted--) +- [ERC7786OpenBridgeRemoteNotRegistered(chainType, chainReference)](#ERC7786OpenBridge-ERC7786OpenBridgeRemoteNotRegistered-bytes2-bytes-) +- [ERC7786OpenBridgeGatewayAlreadyRegistered(gateway)](#ERC7786OpenBridge-ERC7786OpenBridgeGatewayAlreadyRegistered-address-) +- [ERC7786OpenBridgeGatewayNotRegistered(gateway)](#ERC7786OpenBridge-ERC7786OpenBridgeGatewayNotRegistered-address-) +- [ERC7786OpenBridgeThresholdViolation()](#ERC7786OpenBridge-ERC7786OpenBridgeThresholdViolation--) +- [ERC7786OpenBridgeInvalidExecutionReturnValue()](#ERC7786OpenBridge-ERC7786OpenBridgeInvalidExecutionReturnValue--) +- [RemoteAlreadyRegistered(remote)](#ERC7786OpenBridge-RemoteAlreadyRegistered-bytes-) +#### Pausable [!toc] +- [EnforcedPause()](#Pausable-EnforcedPause--) +- [ExpectedPause()](#Pausable-ExpectedPause--) +#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +#### IERC7786Receiver [!toc] +#### IERC7786GatewaySource [!toc] +- [UnsupportedAttribute(selector)](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) +
+
+ + + +
+
+

constructor(address owner_, address[] gateways_, uint8 threshold_)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

supportsAttribute(bytes4) → bool

+
+

public

+# +
+
+
+ +Getter to check whether an attribute is supported or not. + +
+
+ + + +
+
+

sendMessage(bytes recipient, bytes payload, bytes[] attributes) → bytes32 sendId

+
+

public

+# +
+
+
+ +Using memory instead of calldata avoids stack too deep errors + +
+
+ + + +
+
+

receiveMessage(bytes32, bytes sender, bytes payload) → bytes4

+
+

public

+# +
+
+
+ +This function serves a dual purpose: + +It will be called by ERC-7786 gateways with message coming from the the corresponding bridge on the source +chain. These "signals" are tracked until the threshold is reached. At that point the message is sent to the +destination. + +It can also be called by anyone (including an ERC-7786 gateway) to retry the execution. This can be useful if +the automatic execution (that is triggered when the threshold is reached) fails, and someone wants to retry it. + +When a message is forwarded by a known gateway, a [`ERC7786OpenBridge.Received`](#ERC7786OpenBridge-Received-bytes32-address-) event is emitted. If a known gateway calls this +function more than once (for a given message), only the first call is counts toward the threshold and emits an +[`ERC7786OpenBridge.Received`](#ERC7786OpenBridge-Received-bytes32-address-) event. + +This function revert if: + +* the message is not properly formatted or does not originate from the registered bridge on the source + chain. +* someone tries re-execute a message that was already successfully delivered. This includes gateways that call + this function a second time with a message that was already executed. +* the execution of the message (on the [`IERC7786Receiver`](./interfaces#IERC7786Receiver) receiver) is successful but fails to return the + executed value. + +This function does not revert if: + +* A known gateway delivers a message for the first time, and that message was already executed. In that case + the message is NOT re-executed, and the correct "magic value" is returned. +* The execution of the message (on the [`IERC7786Receiver`](./interfaces#IERC7786Receiver) receiver) reverts. In that case a [`ERC7786OpenBridge.ExecutionFailed`](#ERC7786OpenBridge-ExecutionFailed-bytes32-) + event is emitted. + +This function emits: + +* [`ERC7786OpenBridge.Received`](#ERC7786OpenBridge-Received-bytes32-address-) when a known ERC-7786 gateway delivers a message for the first time. +* [`ERC7786OpenBridge.ExecutionSuccess`](#ERC7786OpenBridge-ExecutionSuccess-bytes32-) when a message is successfully delivered to the receiver. +* [`ERC7786OpenBridge.ExecutionFailed`](#ERC7786OpenBridge-ExecutionFailed-bytes32-) when a message delivery to the receiver reverted (for example because of OOG error). + + +interface requires this function to be payable. Even if we don't expect any value, a gateway may pass +some value for unknown reason. In that case we want to register this gateway having delivered the message and +not revert. Any value accrued that way can be recovered by the admin using the [`ERC7786OpenBridge.sweep`](#ERC7786OpenBridge-sweep-address-payable-) function. + + +
+
+ + + +
+
+

getGateways() → address[]

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

getThreshold() → uint8

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

getRemoteBridge(bytes chain) → bytes

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

getRemoteBridge(bytes2 chainType, bytes chainReference) → bytes

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

addGateway(address gateway)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

removeGateway(address gateway)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

setThreshold(uint8 newThreshold)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

registerRemoteBridge(bytes bridge)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

pause()

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

unpause()

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

sweep(address payable to)

+
+

public

+# +
+
+
+ +Recovery method in case value is ever received through [`ERC7786OpenBridge.receiveMessage`](#ERC7786OpenBridge-receiveMessage-bytes32-bytes-bytes-) + +
+
+ + + +
+
+

_addGateway(address gateway)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_removeGateway(address gateway)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_setThreshold(uint8 newThreshold)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_registerRemoteBridge(bytes bridge)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

OutboxDetails(bytes32 indexed sendId, struct ERC7786OpenBridge.Outbox[] outbox)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

Received(bytes32 indexed receiveId, address gateway)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ExecutionSuccess(bytes32 indexed receiveId)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ExecutionFailed(bytes32 indexed receiveId)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

GatewayAdded(address indexed gateway)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

GatewayRemoved(address indexed gateway)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ThresholdUpdated(uint8 threshold)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

RemoteRegistered(bytes remote)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

UnsupportedNativeTransfer()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeInvalidCrosschainSender()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeAlreadyExecuted()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeRemoteNotRegistered(bytes2 chainType, bytes chainReference)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeGatewayAlreadyRegistered(address gateway)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeGatewayNotRegistered(address gateway)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeThresholdViolation()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786OpenBridgeInvalidExecutionReturnValue()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

RemoteAlreadyRegistered(bytes remote)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `AxelarGatewayAdapter` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/crosschain/axelar/AxelarGatewayAdapter.sol"; +``` + +Implementation of an ERC-7786 gateway destination adapter for the Axelar Network in dual mode. + +The contract implements AxelarExecutable's [`ERC7579DelayedExecutor._execute`](./account#ERC7579DelayedExecutor-_execute-address-bytes32-bytes32-bytes-) function to execute the message, converting Axelar's native +workflow into the standard ERC-7786. + + +While both ERC-7786 and Axelar do support non-evm chains, this adaptor does not. This limitation comes from +the translation of the ERC-7930 interoperable address (binary objects -- bytes) to strings. This is necessary +because Axelar uses string to represent addresses. For EVM network, this adapter uses a checksum hex string +representation. Other networks would require a different encoding. Ideally we would have a single encoding for all +networks (could be base58, base64, ...) but Axelar doesn't support that. + + +
+

Functions

+
+- [constructor(gateway, initialOwner)](#AxelarGatewayAdapter-constructor-contract-IAxelarGateway-address-) +- [getAxelarChain(input)](#AxelarGatewayAdapter-getAxelarChain-bytes-) +- [getErc7930Chain(input)](#AxelarGatewayAdapter-getErc7930Chain-string-) +- [getRemoteGateway(chain)](#AxelarGatewayAdapter-getRemoteGateway-bytes-) +- [getRemoteGateway(chainType, chainReference)](#AxelarGatewayAdapter-getRemoteGateway-bytes2-bytes-) +- [registerChainEquivalence(chain, axelar)](#AxelarGatewayAdapter-registerChainEquivalence-bytes-string-) +- [registerRemoteGateway(remote)](#AxelarGatewayAdapter-registerRemoteGateway-bytes-) +- [supportsAttribute()](#AxelarGatewayAdapter-supportsAttribute-bytes4-) +- [sendMessage(recipient, payload, attributes)](#AxelarGatewayAdapter-sendMessage-bytes-bytes-bytes---) +- [_execute(commandId, axelarSourceChain, axelarSourceAddress, adapterPayload)](#AxelarGatewayAdapter-_execute-bytes32-string-string-bytes-) +- [_stringifyAddress(chainType, addr)](#AxelarGatewayAdapter-_stringifyAddress-bytes2-bytes-) +#### AxelarExecutable [!toc] +- [execute(commandId, sourceChain, sourceAddress, payload)](#AxelarExecutable-execute-bytes32-string-string-bytes-) +- [gateway()](#AxelarExecutable-gateway--) +#### IAxelarExecutable [!toc] +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +#### IERC7786GatewaySource [!toc] +
+
+ +
+

Events

+
+- [RegisteredRemoteGateway(remote)](#AxelarGatewayAdapter-RegisteredRemoteGateway-bytes-) +- [RegisteredChainEquivalence(erc7930binary, axelar)](#AxelarGatewayAdapter-RegisteredChainEquivalence-bytes-string-) +#### AxelarExecutable [!toc] +#### IAxelarExecutable [!toc] +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +#### IERC7786GatewaySource [!toc] +- [MessageSent(sendId, sender, receiver, payload, value, attributes)](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) +
+
+ +
+

Errors

+
+- [UnsupportedNativeTransfer()](#AxelarGatewayAdapter-UnsupportedNativeTransfer--) +- [InvalidOriginGateway(axelarSourceChain, axelarSourceAddress)](#AxelarGatewayAdapter-InvalidOriginGateway-string-string-) +- [ReceiverExecutionFailed()](#AxelarGatewayAdapter-ReceiverExecutionFailed--) +- [UnsupportedChainType(chainType)](#AxelarGatewayAdapter-UnsupportedChainType-bytes2-) +- [UnsupportedERC7930Chain(erc7930binary)](#AxelarGatewayAdapter-UnsupportedERC7930Chain-bytes-) +- [UnsupportedAxelarChain(axelar)](#AxelarGatewayAdapter-UnsupportedAxelarChain-string-) +- [InvalidChainIdentifier(erc7930binary)](#AxelarGatewayAdapter-InvalidChainIdentifier-bytes-) +- [ChainEquivalenceAlreadyRegistered(erc7930binary, axelar)](#AxelarGatewayAdapter-ChainEquivalenceAlreadyRegistered-bytes-string-) +- [RemoteGatewayAlreadyRegistered(chainType, chainReference)](#AxelarGatewayAdapter-RemoteGatewayAlreadyRegistered-bytes2-bytes-) +#### AxelarExecutable [!toc] +#### IAxelarExecutable [!toc] +- [InvalidAddress()](#IAxelarExecutable-InvalidAddress--) +- [NotApprovedByGateway()](#IAxelarExecutable-NotApprovedByGateway--) +#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +#### IERC7786GatewaySource [!toc] +- [UnsupportedAttribute(selector)](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) +
+
+ + + +
+
+

constructor(contract IAxelarGateway gateway, address initialOwner)

+
+

public

+# +
+
+
+ +Initializes the contract with the Axelar gateway and the initial owner. + +
+
+ + + +
+
+

getAxelarChain(bytes input) → string output

+
+

public

+# +
+
+
+ +Returns the Axelar chain identifier for a given binary interoperable chain id. + +
+
+ + + +
+
+

getErc7930Chain(string input) → bytes output

+
+

public

+# +
+
+
+ +Returns the binary interoperable chain id for a given Axelar chain identifier. + +
+
+ + + +
+
+

getRemoteGateway(bytes chain) → bytes

+
+

public

+# +
+
+
+ +Returns the address of the remote gateway for a given binary interoperable chain id. + +
+
+ + + +
+
+

getRemoteGateway(bytes2 chainType, bytes chainReference) → bytes

+
+

public

+# +
+
+
+ +Returns the address of the remote gateway for a given chainType and chainReference. + +
+
+ + + +
+
+

registerChainEquivalence(bytes chain, string axelar)

+
+

public

+# +
+
+
+ +Registers a chain equivalence between a binary interoperable chain id and an Axelar chain identifier. + +
+
+ + + +
+
+

registerRemoteGateway(bytes remote)

+
+

public

+# +
+
+
+ +Registers the address of a remote gateway. + +
+
+ + + +
+
+

supportsAttribute(bytes4) → bool

+
+

public

+# +
+
+
+ +Getter to check whether an attribute is supported or not. + +
+
+ + + +
+
+

sendMessage(bytes recipient, bytes payload, bytes[] attributes) → bytes32

+
+

external

+# +
+
+
+ +Endpoint for creating a new message. If the message requires further (gateway specific) processing before +it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the +message MUST be sent and this function must return 0. + +* MUST emit a [`IERC7786GatewaySource.MessageSent`](./interfaces#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) event. + +If any of the `attributes` is not supported, this function SHOULD revert with an [`IERC7786GatewaySource.UnsupportedAttribute`](./interfaces#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) error. +Other errors SHOULD revert with errors not specified in ERC-7786. + +
+
+ + + +
+
+

_execute(bytes32 commandId, string axelarSourceChain, string axelarSourceAddress, bytes adapterPayload)

+
+

internal

+# +
+
+
+ +Execution of a cross-chain message. + +In this function: + +- `axelarSourceChain` is in the Axelar format. It should not be expected to be a proper ERC-7930 format +- `axelarSourceAddress` is the sender of the Axelar message. That should be the remote gateway on the chain + which the message originates from. It is NOT the sender of the ERC-7786 crosschain message. + +Proper ERC-7930 encoding of the crosschain message sender can be found in the message + +
+
+ + + +
+
+

_stringifyAddress(bytes2 chainType, bytes addr) → string

+
+

internal

+# +
+
+
+ +ERC-7930 to Axelar address translation. Currently only supports EVM chains. + +
+
+ + + +
+
+

RegisteredRemoteGateway(bytes remote)

+
+

event

+# +
+
+ +
+ +A remote gateway has been registered for a chain. + +
+
+ + +
+
+

RegisteredChainEquivalence(bytes erc7930binary, string axelar)

+
+

event

+# +
+
+ +
+ +A chain equivalence has been registered. + +
+
+ + + +
+
+

UnsupportedNativeTransfer()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InvalidOriginGateway(string axelarSourceChain, string axelarSourceAddress)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ReceiverExecutionFailed()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

UnsupportedChainType(bytes2 chainType)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

UnsupportedERC7930Chain(bytes erc7930binary)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

UnsupportedAxelarChain(string axelar)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InvalidChainIdentifier(bytes erc7930binary)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ChainEquivalenceAlreadyRegistered(bytes erc7930binary, string axelar)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

RemoteGatewayAlreadyRegistered(bytes2 chainType, bytes chainReference)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `ERC7786Attributes` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/crosschain/utils/ERC7786Attributes.sol"; +``` + +Library of helper to parse/process ERC-7786 attributes + +
+

Functions

+
+- [tryDecodeRequestRelay(attribute)](#ERC7786Attributes-tryDecodeRequestRelay-bytes-) +
+
+ + + +
+
+

tryDecodeRequestRelay(bytes attribute) → bool success, uint256 value, uint256 gasLimit, address refundRecipient

+
+

internal

+# +
+
+
+ +Parse the `requestRelay(uint256,uint256,address)` (0x4cbb573a) attribute into its components. + +
+
+ + + +
+ +## `ERC7786Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/crosschain/utils/ERC7786Receiver.sol"; +``` + +Base implementation of an ERC-7786 compliant cross-chain message receiver. + +This abstract contract exposes the `receiveMessage` function that is used for communication with (one or multiple) +destination gateways. This contract leaves two functions unimplemented: + +[`ERC7786Receiver._isKnownGateway`](#ERC7786Receiver-_isKnownGateway-address-), an internal getter used to verify whether an address is recognised by the contract as a valid +ERC-7786 destination gateway. One or multiple gateway can be supported. Note that any malicious address for which +this function returns true would be able to impersonate any account on any other chain sending any message. + +[`ERC7786Receiver._processMessage`](#ERC7786Receiver-_processMessage-address-bytes32-bytes-bytes-), the internal function that will be called with any message that has been validated. + +
+

Functions

+
+- [receiveMessage(receiveId, sender, payload)](#ERC7786Receiver-receiveMessage-bytes32-bytes-bytes-) +- [_isKnownGateway(instance)](#ERC7786Receiver-_isKnownGateway-address-) +- [_processMessage(gateway, receiveId, sender, payload)](#ERC7786Receiver-_processMessage-address-bytes32-bytes-bytes-) +#### IERC7786Receiver [!toc] +
+
+ +
+

Errors

+
+- [ERC7786ReceiverInvalidGateway(gateway)](#ERC7786Receiver-ERC7786ReceiverInvalidGateway-address-) +- [ERC7786ReceivePassiveModeValue()](#ERC7786Receiver-ERC7786ReceivePassiveModeValue--) +#### IERC7786Receiver [!toc] +
+
+ + + +
+
+

receiveMessage(bytes32 receiveId, bytes sender, bytes payload) → bytes4

+
+

public

+# +
+
+
+ +Endpoint for receiving cross-chain message. + +This function may be called directly by the gateway. + +
+
+ + + +
+
+

_isKnownGateway(address instance) → bool

+
+

internal

+# +
+
+
+ +Virtual getter that returns whether an address is a valid ERC-7786 gateway. + +
+
+ + + +
+
+

_processMessage(address gateway, bytes32 receiveId, bytes sender, bytes payload)

+
+

internal

+# +
+
+
+ +Virtual function that should contain the logic to execute when a cross-chain message is received. + +
+
+ + + +
+
+

ERC7786ReceiverInvalidGateway(address gateway)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786ReceivePassiveModeValue()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `WormholeGatewayAdapter` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/crosschain/wormhole/WormholeGatewayAdapter.sol"; +``` + +An ERC-7786 compliant adapter to send and receive messages via Wormhole. + +Note: only EVM chains are currently supported + +
+

Modifiers

+
+- [onlyWormholeRelayer()](#WormholeGatewayAdapter-onlyWormholeRelayer--) +
+
+ +
+

Functions

+
+- [constructor(wormholeRelayer, wormholeChainId, initialOwner)](#WormholeGatewayAdapter-constructor-contract-IWormholeRelayer-uint16-address-) +- [relayer()](#WormholeGatewayAdapter-relayer--) +- [supportedChain(chain)](#WormholeGatewayAdapter-supportedChain-bytes-) +- [supportedChain(chainId)](#WormholeGatewayAdapter-supportedChain-uint256-) +- [getWormholeChain(chain)](#WormholeGatewayAdapter-getWormholeChain-bytes-) +- [getWormholeChain(chainId)](#WormholeGatewayAdapter-getWormholeChain-uint256-) +- [getChainId(wormholeId)](#WormholeGatewayAdapter-getChainId-uint16-) +- [getRemoteGateway(chain)](#WormholeGatewayAdapter-getRemoteGateway-bytes-) +- [getRemoteGateway(chainId)](#WormholeGatewayAdapter-getRemoteGateway-uint256-) +- [registerChainEquivalence(chain, wormholeId)](#WormholeGatewayAdapter-registerChainEquivalence-bytes-uint16-) +- [registerChainEquivalence(chainId, wormholeId)](#WormholeGatewayAdapter-registerChainEquivalence-uint256-uint16-) +- [registerRemoteGateway(remote)](#WormholeGatewayAdapter-registerRemoteGateway-bytes-) +- [registerRemoteGateway(chainId, addr)](#WormholeGatewayAdapter-registerRemoteGateway-uint256-address-) +- [supportsAttribute(selector)](#WormholeGatewayAdapter-supportsAttribute-bytes4-) +- [sendMessage(recipient, payload, attributes)](#WormholeGatewayAdapter-sendMessage-bytes-bytes-bytes---) +- [quoteRelay(recipient, , , value, gasLimit, )](#WormholeGatewayAdapter-quoteRelay-bytes-bytes-bytes---uint256-uint256-address-) +- [requestRelay(sendId, gasLimit, refundRecipient)](#WormholeGatewayAdapter-requestRelay-bytes32-uint256-address-) +- [receiveWormholeMessages(adapterPayload, additionalMessages, wormholeSourceAddress, wormholeSourceChain, deliveryHash)](#WormholeGatewayAdapter-receiveWormholeMessages-bytes-bytes---bytes32-uint16-bytes32-) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +#### IWormholeReceiver [!toc] +#### IERC7786GatewaySource [!toc] +
+
+ +
+

Events

+
+- [MessageRelayed(sendId)](#WormholeGatewayAdapter-MessageRelayed-bytes32-) +- [RegisteredRemoteGateway(chainId, remote)](#WormholeGatewayAdapter-RegisteredRemoteGateway-uint256-address-) +- [RegisteredChainEquivalence(chainId, wormholeId)](#WormholeGatewayAdapter-RegisteredChainEquivalence-uint256-uint16-) +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +#### IWormholeReceiver [!toc] +#### IERC7786GatewaySource [!toc] +- [MessageSent(sendId, sender, receiver, payload, value, attributes)](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) +
+
+ +
+

Errors

+
+- [InvalidAttributeEncoding(attribute)](#WormholeGatewayAdapter-InvalidAttributeEncoding-bytes-) +- [DuplicatedAttribute()](#WormholeGatewayAdapter-DuplicatedAttribute--) +- [UnauthorizedCaller()](#WormholeGatewayAdapter-UnauthorizedCaller-address-) +- [InvalidOriginGateway(wormholeSourceChain, wormholeSourceAddress)](#WormholeGatewayAdapter-InvalidOriginGateway-uint16-bytes32-) +- [ReceiverExecutionFailed()](#WormholeGatewayAdapter-ReceiverExecutionFailed--) +- [UnsupportedChainId(chainId)](#WormholeGatewayAdapter-UnsupportedChainId-uint256-) +- [UnsupportedWormholeChain(wormholeId)](#WormholeGatewayAdapter-UnsupportedWormholeChain-uint16-) +- [ChainEquivalenceAlreadyRegistered(chainId, wormhole)](#WormholeGatewayAdapter-ChainEquivalenceAlreadyRegistered-uint256-uint16-) +- [RemoteGatewayAlreadyRegistered(chainId)](#WormholeGatewayAdapter-RemoteGatewayAlreadyRegistered-uint256-) +- [InvalidSendId(sendId)](#WormholeGatewayAdapter-InvalidSendId-bytes32-) +- [AdditionalMessagesNotSupported()](#WormholeGatewayAdapter-AdditionalMessagesNotSupported--) +- [MessageAlreadyExecuted(chainId, outboxId)](#WormholeGatewayAdapter-MessageAlreadyExecuted-uint256-bytes32-) +#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +#### IWormholeReceiver [!toc] +#### IERC7786GatewaySource [!toc] +- [UnsupportedAttribute(selector)](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) +
+
+ + + +
+
+

onlyWormholeRelayer()

+
+

internal

+# +
+
+ +
+ +
+
+ + + +
+
+

constructor(contract IWormholeRelayer wormholeRelayer, uint16 wormholeChainId, address initialOwner)

+
+

public

+# +
+
+
+ +Initializes the contract with the Wormhole gateway and the initial owner. + +
+
+ + + +
+
+

relayer() → address

+
+

public

+# +
+
+
+ +Returns the local Wormhole relayer + +
+
+ + + +
+
+

supportedChain(bytes chain) → bool

+
+

public

+# +
+
+
+ +Returns whether a binary interoperable chain id is supported. + +
+
+ + + +
+
+

supportedChain(uint256 chainId) → bool

+
+

public

+# +
+
+
+ +Returns whether an EVM chain id is supported. + +
+
+ + + +
+
+

getWormholeChain(bytes chain) → uint16

+
+

public

+# +
+
+
+ +Returns the Wormhole chain id that correspond to a given binary interoperable chain id. + +
+
+ + + +
+
+

getWormholeChain(uint256 chainId) → uint16

+
+

public

+# +
+
+
+ +Returns the Wormhole chain id that correspond to a given EVM chain id. + +
+
+ + + +
+
+

getChainId(uint16 wormholeId) → uint256

+
+

public

+# +
+
+
+ +Returns the EVM chain id for a given Wormhole chain id. + +
+
+ + + +
+
+

getRemoteGateway(bytes chain) → address

+
+

public

+# +
+
+
+ +Returns the address of the remote gateway for a given binary interoperable chain id. + +
+
+ + + +
+
+

getRemoteGateway(uint256 chainId) → address

+
+

public

+# +
+
+
+ +Returns the address of the remote gateway for a given EVM chain id. + +
+
+ + + +
+
+

registerChainEquivalence(bytes chain, uint16 wormholeId)

+
+

public

+# +
+
+
+ +Registers a chain equivalence between a binary interoperable chain id and a Wormhole chain id. + +
+
+ + + +
+
+

registerChainEquivalence(uint256 chainId, uint16 wormholeId)

+
+

public

+# +
+
+
+ +Registers a chain equivalence between an EVM chain id and a Wormhole chain id. + +
+
+ + + +
+
+

registerRemoteGateway(bytes remote)

+
+

public

+# +
+
+
+ +Registers the address of a remote gateway (binary interoperable address version). + +
+
+ + + +
+
+

registerRemoteGateway(uint256 chainId, address addr)

+
+

public

+# +
+
+
+ +Registers the address of a remote gateway (EVM version). + +
+
+ + + +
+
+

supportsAttribute(bytes4 selector) → bool

+
+

public

+# +
+
+
+ +Getter to check whether an attribute is supported or not. + +
+
+ + + +
+
+

sendMessage(bytes recipient, bytes payload, bytes[] attributes) → bytes32 sendId

+
+

external

+# +
+
+
+ +Endpoint for creating a new message. If the message requires further (gateway specific) processing before +it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the +message MUST be sent and this function must return 0. + +* MUST emit a [`IERC7786GatewaySource.MessageSent`](./interfaces#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) event. + +If any of the `attributes` is not supported, this function SHOULD revert with an [`IERC7786GatewaySource.UnsupportedAttribute`](./interfaces#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) error. +Other errors SHOULD revert with errors not specified in ERC-7786. + +
+
+ + + +
+
+

quoteRelay(bytes recipient, bytes, bytes[], uint256 value, uint256 gasLimit, address) → uint256

+
+

external

+# +
+
+
+ +Returns a quote for the value that must be passed to [`WormholeGatewayAdapter.requestRelay`](#WormholeGatewayAdapter-requestRelay-bytes32-uint256-address-) + +
+
+ + + +
+
+

requestRelay(bytes32 sendId, uint256 gasLimit, address refundRecipient)

+
+

external

+# +
+
+
+ +Relay a message that was initiated by [`ERC7786OpenBridge.sendMessage`](#ERC7786OpenBridge-sendMessage-bytes-bytes-bytes---). + +
+
+ + + +
+
+

receiveWormholeMessages(bytes adapterPayload, bytes[] additionalMessages, bytes32 wormholeSourceAddress, uint16 wormholeSourceChain, bytes32 deliveryHash)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

MessageRelayed(bytes32 sendId)

+
+

event

+# +
+
+ +
+ +A message was relayed to Wormhole (part of the post processing of the outbox ids created by [`ERC7786OpenBridge.sendMessage`](#ERC7786OpenBridge-sendMessage-bytes-bytes-bytes---)) + +
+
+ + +
+
+

RegisteredRemoteGateway(uint256 chainId, address remote)

+
+

event

+# +
+
+ +
+ +A remote gateway has been registered for a chain. + +
+
+ + +
+
+

RegisteredChainEquivalence(uint256 chainId, uint16 wormholeId)

+
+

event

+# +
+
+ +
+ +A chain equivalence has been registered. + +
+
+ + + +
+
+

InvalidAttributeEncoding(bytes attribute)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

DuplicatedAttribute()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

UnauthorizedCaller(address)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InvalidOriginGateway(uint16 wormholeSourceChain, bytes32 wormholeSourceAddress)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ReceiverExecutionFailed()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

UnsupportedChainId(uint256 chainId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

UnsupportedWormholeChain(uint16 wormholeId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ChainEquivalenceAlreadyRegistered(uint256 chainId, uint16 wormhole)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

RemoteGatewayAlreadyRegistered(uint256 chainId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InvalidSendId(bytes32 sendId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AdditionalMessagesNotSupported()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

MessageAlreadyExecuted(uint256 chainId, bytes32 outboxId)

+
+

error

+# +
+
+
+ +
+
diff --git a/docs/content/community-contracts/api/index.mdx b/docs/content/community-contracts/api/index.mdx new file mode 100644 index 00000000..14a2be3d --- /dev/null +++ b/docs/content/community-contracts/api/index.mdx @@ -0,0 +1,17 @@ +--- +title: API Reference +--- + +## Core APIs + +- **[Access](./api/access)** - Access control and permission management +- **[Account](./api/account)** - Account abstraction and smart account functionality +- **[Crosschain](./api/crosschain)** - Cross-chain communication and bridging utilities +- **[Interfaces](./api/interfaces)** - Standard interfaces and contract definitions +- **[Proxy](./api/proxy)** - Proxy patterns and upgradeable contract utilities +- **[Token](./api/token)** - Token standards and implementations + +## Utilities + +- **[Utils](./api/utils)** - General utility functions and helpers + - **[Cryptography](./api/utils/cryptography)** - Cryptographic functions and primitives diff --git a/docs/content/community-contracts/api/interfaces.mdx b/docs/content/community-contracts/api/interfaces.mdx new file mode 100644 index 00000000..cfa51baf --- /dev/null +++ b/docs/content/community-contracts/api/interfaces.mdx @@ -0,0 +1,713 @@ +--- +title: "Interfaces" +description: "Smart contract interfaces utilities and implementations" +--- + +## List of standardized interfaces + +These interfaces are available as `.sol` files. These are useful to interact with third party contracts that implement them. + +* [`IERC7786GatewaySource`](#IERC7786GatewaySource), [`IERC7786Receiver`](#IERC7786Receiver) +* [`IERC7802`](#IERC7802) +* [`IERC7821`](#IERC7821) +* `IERC7913SignatureVerifier` +* [`IERC7943`](#IERC7943) + +## Detailed ABI + +[`IERC7786GatewaySource`](#IERC7786GatewaySource) + +[`IERC7786Receiver`](#IERC7786Receiver) + +[`IERC7802`](#IERC7802) + +[`IERC7821`](#IERC7821) + +{`IERC7913SignatureVerifier`} + +[`IERC7943`](#IERC7943) + + + +
+ +## `IERC7786GatewaySource` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7786.sol"; +``` + +Interface for ERC-7786 source gateways. + +See ERC-7786 for more details + +
+

Functions

+
+- [supportsAttribute(selector)](#IERC7786GatewaySource-supportsAttribute-bytes4-) +- [sendMessage(recipient, payload, attributes)](#IERC7786GatewaySource-sendMessage-bytes-bytes-bytes---) +
+
+ +
+

Events

+
+- [MessageSent(sendId, sender, receiver, payload, value, attributes)](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) +
+
+ +
+

Errors

+
+- [UnsupportedAttribute(selector)](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) +
+
+ + + +
+
+

supportsAttribute(bytes4 selector) → bool

+
+

external

+# +
+
+
+ +Getter to check whether an attribute is supported or not. + +
+
+ + + +
+
+

sendMessage(bytes recipient, bytes payload, bytes[] attributes) → bytes32 sendId

+
+

external

+# +
+
+
+ +Endpoint for creating a new message. If the message requires further (gateway specific) processing before +it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the +message MUST be sent and this function must return 0. + +* MUST emit a [`IERC7786GatewaySource.MessageSent`](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) event. + +If any of the `attributes` is not supported, this function SHOULD revert with an [`IERC7786GatewaySource.UnsupportedAttribute`](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) error. +Other errors SHOULD revert with errors not specified in ERC-7786. + +
+
+ + + +
+
+

MessageSent(bytes32 indexed sendId, bytes sender, bytes receiver, bytes payload, uint256 value, bytes[] attributes)

+
+

event

+# +
+
+ +
+ +Event emitted when a message is created. If `outboxId` is zero, no further processing is necessary. If +`outboxId` is not zero, then further (gateway specific, and non-standardized) action is required. + +
+
+ + + +
+
+

UnsupportedAttribute(bytes4 selector)

+
+

error

+# +
+
+
+ +This error is thrown when a message creation fails because of an unsupported attribute being specified. + +
+
+ + + +
+ +## `IERC7786Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7786.sol"; +``` + +Interface for the ERC-7786 client contract (receiver). + +See ERC-7786 for more details + +
+

Functions

+
+- [receiveMessage(receiveId, sender, payload)](#IERC7786Receiver-receiveMessage-bytes32-bytes-bytes-) +
+
+ + + +
+
+

receiveMessage(bytes32 receiveId, bytes sender, bytes payload) → bytes4

+
+

external

+# +
+
+
+ +Endpoint for receiving cross-chain message. + +This function may be called directly by the gateway. + +
+
+ + + +
+ +## `IERC7786Attributes` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7786Attributes.sol"; +``` + +Standard attributes for ERC-7786. These attributes may be standardized in different ERCs. + +
+

Functions

+
+- [requestRelay(value, gasLimit, refundRecipient)](#IERC7786Attributes-requestRelay-uint256-uint256-address-) +
+
+ + + +
+
+

requestRelay(uint256 value, uint256 gasLimit, address refundRecipient)

+
+

external

+# +
+
+
+ +
+
+ + + +
+ +## `IERC7802` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7802.sol"; +``` + +
+

Functions

+
+- [crosschainMint(_to, _amount)](#IERC7802-crosschainMint-address-uint256-) +- [crosschainBurn(_from, _amount)](#IERC7802-crosschainBurn-address-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [CrosschainMint(to, amount, sender)](#IERC7802-CrosschainMint-address-uint256-address-) +- [CrosschainBurn(from, amount, sender)](#IERC7802-CrosschainBurn-address-uint256-address-) +#### IERC165 [!toc] +
+
+ + + +
+
+

crosschainMint(address _to, uint256 _amount)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

crosschainBurn(address _from, uint256 _amount)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

CrosschainMint(address indexed to, uint256 amount, address indexed sender)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

CrosschainBurn(address indexed from, uint256 amount, address indexed sender)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC7821` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7821.sol"; +``` + +Interface for minimal batch executor. + +
+

Functions

+
+- [execute(mode, executionData)](#IERC7821-execute-bytes32-bytes-) +- [supportsExecutionMode(mode)](#IERC7821-supportsExecutionMode-bytes32-) +
+
+ + + +
+
+

execute(bytes32 mode, bytes executionData)

+
+

external

+# +
+
+
+ +Executes the calls in `executionData`. +Reverts and bubbles up error if any call fails. + +`executionData` encoding: + +* If `opData` is empty, `executionData` is simply `abi.encode(calls)`. +* Else, `executionData` is `abi.encode(calls, opData)`. + See: https://eips.ethereum.org/EIPS/eip-7579 + +Supported modes: + +* `bytes32(0x01000000000000000000...)`: does not support optional `opData`. +* `bytes32(0x01000000000078210001...)`: supports optional `opData`. + +Authorization checks: + +* If `opData` is empty, the implementation SHOULD require that + `msg.sender == address(this)`. +* If `opData` is not empty, the implementation SHOULD use the signature + encoded in `opData` to determine if the caller can perform the execution. + +`opData` may be used to store additional data for authentication, +paymaster data, gas limits, etc. + +
+
+ + + +
+
+

supportsExecutionMode(bytes32 mode) → bool

+
+

external

+# +
+
+
+ +This function is provided for frontends to detect support. +Only returns true for: + +* `bytes32(0x01000000000000000000...)`: does not support optional `opData`. +* `bytes32(0x01000000000078210001...)`: supports optional `opData`. + +
+
+ + + +
+ +## `IERC7943` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7943.sol"; +``` + +
+

Functions

+
+- [forceTransfer(from, to, tokenId, amount)](#IERC7943-forceTransfer-address-address-uint256-uint256-) +- [setFrozen(user, tokenId, amount)](#IERC7943-setFrozen-address-uint256-uint256-) +- [getFrozen(user, tokenId)](#IERC7943-getFrozen-address-uint256-) +- [isTransferAllowed(from, to, tokenId, amount)](#IERC7943-isTransferAllowed-address-address-uint256-uint256-) +- [isUserAllowed(user)](#IERC7943-isUserAllowed-address-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [ForcedTransfer(from, to, tokenId, amount)](#IERC7943-ForcedTransfer-address-address-uint256-uint256-) +- [Frozen(user, tokenId, amount)](#IERC7943-Frozen-address-uint256-uint256-) +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [ERC7943NotAllowedUser(account)](#IERC7943-ERC7943NotAllowedUser-address-) +- [ERC7943NotAllowedTransfer(from, to, tokenId, amount)](#IERC7943-ERC7943NotAllowedTransfer-address-address-uint256-uint256-) +- [ERC7943InsufficientUnfrozenBalance(user, tokenId, amount, unfrozen)](#IERC7943-ERC7943InsufficientUnfrozenBalance-address-uint256-uint256-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

forceTransfer(address from, address to, uint256 tokenId, uint256 amount)

+
+

external

+# +
+
+
+ +Requires specific authorization. Used for regulatory compliance or recovery scenarios. + +
+
+ + + +
+
+

setFrozen(address user, uint256 tokenId, uint256 amount)

+
+

external

+# +
+
+
+ +Requires specific authorization. Frozen tokens cannot be transferred by the user. + +
+
+ + + +
+
+

getFrozen(address user, uint256 tokenId) → uint256 amount

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

isTransferAllowed(address from, address to, uint256 tokenId, uint256 amount) → bool allowed

+
+

external

+# +
+
+
+ +This may involve checks like allowlists, blocklists, transfer limits and other policy-defined restrictions. + +
+
+ + + +
+
+

isUserAllowed(address user) → bool allowed

+
+

external

+# +
+
+
+ +This is often used for allowlist/KYC/KYB/AML checks. + +
+
+ + + +
+
+

ForcedTransfer(address indexed from, address indexed to, uint256 tokenId, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

Frozen(address indexed user, uint256 indexed tokenId, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

ERC7943NotAllowedUser(address account)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7943NotAllowedTransfer(address from, address to, uint256 tokenId, uint256 amount)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7943InsufficientUnfrozenBalance(address user, uint256 tokenId, uint256 amount, uint256 unfrozen)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `IDKIMRegistry` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/interfaces/IERC7969.sol"; +``` + +This interface provides a standard way to register and validate DKIM public key hashes onchain +Domain owners can register their DKIM public key hashes and third parties can verify their validity +The interface enables email-based account abstraction and secure account recovery mechanisms. + + +The ERC-165 identifier for this interface is `0xdee3d600`. + + +
+

Functions

+
+- [isKeyHashValid(domainHash, keyHash)](#IDKIMRegistry-isKeyHashValid-bytes32-bytes32-) +
+
+ +
+

Events

+
+- [KeyHashRegistered(domainHash, keyHash)](#IDKIMRegistry-KeyHashRegistered-bytes32-bytes32-) +- [KeyHashRevoked(domainHash)](#IDKIMRegistry-KeyHashRevoked-bytes32-) +
+
+ + + +
+
+

isKeyHashValid(bytes32 domainHash, bytes32 keyHash) → bool

+
+

external

+# +
+
+
+ +Checks if a DKIM key hash is valid for a given domain + +
+
+ + + +
+
+

KeyHashRegistered(bytes32 domainHash, bytes32 keyHash)

+
+

event

+# +
+
+ +
+ +Emitted when a new DKIM public key hash is registered for a domain + +
+
+ + +
+
+

KeyHashRevoked(bytes32 domainHash)

+
+

event

+# +
+
+ +
+ +Emitted when a DKIM public key hash is revoked for a domain + +
+
diff --git a/docs/content/community-contracts/api/proxy.mdx b/docs/content/community-contracts/api/proxy.mdx new file mode 100644 index 00000000..9cc675f5 --- /dev/null +++ b/docs/content/community-contracts/api/proxy.mdx @@ -0,0 +1,94 @@ +--- +title: "Proxy" +description: "Smart contract proxy utilities and implementations" +--- + +Variants of proxy patterns, which are contracts that allow to forward a call to an implementation contract by using `delegatecall`. This contracts include: + +* [`HybridProxy`](#HybridProxy): An ERC-1967 proxy that uses the implementation slot as a beacon in a way that a user can upgrade to an implementation of their choice. + +## General + +[`HybridProxy`](#HybridProxy) + + + +
+ +## `HybridProxy` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/proxy/HybridProxy.sol"; +``` + +A version of an ERC-1967 proxy that uses the address stored in the implementation slot as a beacon. + +The design allows to set an initial beacon that the contract may quit by upgrading to its own implementation +afterwards. Transition between the "beacon mode" and the "direct mode" require implementation that expose an +upgrade mechanism that writes to the ERC-1967 implementation slot. Note that UUPSUpgradable includes security +checks that are not compatible with this proxy design. + + +The fallback mechanism relies on the implementation not to define the `IBeacon-implementation` function. +Consider that if your implementation has this function, it'll be assumed as the beacon address, meaning that +the returned address will be used as this proxy's implementation. + + +
+

Functions

+
+- [constructor(implementation, data)](#HybridProxy-constructor-address-bytes-) +- [_implementation()](#HybridProxy-_implementation--) +#### Proxy [!toc] +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +
+
+ + + +
+
+

constructor(address implementation, bytes data)

+
+

public

+# +
+
+
+ +Initializes the proxy with an initial implementation. If data is present, it will be used to initialize the +implementation using a delegate call. + +
+
+ + + +
+
+

_implementation() → address

+
+

internal

+# +
+
+
+ +Returns the current implementation address according to ERC-1967's implementation slot. + + +The way this function identifies whether the implementation is a beacon, is by checking +if it implements the `IBeacon-implementation` function. Consider that an actual implementation could +define this function, mistakenly identifying it as a beacon. + + +
+
diff --git a/docs/content/community-contracts/api/token.mdx b/docs/content/community-contracts/api/token.mdx new file mode 100644 index 00000000..aecf4bb2 --- /dev/null +++ b/docs/content/community-contracts/api/token.mdx @@ -0,0 +1,2078 @@ +--- +title: "Token" +description: "Smart contract token utilities and implementations" +--- + +Set of extensions and utilities for tokens (e.g ERC-20, ERC-721, ERC-1155) and derivated ERCs (e.g. ERC-4626, ERC-1363). + +* [`OnTokenTransferAdapter`](#OnTokenTransferAdapter): Adapter of the ERC-1363 receiver interface to comply with Chainlink’s 667 interface. +* [`ERC20Allowlist`](#ERC20Allowlist): Extension of ERC20 with transfers and approvals that require users to be registered into an allowlist. +* [`ERC20Blocklist`](#ERC20Blocklist): Extension of ERC20 with transfers and approvals that can be disabled by adding users into a blocklist. +* [`ERC20Collateral`](#ERC20Collateral): Oracle-agnostic extension of ERC20 that limits the total supply based on a collateral amount. +* [`ERC20Custodian`](#ERC20Custodian): Extension of ERC20 that implements an access-control agnostic approach to define a custodian that can freeze user’s transfers and approvals. +* [`ERC4626Fees`](#ERC4626Fees): ERC4626 vault with fees on entry (deposit/mint) or exit (withdraw/redeem). + +## General + +[`OnTokenTransferAdapter`](#OnTokenTransferAdapter) + +## ERC20 + +[`ERC20Allowlist`](#ERC20Allowlist) + +[`ERC20Blocklist`](#ERC20Blocklist) + +[`ERC20Collateral`](#ERC20Collateral) + +[`ERC20Custodian`](#ERC20Custodian) + +[`ERC4626Fees`](#ERC4626Fees) + + + +
+ +## `ERC20Allowlist` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Allowlist.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) that allows to implement an allowlist +mechanism that can be managed by an authorized account with the +[`ERC20Allowlist._disallowUser`](#ERC20Allowlist-_disallowUser-address-) and [`ERC20Allowlist._allowUser`](#ERC20Allowlist-_allowUser-address-) functions. + +The allowlist provides the guarantee to the contract owner +(e.g. a DAO or a well-configured multisig) that any account won't be +able to execute transfers or approvals to other entities to operate +on its behalf if [`ERC20Allowlist._allowUser`](#ERC20Allowlist-_allowUser-address-) was not called with such account as an +argument. Similarly, the account will be disallowed again if +[`ERC20Allowlist._disallowUser`](#ERC20Allowlist-_disallowUser-address-) is called. + + +Deprecated. Use [`ERC20Restricted`](#ERC20Restricted) instead. + + +
+

Functions

+
+- [allowed(./account)](#ERC20Allowlist-allowed-address-) +- [_allowUser(user)](#ERC20Allowlist-_allowUser-address-) +- [_disallowUser(user)](#ERC20Allowlist-_disallowUser-address-) +- [_update(from, to, value)](#ERC20Allowlist-_update-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20Allowlist-_approve-address-address-uint256-bool-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+- [UserAllowed(user)](#ERC20Allowlist-UserAllowed-address-) +- [UserDisallowed(user)](#ERC20Allowlist-UserDisallowed-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20Disallowed(user)](#ERC20Allowlist-ERC20Disallowed-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

allowed(address account) → bool

+
+

public

+# +
+
+
+ +Returns the allowed status of an account. + +
+
+ + + +
+
+

_allowUser(address user) → bool

+
+

internal

+# +
+
+
+ +Allows a user to receive and transfer tokens, including minting and burning. + +
+
+ + + +
+
+

_disallowUser(address user) → bool

+
+

internal

+# +
+
+
+ +Disallows a user from receiving and transferring tokens, including minting and burning. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +See `ERC20-_update`. + +
+
+ + + +
+
+

_approve(address owner, address spender, uint256 value, bool emitEvent)

+
+

internal

+# +
+
+
+ +See `ERC20-_approve`. + +
+
+ + + +
+
+

UserAllowed(address indexed user)

+
+

event

+# +
+
+ +
+ +Emitted when a `user` is allowed to transfer and approve. + +
+
+ + +
+
+

UserDisallowed(address indexed user)

+
+

event

+# +
+
+ +
+ +Emitted when a user is disallowed. + +
+
+ + + +
+
+

ERC20Disallowed(address user)

+
+

error

+# +
+
+
+ +The operation failed because the user is not allowed. + +
+
+ + + +
+ +## `ERC20Blocklist` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Blocklist.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) that allows to implement a blocklist +mechanism that can be managed by an authorized account with the +[`ERC20Blocklist._blockUser`](#ERC20Blocklist-_blockUser-address-) and [`ERC20Blocklist._unblockUser`](#ERC20Blocklist-_unblockUser-address-) functions. + +The blocklist provides the guarantee to the contract owner +(e.g. a DAO or a well-configured multisig) that any account won't be +able to execute transfers or approvals to other entities to operate +on its behalf if [`ERC20Blocklist._blockUser`](#ERC20Blocklist-_blockUser-address-) was not called with such account as an +argument. Similarly, the account will be unblocked again if +[`ERC20Blocklist._unblockUser`](#ERC20Blocklist-_unblockUser-address-) is called. + + +Deprecated. Use [`ERC20Restricted`](#ERC20Restricted) instead. + + +
+

Functions

+
+- [blocked(./account)](#ERC20Blocklist-blocked-address-) +- [_blockUser(user)](#ERC20Blocklist-_blockUser-address-) +- [_unblockUser(user)](#ERC20Blocklist-_unblockUser-address-) +- [_update(from, to, value)](#ERC20Blocklist-_update-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20Blocklist-_approve-address-address-uint256-bool-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+- [UserBlocked(user)](#ERC20Blocklist-UserBlocked-address-) +- [UserUnblocked(user)](#ERC20Blocklist-UserUnblocked-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20Blocked(user)](#ERC20Blocklist-ERC20Blocked-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

blocked(address account) → bool

+
+

public

+# +
+
+
+ +Returns the blocked status of an account. + +
+
+ + + +
+
+

_blockUser(address user) → bool

+
+

internal

+# +
+
+
+ +Blocks a user from receiving and transferring tokens, including minting and burning. + +
+
+ + + +
+
+

_unblockUser(address user) → bool

+
+

internal

+# +
+
+
+ +Unblocks a user from receiving and transferring tokens, including minting and burning. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +See `ERC20-_update`. + +
+
+ + + +
+
+

_approve(address owner, address spender, uint256 value, bool emitEvent)

+
+

internal

+# +
+
+
+ +See `ERC20-_approve`. + +
+
+ + + +
+
+

UserBlocked(address indexed user)

+
+

event

+# +
+
+ +
+ +Emitted when a user is blocked. + +
+
+ + +
+
+

UserUnblocked(address indexed user)

+
+

event

+# +
+
+ +
+ +Emitted when a user is unblocked. + +
+
+ + + +
+
+

ERC20Blocked(address user)

+
+

error

+# +
+
+
+ +The operation failed because the user is blocked. + +
+
+ + + +
+ +## `ERC20Collateral` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Collateral.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) that limits the supply of tokens based +on a collateral amount and time-based expiration. + +The [`ERC20Collateral.collateral`](#ERC20Collateral-collateral--) function must be implemented to return the collateral +data. This function can call external oracles or use any local storage. + +
+

Functions

+
+- [constructor(liveness_)](#ERC20Collateral-constructor-uint48-) +- [liveness()](#ERC20Collateral-liveness--) +- [clock()](#ERC20Collateral-clock--) +- [CLOCK_MODE()](#ERC20Collateral-CLOCK_MODE--) +- [collateral()](#ERC20Collateral-collateral--) +- [_update(from, to, value)](#ERC20Collateral-_update-address-address-uint256-) +#### IERC6372 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC6372 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20ExceededSupply(increasedSupply, cap)](#ERC20Collateral-ERC20ExceededSupply-uint256-uint256-) +- [ERC20ExpiredCollateral(timestamp, expiration)](#ERC20Collateral-ERC20ExpiredCollateral-uint48-uint48-) +#### IERC6372 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

constructor(uint48 liveness_)

+
+

internal

+# +
+
+
+ +Sets the value of the `_liveness`. This value is immutable, it can only be +set once during construction. + +
+
+ + + +
+
+

liveness() → uint48

+
+

public

+# +
+
+
+ +Returns the minimum liveness duration of collateral. + +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Description of the clock + +
+
+ + + +
+
+

collateral() → uint256 amount, uint48 timestamp

+
+

public

+# +
+
+
+ +Returns the collateral data of the token. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +See `ERC20-_update`. + +
+
+ + + +
+
+

ERC20ExceededSupply(uint256 increasedSupply, uint256 cap)

+
+

error

+# +
+
+
+ +Total supply cap has been exceeded. + +
+
+ + + +
+
+

ERC20ExpiredCollateral(uint48 timestamp, uint48 expiration)

+
+

error

+# +
+
+
+ +Collateral amount has expired. + +
+
+ + + +
+ +## `ERC20Custodian` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Custodian.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) that allows to implement a custodian +mechanism that can be managed by an authorized account with the +[`ERC20Custodian.freeze`](#ERC20Custodian-freeze-address-uint256-) function. + +This mechanism allows a custodian (e.g. a DAO or a +well-configured multisig) to freeze and unfreeze the balance +of a user. + +The frozen balance is not available for transfers or approvals +to other entities to operate on its behalf if. The frozen balance +can be reduced by calling [`ERC20Custodian.freeze`](#ERC20Custodian-freeze-address-uint256-) again with a lower amount. + + +Deprecated. Use [`ERC20Freezable`](#ERC20Freezable) instead. + + +
+

Modifiers

+
+- [onlyCustodian()](#ERC20Custodian-onlyCustodian--) +
+
+ +
+

Functions

+
+- [frozen(user)](#ERC20Custodian-frozen-address-) +- [freeze(user, amount)](#ERC20Custodian-freeze-address-uint256-) +- [availableBalance(./account)](#ERC20Custodian-availableBalance-address-) +- [_isCustodian(user)](#ERC20Custodian-_isCustodian-address-) +- [_update(from, to, value)](#ERC20Custodian-_update-address-address-uint256-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+- [TokensFrozen(user, amount)](#ERC20Custodian-TokensFrozen-address-uint256-) +- [TokensUnfrozen(user, amount)](#ERC20Custodian-TokensUnfrozen-address-uint256-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20InsufficientUnfrozenBalance(user)](#ERC20Custodian-ERC20InsufficientUnfrozenBalance-address-) +- [ERC20InsufficientFrozenBalance(user)](#ERC20Custodian-ERC20InsufficientFrozenBalance-address-) +- [ERC20NotCustodian()](#ERC20Custodian-ERC20NotCustodian--) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

onlyCustodian()

+
+

internal

+# +
+
+ +
+ +Modifier to restrict access to custodian accounts only. + +
+
+ + + +
+
+

frozen(address user) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of tokens frozen for a user. + +
+
+ + + +
+
+

freeze(address user, uint256 amount)

+
+

external

+# +
+
+
+ +Adjusts the amount of tokens frozen for a user. + +
+
+ + + +
+
+

availableBalance(address account) → uint256 available

+
+

public

+# +
+
+
+ +Returns the available (unfrozen) balance of an account. + +
+
+ + + +
+
+

_isCustodian(address user) → bool

+
+

internal

+# +
+
+
+ +Checks if the user is a custodian. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` +(or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding +this function. + +Emits a [`ERC7786OpenBridge.UnsupportedNativeTransfer`](./crosschain#ERC7786OpenBridge-UnsupportedNativeTransfer--) event. + +
+
+ + + +
+
+

TokensFrozen(address indexed user, uint256 amount)

+
+

event

+# +
+
+ +
+ +Emitted when tokens are frozen for a user. + +
+
+ + +
+
+

TokensUnfrozen(address indexed user, uint256 amount)

+
+

event

+# +
+
+ +
+ +Emitted when tokens are unfrozen for a user. + +
+
+ + + +
+
+

ERC20InsufficientUnfrozenBalance(address user)

+
+

error

+# +
+
+
+ +The operation failed because the user has insufficient unfrozen balance. + +
+
+ + + +
+
+

ERC20InsufficientFrozenBalance(address user)

+
+

error

+# +
+
+
+ +The operation failed because the user has insufficient frozen balance. + +
+
+ + + +
+
+

ERC20NotCustodian()

+
+

error

+# +
+
+
+ +Error thrown when a non-custodian account attempts to perform a custodian-only operation. + +
+
+ + + +
+ +## `ERC20Freezable` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Freezable.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) that allows to implement a freezing +mechanism that can be managed by an authorized account with the +`_freezeTokens` and `_unfreezeTokens` functions. + +The freezing mechanism provides the guarantee to the contract owner +(e.g. a DAO or a well-configured multisig) that a specific amount +of tokens held by an account won't be transferable until those +tokens are unfrozen using `_unfreezeTokens`. + +
+

Functions

+
+- [frozen(./account)](#ERC20Freezable-frozen-address-) +- [available(./account)](#ERC20Freezable-available-address-) +- [_setFrozen(user, amount)](#ERC20Freezable-_setFrozen-address-uint256-) +- [_update(from, to, value)](#ERC20Freezable-_update-address-address-uint256-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20InsufficientUnfrozenBalance(user, needed, available)](#ERC20Freezable-ERC20InsufficientUnfrozenBalance-address-uint256-uint256-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

frozen(address account) → uint256

+
+

public

+# +
+
+
+ +Returns the frozen balance of an account. + +
+
+ + + +
+
+

available(address account) → uint256

+
+

public

+# +
+
+
+ +Returns the available (unfrozen) balance of an account. Up to `balanceOf`. + +
+
+ + + +
+
+

_setFrozen(address user, uint256 amount)

+
+

internal

+# +
+
+
+ +Internal function to set the frozen token amount for a user. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +See `ERC20-_update`. + +Requirements: + +* `from` must have sufficient unfrozen balance. + +
+
+ + + +
+
+

ERC20InsufficientUnfrozenBalance(address user, uint256 needed, uint256 available)

+
+

error

+# +
+
+
+ +The operation failed because the user has insufficient unfrozen balance. + +
+
+ + + +
+ +## `ERC20Restricted` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Restricted.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) that allows to implement user account transfer restrictions +through the [`IERC7943.isUserAllowed`](./interfaces#IERC7943-isUserAllowed-address-) function. Inspired by [EIP-7943](https://eips.ethereum.org/EIPS/eip-7943). + +By default, each account has no explicit restriction. The [`IERC7943.isUserAllowed`](./interfaces#IERC7943-isUserAllowed-address-) function acts as +a blocklist. Developers can override [`IERC7943.isUserAllowed`](./interfaces#IERC7943-isUserAllowed-address-) to check that `restriction == ALLOWED` +to implement an allowlist. + +
+

Functions

+
+- [getRestriction(./account)](#ERC20Restricted-getRestriction-address-) +- [isUserAllowed(./account)](#ERC20Restricted-isUserAllowed-address-) +- [_update(from, to, value)](#ERC20Restricted-_update-address-address-uint256-) +- [_setRestriction(./account, restriction)](#ERC20Restricted-_setRestriction-address-enum-ERC20Restricted-Restriction-) +- [_blockUser(./account)](#ERC20Restricted-_blockUser-address-) +- [_allowUser(./account)](#ERC20Restricted-_allowUser-address-) +- [_resetUser(./account)](#ERC20Restricted-_resetUser-address-) +- [_checkRestriction(./account)](#ERC20Restricted-_checkRestriction-address-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+- [UserRestrictionsUpdated(./account, restriction)](#ERC20Restricted-UserRestrictionsUpdated-address-enum-ERC20Restricted-Restriction-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20UserRestricted(./account)](#ERC20Restricted-ERC20UserRestricted-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

getRestriction(address account) → enum ERC20Restricted.Restriction

+
+

public

+# +
+
+
+ +Returns the restriction of a user account. + +
+
+ + + +
+
+

isUserAllowed(address account) → bool

+
+

public

+# +
+
+
+ +Returns whether a user account is allowed to interact with the token. + +Default implementation only disallows explicitly BLOCKED accounts (i.e. a blocklist). + +To convert into an allowlist, override as: + +```solidity +function isUserAllowed(address account) public view virtual override returns (bool) { + return getRestriction(./account) == Restriction.ALLOWED; +} +``` + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +See `ERC20-_update`. Enforces restriction transfers (excluding minting and burning). + +Requirements: + +* `from` must be allowed to transfer tokens (see [`IERC7943.isUserAllowed`](./interfaces#IERC7943-isUserAllowed-address-)). +* `to` must be allowed to receive tokens (see [`IERC7943.isUserAllowed`](./interfaces#IERC7943-isUserAllowed-address-)). + +
+
+ + + +
+
+

_setRestriction(address account, enum ERC20Restricted.Restriction restriction)

+
+

internal

+# +
+
+
+ +Updates the restriction of a user account. + +
+
+ + + +
+
+

_blockUser(address account)

+
+

internal

+# +
+
+
+ +Convenience function to block a user account (set to BLOCKED). + +
+
+ + + +
+
+

_allowUser(address account)

+
+

internal

+# +
+
+
+ +Convenience function to allow a user account (set to ALLOWED). + +
+
+ + + +
+
+

_resetUser(address account)

+
+

internal

+# +
+
+
+ +Convenience function to reset a user account to default restriction. + +
+
+ + + +
+
+

_checkRestriction(address account)

+
+

internal

+# +
+
+
+ +Checks if a user account is restricted. Reverts with [`ERC20Restricted`](#ERC20Restricted) if so. + +
+
+ + + +
+
+

UserRestrictionsUpdated(address indexed account, enum ERC20Restricted.Restriction restriction)

+
+

event

+# +
+
+ +
+ +Emitted when a user account's restriction is updated. + +
+
+ + + +
+
+

ERC20UserRestricted(address account)

+
+

error

+# +
+
+
+ +The operation failed because the user account is restricted. + +
+
+ + + +
+ +## `ERC20uRWA` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20uRWA.sol"; +``` + +Extension of [`PaymasterERC20`](./account#PaymasterERC20) according to [EIP-7943](https://eips.ethereum.org/EIPS/eip-7943). + +Combines standard ERC-20 functionality with RWA-specific features like user restrictions, +asset freezing, and forced asset transfers. + +
+

Functions

+
+- [isUserAllowed(user)](#ERC20uRWA-isUserAllowed-address-) +- [supportsInterface(interfaceId)](#ERC20uRWA-supportsInterface-bytes4-) +- [isTransferAllowed(from, to, , amount)](#ERC20uRWA-isTransferAllowed-address-address-uint256-uint256-) +- [getFrozen(user, )](#ERC20uRWA-getFrozen-address-uint256-) +- [setFrozen(user, , amount)](#ERC20uRWA-setFrozen-address-uint256-uint256-) +- [forceTransfer(from, to, , amount)](#ERC20uRWA-forceTransfer-address-address-uint256-uint256-) +- [_update(from, to, amount)](#ERC20uRWA-_update-address-address-uint256-) +- [_checkEnforcer(from, to, amount)](#ERC20uRWA-_checkEnforcer-address-address-uint256-) +- [_checkFreezer(user, amount)](#ERC20uRWA-_checkFreezer-address-uint256-) +#### IERC7943 [!toc] +#### ERC20Restricted [!toc] +- [getRestriction(./account)](#ERC20Restricted-getRestriction-address-) +- [_setRestriction(./account, restriction)](#ERC20Restricted-_setRestriction-address-enum-ERC20Restricted-Restriction-) +- [_blockUser(./account)](#ERC20Restricted-_blockUser-address-) +- [_allowUser(./account)](#ERC20Restricted-_allowUser-address-) +- [_resetUser(./account)](#ERC20Restricted-_resetUser-address-) +- [_checkRestriction(./account)](#ERC20Restricted-_checkRestriction-address-) +#### ERC20Freezable [!toc] +- [frozen(./account)](#ERC20Freezable-frozen-address-) +- [available(./account)](#ERC20Freezable-available-address-) +- [_setFrozen(user, amount)](#ERC20Freezable-_setFrozen-address-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC7943 [!toc] +- [ForcedTransfer(from, to, tokenId, amount)](#IERC7943-ForcedTransfer-address-address-uint256-uint256-) +- [Frozen(user, tokenId, amount)](#IERC7943-Frozen-address-uint256-uint256-) +#### ERC20Restricted [!toc] +- [UserRestrictionsUpdated(./account, restriction)](#ERC20Restricted-UserRestrictionsUpdated-address-enum-ERC20Restricted-Restriction-) +#### ERC20Freezable [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### IERC7943 [!toc] +- [ERC7943NotAllowedUser(./account)](#IERC7943-ERC7943NotAllowedUser-address-) +- [ERC7943NotAllowedTransfer(from, to, tokenId, amount)](#IERC7943-ERC7943NotAllowedTransfer-address-address-uint256-uint256-) +- [ERC7943InsufficientUnfrozenBalance(user, tokenId, amount, unfrozen)](#IERC7943-ERC7943InsufficientUnfrozenBalance-address-uint256-uint256-uint256-) +#### ERC20Restricted [!toc] +- [ERC20UserRestricted(./account)](#ERC20Restricted-ERC20UserRestricted-address-) +#### ERC20Freezable [!toc] +- [ERC20InsufficientUnfrozenBalance(user, needed, available)](#ERC20Freezable-ERC20InsufficientUnfrozenBalance-address-uint256-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

isUserAllowed(address user) → bool

+
+

public

+# +
+
+
+ +Returns whether a user account is allowed to interact with the token. + +Default implementation only disallows explicitly BLOCKED accounts (i.e. a blocklist). + +To convert into an allowlist, override as: + +```solidity +function isUserAllowed(address account) public view virtual override returns (bool) { + return getRestriction(./account) == Restriction.ALLOWED; +} +``` + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

isTransferAllowed(address from, address to, uint256, uint256 amount) → bool

+
+

external

+# +
+
+
+ +See [`IERC7943.isTransferAllowed`](./interfaces#IERC7943-isTransferAllowed-address-address-uint256-uint256-). + +CAUTION: This function is only meant for external use. Overriding it will not apply the new checks to +the internal [`ERC20Allowlist._update`](#ERC20Allowlist-_update-address-address-uint256-) function. Consider overriding [`ERC20Allowlist._update`](#ERC20Allowlist-_update-address-address-uint256-) accordingly to keep both functions in sync. + +
+
+ + + +
+
+

getFrozen(address user, uint256) → uint256 amount

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

setFrozen(address user, uint256, uint256 amount)

+
+

public

+# +
+
+
+ +See [`IERC7943.setFrozen`](./interfaces#IERC7943-setFrozen-address-uint256-uint256-). + + +The `amount` is capped to the balance of the `user` to ensure the [`IERC7943.Frozen`](./interfaces#IERC7943-Frozen-address-uint256-uint256-) event +emits values that consistently reflect the actual amount of tokens that are frozen. + + +
+
+ + + +
+
+

forceTransfer(address from, address to, uint256, uint256 amount)

+
+

public

+# +
+
+
+ +See [`IERC7943.forceTransfer`](./interfaces#IERC7943-forceTransfer-address-address-uint256-uint256-). + +Bypasses the [`ERC20Restricted`](#ERC20Restricted) restrictions for the `from` address and adjusts the frozen balance +to the new balance after the transfer. + + +This function uses [`ERC20Allowlist._update`](#ERC20Allowlist-_update-address-address-uint256-) to perform the transfer, ensuring all standard ERC20 +side effects (such as balance updates and events) are preserved. If you override [`ERC20Allowlist._update`](#ERC20Allowlist-_update-address-address-uint256-) +to add additional restrictions or logic, those changes will also apply here. +Consider overriding this function to bypass newer restrictions if needed. + + +
+
+ + + +
+
+

_update(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` +(or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding +this function. + +Emits a [`ERC7786OpenBridge.UnsupportedNativeTransfer`](./crosschain#ERC7786OpenBridge-UnsupportedNativeTransfer--) event. + +
+
+ + + +
+
+

_checkEnforcer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Internal function to check if the `enforcer` is allowed to forcibly transfer the `amount` of `tokens`. + +Example usage with `AccessControl-onlyRole`: + +```solidity +function _checkEnforcer(address from, address to, uint256 amount) internal view override onlyRole(ENFORCER_ROLE) {} +``` + +
+
+ + + +
+
+

_checkFreezer(address user, uint256 amount)

+
+

internal

+# +
+
+
+ +Internal function to check if the `freezer` is allowed to freeze the `amount` of `tokens`. + +Example usage with `AccessControl-onlyRole`: + +```solidity +function _checkFreezer(address user, uint256 amount) internal view override onlyRole(FREEZER_ROLE) {} +``` + +
+
+ + + +
+ +## `ERC4626Fees` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/ERC20/extensions/ERC4626Fees.sol"; +``` + +ERC-4626 vault with entry/exit fees expressed in [basis point (bp)](https://en.wikipedia.org/wiki/Basis_point). + +
+

Functions

+
+- [previewDeposit(assets)](#ERC4626Fees-previewDeposit-uint256-) +- [previewMint(shares)](#ERC4626Fees-previewMint-uint256-) +- [previewWithdraw(assets)](#ERC4626Fees-previewWithdraw-uint256-) +- [previewRedeem(shares)](#ERC4626Fees-previewRedeem-uint256-) +- [_deposit(caller, receiver, assets, shares)](#ERC4626Fees-_deposit-address-address-uint256-uint256-) +- [_withdraw(caller, receiver, owner, assets, shares)](#ERC4626Fees-_withdraw-address-address-address-uint256-uint256-) +- [_entryFeeBasisPoints()](#ERC4626Fees-_entryFeeBasisPoints--) +- [_exitFeeBasisPoints()](#ERC4626Fees-_exitFeeBasisPoints--) +- [_entryFeeRecipient()](#ERC4626Fees-_entryFeeRecipient--) +- [_exitFeeRecipient()](#ERC4626Fees-_exitFeeRecipient--) +#### ERC4626 [!toc] +- [decimals()](#ERC4626-decimals--) +- [asset()](#ERC4626-asset--) +- [totalAssets()](#ERC4626-totalAssets--) +- [convertToShares(assets)](#ERC4626-convertToShares-uint256-) +- [convertToAssets(shares)](#ERC4626-convertToAssets-uint256-) +- [maxDeposit()](#ERC4626-maxDeposit-address-) +- [maxMint()](#ERC4626-maxMint-address-) +- [maxWithdraw(owner)](#ERC4626-maxWithdraw-address-) +- [maxRedeem(owner)](#ERC4626-maxRedeem-address-) +- [deposit(assets, receiver)](#ERC4626-deposit-uint256-address-) +- [mint(shares, receiver)](#ERC4626-mint-uint256-address-) +- [withdraw(assets, receiver, owner)](#ERC4626-withdraw-uint256-address-address-) +- [redeem(shares, receiver, owner)](#ERC4626-redeem-uint256-address-address-) +- [_convertToShares(assets, rounding)](#ERC4626-_convertToShares-uint256-enum-Math-Rounding-) +- [_convertToAssets(shares, rounding)](#ERC4626-_convertToAssets-uint256-enum-Math-Rounding-) +- [_decimalsOffset()](#ERC4626-_decimalsOffset--) +#### IERC4626 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(./account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(./account, value)](#ERC20-_mint-address-uint256-) +- [_burn(./account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### ERC4626 [!toc] +#### IERC4626 [!toc] +- [Deposit(sender, owner, assets, shares)](#IERC4626-Deposit-address-address-uint256-uint256-) +- [Withdraw(sender, receiver, owner, assets, shares)](#IERC4626-Withdraw-address-address-address-uint256-uint256-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### ERC4626 [!toc] +- [ERC4626ExceededMaxDeposit(receiver, assets, max)](#ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256-) +- [ERC4626ExceededMaxMint(receiver, shares, max)](#ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256-) +- [ERC4626ExceededMaxWithdraw(owner, assets, max)](#ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256-) +- [ERC4626ExceededMaxRedeem(owner, shares, max)](#ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256-) +#### IERC4626 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

previewDeposit(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +Preview taking an entry fee on deposit. See `IERC4626-previewDeposit`. + +
+
+ + + +
+
+

previewMint(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +Preview adding an entry fee on mint. See `IERC4626-previewMint`. + +
+
+ + + +
+
+

previewWithdraw(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +Preview adding an exit fee on withdraw. See `IERC4626-previewWithdraw`. + +
+
+ + + +
+
+

previewRedeem(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +Preview taking an exit fee on redeem. See `IERC4626-previewRedeem`. + +
+
+ + + +
+
+

_deposit(address caller, address receiver, uint256 assets, uint256 shares)

+
+

internal

+# +
+
+
+ +Send entry fee to [`ERC4626Fees._entryFeeRecipient`](#ERC4626Fees-_entryFeeRecipient--). See `IERC4626-_deposit`. + +
+
+ + + +
+
+

_withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)

+
+

internal

+# +
+
+
+ +Send exit fee to [`ERC4626Fees._exitFeeRecipient`](#ERC4626Fees-_exitFeeRecipient--). See `IERC4626-_deposit`. + +
+
+ + + +
+
+

_entryFeeBasisPoints() → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_exitFeeBasisPoints() → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_entryFeeRecipient() → address

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_exitFeeRecipient() → address

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `OnTokenTransferAdapter` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/token/OnTokenTransferAdapter.sol"; +``` + +This contract exposes the 667 `onTokenTransfer` hook on top of `IERC1363Receiver-onTransferReceived`. + +Inheriting from this adapter makes your `ERC1363Receiver` contract automatically compatible with tokens, such as +Chainlink's Link, that implement the 667 interface for transferAndCall. + +
+

Functions

+
+- [onTokenTransfer(from, amount, data)](#OnTokenTransferAdapter-onTokenTransfer-address-uint256-bytes-) +#### IERC1363Receiver [!toc] +- [onTransferReceived(operator, from, value, data)](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) +
+
+ + + +
+
+

onTokenTransfer(address from, uint256 amount, bytes data) → bool

+
+

public

+# +
+
+
+ +
+
diff --git a/docs/content/community-contracts/api/utils.mdx b/docs/content/community-contracts/api/utils.mdx new file mode 100644 index 00000000..166b04ea --- /dev/null +++ b/docs/content/community-contracts/api/utils.mdx @@ -0,0 +1,975 @@ +--- +title: "Utils" +description: "Smart contract utils utilities and implementations" +--- + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + +* [`EnumerableSetExtended`](#EnumerableSetExtended) and [`EnumerableMapExtended`](#EnumerableMapExtended): Extensions of the `EnumerableSet` and `EnumerableMap` libraries with more types, including non-value types. +* [`Masks`](#Masks): Library to handle `bytes32` masks. + +## Structs + +[`EnumerableSetExtended`](#EnumerableSetExtended) + +[`EnumerableMapExtended`](#EnumerableMapExtended) + +## Libraries + +[`Masks`](#Masks) + + + +
+ +## `Masks` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/Masks.sol"; +``` + +Library for handling bit masks + +
+

Functions

+
+- [toMask(group)](#Masks-toMask-uint8-) +- [toMask(groups)](#Masks-toMask-uint8---) +- [get(self, group)](#Masks-get-Masks-Mask-uint8-) +- [isEmpty(self)](#Masks-isEmpty-Masks-Mask-) +- [complement(m1)](#Masks-complement-Masks-Mask-) +- [union(m1, m2)](#Masks-union-Masks-Mask-Masks-Mask-) +- [intersection(m1, m2)](#Masks-intersection-Masks-Mask-Masks-Mask-) +- [difference(m1, m2)](#Masks-difference-Masks-Mask-Masks-Mask-) +- [symmetricDifference(m1, m2)](#Masks-symmetricDifference-Masks-Mask-Masks-Mask-) +
+
+ + + +
+
+

toMask(uint8 group) → Masks.Mask

+
+

internal

+# +
+
+
+ +Returns a new mask with the bit at `group` index set to 1. + +
+
+ + + +
+
+

toMask(uint8[] groups) → Masks.Mask

+
+

internal

+# +
+
+
+ +Returns a new mask with the bits at `groups` indices set to 1. + +
+
+ + + +
+
+

get(Masks.Mask self, uint8 group) → bool

+
+

internal

+# +
+
+
+ +Get value of the mask at `group` index + +
+
+ + + +
+
+

isEmpty(Masks.Mask self) → bool

+
+

internal

+# +
+
+
+ +Whether the mask is `bytes32(0)` + +
+
+ + + +
+
+

complement(Masks.Mask m1) → Masks.Mask

+
+

internal

+# +
+
+
+ +Invert the bits of a mask + +
+
+ + + +
+
+

union(Masks.Mask m1, Masks.Mask m2) → Masks.Mask

+
+

internal

+# +
+
+
+ +Perform a bitwise OR operation on two masks + +
+
+ + + +
+
+

intersection(Masks.Mask m1, Masks.Mask m2) → Masks.Mask

+
+

internal

+# +
+
+
+ +Perform a bitwise AND operation on two masks + +
+
+ + + +
+
+

difference(Masks.Mask m1, Masks.Mask m2) → Masks.Mask

+
+

internal

+# +
+
+
+ +Perform a bitwise difference operation on two masks (m1 - m2) + +
+
+ + + +
+
+

symmetricDifference(Masks.Mask m1, Masks.Mask m2) → Masks.Mask

+
+

internal

+# +
+
+
+ +Returns the symmetric difference (∆) of two masks, also known as disjunctive union or exclusive OR (XOR) + +
+
+ + + +
+ +## `EnumerableMapExtended` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/structs/EnumerableMapExtended.sol"; +``` + +Library for managing an enumerable variant of Solidity's +[`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types) +type for non-value types as keys. + +Maps have the following properties: + +- Entries are added, removed, and checked for existence in constant time +(O(1)). +- Entries are enumerated in O(n). No guarantees are made on the ordering. +- Map can be cleared (all entries removed) in O(n). + +```solidity +contract Example { + // Add the library methods + using EnumerableMapExtended for EnumerableMapExtended.BytesToUintMap; + + // Declare a set state variable + EnumerableMapExtended.BytesToUintMap private myMap; +} +``` + +The following map types are supported: + +- `bytes -> uint256` (`BytesToUintMap`) +- `string -> string` (`StringToStringMap`) + + +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See [ethereum/solidity#11843](https://github.com/ethereum/solidity/pull/11843) for more info. + +In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableMap. + + +Extensions of openzeppelin/contracts/utils/struct/EnumerableMap.sol. + + +
+

Functions

+
+- [set(map, key, value)](#EnumerableMapExtended-set-struct-EnumerableMapExtended-BytesToUintMap-bytes-uint256-) +- [remove(map, key)](#EnumerableMapExtended-remove-struct-EnumerableMapExtended-BytesToUintMap-bytes-) +- [clear(map)](#EnumerableMapExtended-clear-struct-EnumerableMapExtended-BytesToUintMap-) +- [contains(map, key)](#EnumerableMapExtended-contains-struct-EnumerableMapExtended-BytesToUintMap-bytes-) +- [length(map)](#EnumerableMapExtended-length-struct-EnumerableMapExtended-BytesToUintMap-) +- [at(map, index)](#EnumerableMapExtended-at-struct-EnumerableMapExtended-BytesToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMapExtended-tryGet-struct-EnumerableMapExtended-BytesToUintMap-bytes-) +- [get(map, key)](#EnumerableMapExtended-get-struct-EnumerableMapExtended-BytesToUintMap-bytes-) +- [keys(map)](#EnumerableMapExtended-keys-struct-EnumerableMapExtended-BytesToUintMap-) +- [keys(map, start, end)](#EnumerableMapExtended-keys-struct-EnumerableMapExtended-BytesToUintMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMapExtended-set-struct-EnumerableMapExtended-StringToStringMap-string-string-) +- [remove(map, key)](#EnumerableMapExtended-remove-struct-EnumerableMapExtended-StringToStringMap-string-) +- [clear(map)](#EnumerableMapExtended-clear-struct-EnumerableMapExtended-StringToStringMap-) +- [contains(map, key)](#EnumerableMapExtended-contains-struct-EnumerableMapExtended-StringToStringMap-string-) +- [length(map)](#EnumerableMapExtended-length-struct-EnumerableMapExtended-StringToStringMap-) +- [at(map, index)](#EnumerableMapExtended-at-struct-EnumerableMapExtended-StringToStringMap-uint256-) +- [tryGet(map, key)](#EnumerableMapExtended-tryGet-struct-EnumerableMapExtended-StringToStringMap-string-) +- [get(map, key)](#EnumerableMapExtended-get-struct-EnumerableMapExtended-StringToStringMap-string-) +- [keys(map)](#EnumerableMapExtended-keys-struct-EnumerableMapExtended-StringToStringMap-) +- [keys(map, start, end)](#EnumerableMapExtended-keys-struct-EnumerableMapExtended-StringToStringMap-uint256-uint256-) +
+
+ +
+

Errors

+
+- [EnumerableMapNonexistentBytesKey(key)](#EnumerableMapExtended-EnumerableMapNonexistentBytesKey-bytes-) +- [EnumerableMapNonexistentStringKey(key)](#EnumerableMapExtended-EnumerableMapNonexistentStringKey-string-) +
+
+ + + +
+
+

set(struct EnumerableMapExtended.BytesToUintMap map, bytes key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMapExtended.BytesToUintMap map, bytes key) → bool

+
+

internal

+# +
+
+
+ +Removes a key-value pair from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMapExtended.BytesToUintMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMapExtended.BytesToUintMap map, bytes key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMapExtended.BytesToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of key-value pairs in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMapExtended.BytesToUintMap map, uint256 index) → bytes key, uint256 value

+
+

internal

+# +
+
+
+ +Returns the key-value pair stored at position `index` in the map. O(1). + +Note that there are no guarantees on the ordering of entries inside the +array, and it may change when more entries are added or removed. + +Requirements: + +- `index` must be strictly less than [`EnumerableMapExtended.length`](#EnumerableMapExtended-length-struct-EnumerableMapExtended-StringToStringMap-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMapExtended.BytesToUintMap map, bytes key) → bool exists, uint256 value

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMapExtended.BytesToUintMap map, bytes key) → uint256 value

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMapExtended.BytesToUintMap map) → bytes[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMapExtended.BytesToUintMap map, uint256 start, uint256 end) → bytes[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMapExtended.StringToStringMap map, string key, string value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMapExtended.StringToStringMap map, string key) → bool

+
+

internal

+# +
+
+
+ +Removes a key-value pair from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMapExtended.StringToStringMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMapExtended.StringToStringMap map, string key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMapExtended.StringToStringMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of key-value pairs in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMapExtended.StringToStringMap map, uint256 index) → string key, string value

+
+

internal

+# +
+
+
+ +Returns the key-value pair stored at position `index` in the map. O(1). + +Note that there are no guarantees on the ordering of entries inside the +array, and it may change when more entries are added or removed. + +Requirements: + +- `index` must be strictly less than [`EnumerableMapExtended.length`](#EnumerableMapExtended-length-struct-EnumerableMapExtended-StringToStringMap-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMapExtended.StringToStringMap map, string key) → bool exists, string value

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMapExtended.StringToStringMap map, string key) → string value

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMapExtended.StringToStringMap map) → string[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMapExtended.StringToStringMap map, uint256 start, uint256 end) → string[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

EnumerableMapNonexistentBytesKey(bytes key)

+
+

error

+# +
+
+
+ +Query for a nonexistent map key. + +
+
+ + + +
+
+

EnumerableMapNonexistentStringKey(string key)

+
+

error

+# +
+
+
+ +Query for a nonexistent map key. + +
+
+ + + +
+ +## `EnumerableSetExtended` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/structs/EnumerableSetExtended.sol"; +``` + +Library for managing +[sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) of non-value +types. + +Sets have the following properties: + +- Elements are added, removed, and checked for existence in constant time +(O(1)). +- Elements are enumerated in O(n). No guarantees are made on the ordering. +- Set can be cleared (all elements removed) in O(n). + +```solidity +contract Example { + // Add the library methods + using EnumerableSetExtended for EnumerableSetExtended.StringSet; + + // Declare a set state variable + EnumerableSetExtended.StringSet private mySet; +} +``` + +Sets of type `string` (`StringSet`), `bytes` (`BytesSet`) and +`bytes32[2]` (`Bytes32x2Set`) are supported. + + +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See [ethereum/solidity#11843](https://github.com/ethereum/solidity/pull/11843) for more info. + +In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableSet. + + +This is an extension of openzeppelin/contracts/utils/struct/EnumerableSet.sol. + + +
+

Functions

+
+- [add(self, value)](#EnumerableSetExtended-add-struct-EnumerableSetExtended-Bytes32x2Set-bytes32-2--) +- [remove(self, value)](#EnumerableSetExtended-remove-struct-EnumerableSetExtended-Bytes32x2Set-bytes32-2--) +- [clear(self)](#EnumerableSetExtended-clear-struct-EnumerableSetExtended-Bytes32x2Set-) +- [contains(self, value)](#EnumerableSetExtended-contains-struct-EnumerableSetExtended-Bytes32x2Set-bytes32-2--) +- [length(self)](#EnumerableSetExtended-length-struct-EnumerableSetExtended-Bytes32x2Set-) +- [at(self, index)](#EnumerableSetExtended-at-struct-EnumerableSetExtended-Bytes32x2Set-uint256-) +- [values(self)](#EnumerableSetExtended-values-struct-EnumerableSetExtended-Bytes32x2Set-) +- [values(set, start, end)](#EnumerableSetExtended-values-struct-EnumerableSetExtended-Bytes32x2Set-uint256-uint256-) +
+
+ + + +
+
+

add(struct EnumerableSetExtended.Bytes32x2Set self, bytes32[2] value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSetExtended.Bytes32x2Set self, bytes32[2] value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

clear(struct EnumerableSetExtended.Bytes32x2Set self)

+
+

internal

+# +
+
+
+ +Removes all the values from a set. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableSetExtended.Bytes32x2Set self, bytes32[2] value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSetExtended.Bytes32x2Set self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values on the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSetExtended.Bytes32x2Set self, uint256 index) → bytes32[2]

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`EnumerableMapExtended.length`](#EnumerableMapExtended-length-struct-EnumerableMapExtended-StringToStringMap-). + +
+
+ + + +
+
+

values(struct EnumerableSetExtended.Bytes32x2Set self) → bytes32[2][]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

values(struct EnumerableSetExtended.Bytes32x2Set set, uint256 start, uint256 end) → bytes32[2][]

+
+

internal

+# +
+
+
+ +Return a slice of the set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
diff --git a/docs/content/community-contracts/api/utils/cryptography.mdx b/docs/content/community-contracts/api/utils/cryptography.mdx new file mode 100644 index 00000000..b2365ff0 --- /dev/null +++ b/docs/content/community-contracts/api/utils/cryptography.mdx @@ -0,0 +1,936 @@ +--- +title: "Cryptography" +description: "Smart contract cryptography utilities and implementations" +--- + +A collection of contracts and libraries that implement various signature validation schemes and cryptographic primitives. These utilities enable secure authentication, multisignature operations, and advanced cryptographic operations in smart contracts. + +* [`ZKEmailUtils`](#ZKEmailUtils): Library for ZKEmail signature validation utilities, enabling email-based authentication through zero-knowledge proofs. +* [`WebAuthn`](#WebAuthn): Library for verifying WebAuthn Authentication Assertions. +* [`DKIMRegistry`](#DKIMRegistry): Implementation of [ERC-7969](https://eips.ethereum.org/EIPS/eip-7969) to enable onchain verification of DomainKeys Identified Mail (DKIM) signatures. +* [`SignerZKEmail`](#SignerZKEmail): Implementation of an [AbstractSigner](https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#AbstractSigner) that enables email-based authentication through zero-knowledge proofs. +* [`SignerWebAuthn`](#SignerWebAuthn): Implementation of [SignerP256](https://docs.openzeppelin.com/contracts/5.x/api/utils/cryptography#SignerP256) that supports WebAuthn authentication assertions. +* [`ERC7913ZKEmailVerifier`](#ERC7913ZKEmailVerifier), [`ERC7913WebAuthnVerifier`](#ERC7913WebAuthnVerifier): Ready to use ERC-7913 signature verifiers for ZKEmail and WebAuthn. + +## Utils + +[`ZKEmailUtils`](#ZKEmailUtils) + +[`WebAuthn`](#WebAuthn) + +[`DKIMRegistry`](#DKIMRegistry) + +## Abstract Signers + +[`SignerZKEmail`](#SignerZKEmail) + +[`SignerWebAuthn`](#SignerWebAuthn) + +## Verifiers + +[`ERC7913ZKEmailVerifier`](#ERC7913ZKEmailVerifier) + +[`ERC7913WebAuthnVerifier`](#ERC7913WebAuthnVerifier) + + + +
+ +## `DKIMRegistry` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/DKIMRegistry.sol"; +``` + +Implementation of the [ERC-7969](https://eips.ethereum.org/EIPS/eip-7969) interface for registering +and validating DomainKeys Identified Mail (DKIM) public key hashes onchain. + +This contract provides a standard way to register and validate DKIM public key hashes, enabling +email-based account abstraction and secure account recovery mechanisms. Domain owners can register +their DKIM public key hashes and third parties can verify their validity. + +The contract stores mappings of domain hashes to DKIM public key hashes, where: + +* Domain hash: keccak256 hash of the lowercase domain name +* Key hash: keccak256 hash of the DKIM public key + +Example of usage: + +```solidity +contract MyDKIMRegistry is DKIMRegistry, Ownable { + function setKeyHash(bytes32 domainHash, bytes32 keyHash) public onlyOwner { + _setKeyHash(domainHash, keyHash); + } + + function setKeyHashes(bytes32 domainHash, bytes32[] memory keyHashes) public onlyOwner { + _setKeyHashes(domainHash, keyHashes); + } + + function revokeKeyHash(bytes32 domainHash, bytes32 keyHash) public onlyOwner { + _revokeKeyHash(domainHash, keyHash); + } +} +``` + +
+

Functions

+
+- [isKeyHashValid(domainHash, keyHash)](#DKIMRegistry-isKeyHashValid-bytes32-bytes32-) +- [_setKeyHash(domainHash, keyHash)](#DKIMRegistry-_setKeyHash-bytes32-bytes32-) +- [_setKeyHashes(domainHash, keyHashes)](#DKIMRegistry-_setKeyHashes-bytes32-bytes32---) +- [_revokeKeyHash(domainHash, keyHash)](#DKIMRegistry-_revokeKeyHash-bytes32-bytes32-) +#### IDKIMRegistry [!toc] +
+
+ +
+

Events

+
+#### IDKIMRegistry [!toc] +- [KeyHashRegistered(domainHash, keyHash)](#IDKIMRegistry-KeyHashRegistered-bytes32-bytes32-) +- [KeyHashRevoked(domainHash)](#IDKIMRegistry-KeyHashRevoked-bytes32-) +
+
+ + + +
+
+

isKeyHashValid(bytes32 domainHash, bytes32 keyHash) → bool

+
+

public

+# +
+
+
+ +Returns whether a DKIM key hash is valid for a given domain. + +
+
+ + + +
+
+

_setKeyHash(bytes32 domainHash, bytes32 keyHash)

+
+

internal

+# +
+
+
+ +Sets a DKIM key hash as valid for a domain. Internal version without access control. + +Emits a [`IDKIMRegistry.KeyHashRegistered`](../interfaces#IDKIMRegistry-KeyHashRegistered-bytes32-bytes32-) event. + + +This function does not validate that keyHash is non-zero. Consider adding +validation in derived contracts if needed. + + +
+
+ + + +
+
+

_setKeyHashes(bytes32 domainHash, bytes32[] keyHashes)

+
+

internal

+# +
+
+
+ +Sets multiple DKIM key hashes as valid for a domain in a single transaction. +Internal version without access control. + +Emits a [`IDKIMRegistry.KeyHashRegistered`](../interfaces#IDKIMRegistry-KeyHashRegistered-bytes32-bytes32-) event for each key hash. + + +This function does not validate that the keyHashes array is non-empty. +Consider adding validation in derived contracts if needed. + + +
+
+ + + +
+
+

_revokeKeyHash(bytes32 domainHash, bytes32 keyHash)

+
+

internal

+# +
+
+
+ +Revokes a DKIM key hash for a domain, making it invalid. +Internal version without access control. + +Emits a [`IDKIMRegistry.KeyHashRevoked`](../interfaces#IDKIMRegistry-KeyHashRevoked-bytes32-) event. + +
+
+ + + +
+ +## `WebAuthn` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/WebAuthn.sol"; +``` + +Library for verifying WebAuthn Authentication Assertions. + +WebAuthn enables strong authentication for smart contracts using +[P256](https://docs.openzeppelin.com/contracts/5.x/api/utils#P256) +as an alternative to traditional secp256k1 ECDSA signatures. This library verifies +signatures generated during WebAuthn authentication ceremonies as specified in the +[WebAuthn Level 2 standard](https://www.w3.org/TR/webauthn-2/). + +For blockchain use cases, the following WebAuthn validations are intentionally omitted: + +* Origin validation: Origin verification in `clientDataJSON` is omitted as blockchain + contexts rely on authenticator and dapp frontend enforcement. Standard authenticators + implement proper origin validation. +* RP ID hash validation: Verification of `rpIdHash` in authenticatorData against expected + RP ID hash is omitted. This is typically handled by platform-level security measures. + Including an expiry timestamp in signed data is recommended for enhanced security. +* Signature counter: Verification of signature counter increments is omitted. While + useful for detecting credential cloning, on-chain operations typically include nonce + protection, making this check redundant. +* Extension outputs: Extension output value verification is omitted as these are not + essential for core authentication security in blockchain applications. +* Attestation: Attestation object verification is omitted as this implementation + focuses on authentication (`webauthn.get`) rather than registration ceremonies. + +Inspired by: + +* [daimo-eth implementation](https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol) +* [base implementation](https://github.com/base/webauthn-sol/blob/main/src/WebAuthn.sol) + +
+

Functions

+
+- [verify(challenge, auth, qx, qy)](#WebAuthn-verify-bytes-struct-WebAuthn-WebAuthnAuth-bytes32-bytes32-) +- [verify(challenge, auth, qx, qy, requireUV)](#WebAuthn-verify-bytes-struct-WebAuthn-WebAuthnAuth-bytes32-bytes32-bool-) +- [tryDecodeAuth(input)](#WebAuthn-tryDecodeAuth-bytes-) +
+
+ + + +
+
+

verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy) → bool

+
+

internal

+# +
+
+
+ +Performs standard verification of a WebAuthn Authentication Assertion. + +
+
+ + + +
+
+

verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy, bool requireUV) → bool

+
+

internal

+# +
+
+
+ +Performs verification of a WebAuthn Authentication Assertion. This variants allow the caller to select +whether of not to require the UV flag (step 17). + +Verifies: + +1. Type is "webauthn.get" (see [`WebAuthn._validateExpectedTypeHash`](#WebAuthn-_validateExpectedTypeHash-string-uint256-)) +2. Challenge matches the expected value (see [`WebAuthn._validateChallenge`](#WebAuthn-_validateChallenge-string-uint256-bytes-)) +3. Cryptographic signature is valid for the given public key +4. confirming physical user presence during authentication +5. (if `requireUV` is true) confirming stronger user authentication (biometrics/PIN) +6. Backup Eligibility (`BE`) and Backup State (BS) bits relationship is valid + +
+
+ + + +
+
+

tryDecodeAuth(bytes input) → bool success, struct WebAuthn.WebAuthnAuth auth

+
+

internal

+# +
+
+
+ +Verifies that calldata bytes (`input`) represents a valid `WebAuthnAuth` object. If encoding is valid, +returns true and the calldata view at the object. Otherwise, returns false and an invalid calldata object. + + +The returned `auth` object should not be accessed if `success` is false. Trying to access the data may +cause revert/panic. + + +
+
+ + + +
+ +## `ZKEmailUtils` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/ZKEmailUtils.sol"; +``` + +Library for [ZKEmail](https://docs.zk.email) Groth16 proof validation utilities. + +ZKEmail is a protocol that enables email-based authentication and authorization for smart contracts +using zero-knowledge proofs. It allows users to prove ownership of an email address without revealing +the email content or private keys. + +The validation process involves several key components: + +* A [DKIMRegistry](https://docs.zk.email/architecture/dkim-verification) (DomainKeys Identified Mail) verification +mechanism to ensure the email was sent from a valid domain. Defined by an `IDKIMRegistry` interface. +* A [command template](https://docs.zk.email/email-tx-builder/architecture/command-templates) validation +mechanism to ensure the email command matches the expected format and parameters. +* A [zero-knowledge proof](https://docs.zk.email/architecture/zk-proofs#how-zk-email-uses-zero-knowledge-proofs) verification +mechanism to ensure the email was actually sent and received without revealing its contents. Defined by an `IGroth16Verifier` interface. + +
+

Functions

+
+- [isValidZKEmail(emailProof, dkimregistry, groth16Verifier, hash)](#ZKEmailUtils-isValidZKEmail-struct-EmailProof-contract-IDKIMRegistry-contract-IGroth16Verifier-bytes32-) +- [isValidZKEmail(emailProof, dkimregistry, groth16Verifier, template, templateParams)](#ZKEmailUtils-isValidZKEmail-struct-EmailProof-contract-IDKIMRegistry-contract-IGroth16Verifier-string---bytes---) +- [isValidZKEmail(emailProof, dkimregistry, groth16Verifier, template, templateParams, stringCase)](#ZKEmailUtils-isValidZKEmail-struct-EmailProof-contract-IDKIMRegistry-contract-IGroth16Verifier-string---bytes---enum-ZKEmailUtils-Case-) +- [tryDecodeEmailProof(input)](#ZKEmailUtils-tryDecodeEmailProof-bytes-) +- [toPubSignals(proof)](#ZKEmailUtils-toPubSignals-struct-EmailProof-) +
+
+ + + +
+
+

isValidZKEmail(struct EmailProof emailProof, contract IDKIMRegistry dkimregistry, contract IGroth16Verifier groth16Verifier, bytes32 hash) → enum ZKEmailUtils.EmailProofError

+
+

internal

+# +
+
+
+ +Variant of [`ZKEmailUtils.isValidZKEmail`](#ZKEmailUtils-isValidZKEmail-struct-EmailProof-contract-IDKIMRegistry-contract-IGroth16Verifier-string---bytes---enum-ZKEmailUtils-Case-) that validates the `["signHash", "../access#AccessManagerLight-ADMIN_ROLE-uint8"]` command template. + +
+
+ + + +
+
+

isValidZKEmail(struct EmailProof emailProof, contract IDKIMRegistry dkimregistry, contract IGroth16Verifier groth16Verifier, string[] template, bytes[] templateParams) → enum ZKEmailUtils.EmailProofError

+
+

internal

+# +
+
+
+ +Validates a ZKEmail proof against a command template. + +This function takes an email proof, a DKIM registry contract, and a verifier contract +as inputs. It performs several validation checks and returns an [`ZKEmailUtils.EmailProofError`](#ZKEmailUtils-EmailProofError) indicating the result. +Returns `EmailProofError.NoError` if all validations pass, or a specific [`ZKEmailUtils.EmailProofError`](#ZKEmailUtils-EmailProofError) indicating +which validation check failed. + + +Attempts to validate the command for all possible string [`ZKEmailUtils.Case`](#ZKEmailUtils-Case) values. + + +
+
+ + + +
+
+

isValidZKEmail(struct EmailProof emailProof, contract IDKIMRegistry dkimregistry, contract IGroth16Verifier groth16Verifier, string[] template, bytes[] templateParams, enum ZKEmailUtils.Case stringCase) → enum ZKEmailUtils.EmailProofError

+
+

internal

+# +
+
+
+ +Variant of [`ZKEmailUtils.isValidZKEmail`](#ZKEmailUtils-isValidZKEmail-struct-EmailProof-contract-IDKIMRegistry-contract-IGroth16Verifier-string---bytes---enum-ZKEmailUtils-Case-) that validates a template with a specific string [`ZKEmailUtils.Case`](#ZKEmailUtils-Case). + +Useful for templates with Ethereum address matchers (i.e. ``ethAddr``), which are case-sensitive (e.g., `["someCommand", "../access#AccessManagerLight-_groups-mapping-address----Masks-Mask-"]`). + +
+
+ + + +
+
+

tryDecodeEmailProof(bytes input) → bool success, struct EmailProof emailProof

+
+

internal

+# +
+
+
+ +Verifies that calldata bytes (`input`) represents a valid `EmailProof` object. If encoding is valid, +returns true and the calldata view at the object. Otherwise, returns false and an invalid calldata object. + + +The returned `emailProof` object should not be accessed if `success` is false. Trying to access the data may +cause revert/panic. + + +
+
+ + + +
+
+

toPubSignals(struct EmailProof proof) → uint256[34] pubSignals

+
+

internal

+# +
+
+
+ +Builds the expected public signals array for the Groth16 verifier from the given EmailProof. + +Packs the domain, public key hash, email nullifier, timestamp, masked command, account salt, and isCodeExist fields +into a uint256 array in the order expected by the verifier circuit. + +
+
+ + + +
+ +## `SignerWebAuthn` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerWebAuthn.sol"; +``` + +Implementation of `SignerP256` that supports WebAuthn authentication assertions. + +This contract enables signature validation using WebAuthn authentication assertions, +leveraging the P256 public key stored in the contract. It allows for both WebAuthn +and raw P256 signature validation, providing compatibility with both signature types. + +The signature is expected to be an abi-encoded [`WebAuthn.WebAuthnAuth`](#WebAuthn-WebAuthnAuth) struct. + +Example usage: + +```solidity +contract MyAccountWebAuthn is Account, SignerWebAuthn, Initializable { + function initialize(bytes32 qx, bytes32 qy) public initializer { + _setSigner(qx, qy); + } +} +``` + + +Failing to call [`ERC7579Signature._setSigner`](../account#ERC7579Signature-_setSigner-address-bytes-) either during construction (if used standalone) +or during initialization (if used as a clone) may leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [_rawSignatureValidation(hash, signature)](#SignerWebAuthn-_rawSignatureValidation-bytes32-bytes-) +#### SignerP256 [!toc] +- [_setSigner(qx, qy)](#SignerP256-_setSigner-bytes32-bytes32-) +- [signer()](#SignerP256-signer--) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+#### SignerP256 [!toc] +- [SignerP256InvalidPublicKey(qx, qy)](#SignerP256-SignerP256InvalidPublicKey-bytes32-bytes32-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Validates a raw signature using the WebAuthn authentication assertion. + +In case the signature can't be validated, it falls back to the +`SignerP256-_rawSignatureValidation` method for raw P256 signature validation by passing +the raw `r` and `s` values from the signature. + +
+
+ + + +
+ +## `SignerZKEmail` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/signers/SignerZKEmail.sol"; +``` + +Implementation of `AbstractSigner` using [ZKEmail](https://docs.zk.email) signatures. + +ZKEmail enables secure authentication and authorization through email messages, leveraging +DKIM signatures from a [`DKIMRegistry`](#DKIMRegistry) and zero-knowledge proofs enabled by a [`SignerZKEmail.verifier`](#SignerZKEmail-verifier--) +contract that ensures email authenticity without revealing sensitive information. The DKIM +registry is trusted to correctly update DKIM keys, but users can override this behaviour and +set their own keys. This contract implements the core functionality for validating email-based +signatures in smart contracts. + +Developers must set the following components during contract initialization: + +* [`SignerZKEmail.accountSalt`](#SignerZKEmail-accountSalt--) - A unique identifier derived from the user's email address and account code. +* [`DKIMRegistry`](#DKIMRegistry) - An instance of the DKIM registry contract for domain verification. +* [`SignerZKEmail.verifier`](#SignerZKEmail-verifier--) - An instance of the Groth16Verifier contract for zero-knowledge proof validation. + +Example of usage: + +```solidity +contract MyAccountZKEmail is Account, SignerZKEmail, Initializable { + function initialize( + bytes32 accountSalt, + IDKIMRegistry registry, + IGroth16Verifier groth16Verifier + ) public initializer { + // Will revert if the signer is already initialized + _setAccountSalt(accountSalt); + _setDKIMRegistry(registry); + _setVerifier(groth16Verifier); + } +} +``` + + +Failing to call [`SignerZKEmail._setAccountSalt`](#SignerZKEmail-_setAccountSalt-bytes32-), [`SignerZKEmail._setDKIMRegistry`](#SignerZKEmail-_setDKIMRegistry-contract-IDKIMRegistry-), and [`SignerZKEmail._setVerifier`](#SignerZKEmail-_setVerifier-contract-IGroth16Verifier-) +either during construction (if used standalone) or during initialization (if used as a clone) may +leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [accountSalt()](#SignerZKEmail-accountSalt--) +- [DKIMRegistry()](#SignerZKEmail-DKIMRegistry--) +- [verifier()](#SignerZKEmail-verifier--) +- [_setAccountSalt(accountSalt_)](#SignerZKEmail-_setAccountSalt-bytes32-) +- [_setDKIMRegistry(registry_)](#SignerZKEmail-_setDKIMRegistry-contract-IDKIMRegistry-) +- [_setVerifier(verifier_)](#SignerZKEmail-_setVerifier-contract-IGroth16Verifier-) +- [_rawSignatureValidation(hash, signature)](#SignerZKEmail-_rawSignatureValidation-bytes32-bytes-) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+- [InvalidEmailProof(err)](#SignerZKEmail-InvalidEmailProof-enum-ZKEmailUtils-EmailProofError-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

accountSalt() → bytes32

+
+

public

+# +
+
+
+ +Unique identifier for owner of this contract defined as a hash of an email address and an account code. + +An account code is a random integer in a finite scalar field of [BN254](https://neuromancer.sk/std/bn/bn254) curve. +It is a private randomness to derive a CREATE2 salt of the user's Ethereum address +from the email address, i.e., userEtherAddr := CREATE2(hash(userEmailAddr, accountCode)). + +The account salt is used for: + +* Privacy: Enables email address privacy on-chain so long as the randomly generated account code is not revealed + to an adversary. +* Security: Provides a unique identifier that cannot be easily guessed or brute-forced, as it's derived + from both the email address and a random account code. +* Deterministic Address Generation: Enables the creation of deterministic addresses based on email addresses, + allowing users to recover their accounts using only their email. + +
+
+ + + +
+
+

DKIMRegistry() → contract IDKIMRegistry

+
+

public

+# +
+
+
+ +An instance of the DKIM registry contract. +See [DKIM Verification](https://docs.zk.email/architecture/dkim-verification). + +
+
+ + + +
+
+

verifier() → contract IGroth16Verifier

+
+

public

+# +
+
+
+ +An instance of the Groth16Verifier contract. +See [ZK Proofs](https://docs.zk.email/architecture/zk-proofs#how-zk-email-uses-zero-knowledge-proofs). + +
+
+ + + +
+
+

_setAccountSalt(bytes32 accountSalt_)

+
+

internal

+# +
+
+
+ +Set the [`SignerZKEmail.accountSalt`](#SignerZKEmail-accountSalt--). + +
+
+ + + +
+
+

_setDKIMRegistry(contract IDKIMRegistry registry_)

+
+

internal

+# +
+
+
+ +Set the [`DKIMRegistry`](#DKIMRegistry) contract address. + +
+
+ + + +
+
+

_setVerifier(contract IGroth16Verifier verifier_)

+
+

internal

+# +
+
+
+ +Set the [`SignerZKEmail.verifier`](#SignerZKEmail-verifier--) contract address. + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +See `AbstractSigner-_rawSignatureValidation`. Validates a raw signature by: + +1. Decoding the email proof from the signature +2. Validating the account salt matches +3. Verifying the email proof using ZKEmail utilities + +
+
+ + + +
+
+

InvalidEmailProof(enum ZKEmailUtils.EmailProofError err)

+
+

error

+# +
+
+
+ +Proof verification error. + +
+
+ + + +
+ +## `ERC7913WebAuthnVerifier` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/verifiers/ERC7913WebAuthnVerifier.sol"; +``` + +ERC-7913 signature verifier that supports WebAuthn authentication assertions. + +This verifier enables the validation of WebAuthn signatures using P256 public keys. +The key is expected to be a 64-byte concatenation of the P256 public key coordinates (qx || qy). +The signature is expected to be an abi-encoded [`WebAuthn.WebAuthnAuth`](#WebAuthn-WebAuthnAuth) struct. + +Uses `WebAuthn-verifyMinimal` for signature verification, which performs the essential +WebAuthn checks: type validation, challenge matching, and cryptographic signature verification. + + +Wallets that may require default P256 validation may install a P256 verifier separately. + + +
+

Functions

+
+- [verify(key, hash, signature)](#ERC7913WebAuthnVerifier-verify-bytes-bytes32-bytes-) +#### IERC7913SignatureVerifier [!toc] +
+
+ + + +
+
+

verify(bytes key, bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +Verifies `signature` as a valid signature of `hash` by `key`. + +MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. +SHOULD return 0xffffffff or revert if the signature is not valid. +SHOULD return 0xffffffff or revert if the key is empty + +
+
+ + + +
+ +## `ERC7913ZKEmailVerifier` + + + + + +
+ +```solidity +import "@openzeppelin/community-contracts/utils/cryptography/verifiers/ERC7913ZKEmailVerifier.sol"; +``` + +ERC-7913 signature verifier that supports ZKEmail accounts. + +This contract verifies signatures produced through ZKEmail's zero-knowledge +proofs which allows users to authenticate using their email addresses. + +The key decoding logic is customizable: users may override the [`ERC7913ZKEmailVerifier._decodeKey`](#ERC7913ZKEmailVerifier-_decodeKey-bytes-) function +to enforce restrictions or validation on the decoded values (e.g., requiring a specific +verifier or registry). To remain compliant with ERC-7913's statelessness, +it is recommended to enforce such restrictions using immutable variables only. + +Example of overriding _decodeKey to enforce a specific verifier, registry: + +```solidity + function _decodeKey(bytes calldata key) internal view override returns ( + IDKIMRegistry registry, + bytes32 accountSalt, + IGroth16Verifier verifier + ) { + (registry, accountSalt, verifier) = super._decodeKey(key); + require(verifier == _verifier, "Invalid verifier"); + require(registry == _registry, "Invalid registry"); + return (registry, accountSalt, verifier); + } +``` + +
+

Functions

+
+- [verify(key, hash, signature)](#ERC7913ZKEmailVerifier-verify-bytes-bytes32-bytes-) +- [_decodeKey(key)](#ERC7913ZKEmailVerifier-_decodeKey-bytes-) +#### IERC7913SignatureVerifier [!toc] +
+
+ + + +
+
+

verify(bytes key, bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +Verifies a zero-knowledge proof of an email signature validated by a [`DKIMRegistry`](#DKIMRegistry) contract. + +The key format is ABI-encoded (IDKIMRegistry, bytes32, IGroth16Verifier) where: + +* IDKIMRegistry: The registry contract that validates DKIM public key hashes +* bytes32: The account salt that uniquely identifies the user's email address +* IGroth16Verifier: The verifier contract instance for ZK proof verification. + +See [`ERC7913ZKEmailVerifier._decodeKey`](#ERC7913ZKEmailVerifier-_decodeKey-bytes-) for the key encoding format. + +The signature is an ABI-encoded [`ZKEmailUtils.EmailProofError`](#ZKEmailUtils-EmailProofError) struct containing +the proof details. + +Signature encoding: + +```solidity +bytes memory signature = abi.encode(EmailProof({ + domainName: "example.com", // The domain name of the email sender + publicKeyHash: bytes32(0x...), // Hash of the DKIM public key used to sign the email + timestamp: block.timestamp, // When the email was sent + maskedCommand: "signHash 12345...", // The command being executed, with sensitive data masked + emailNullifier: bytes32(0x...), // Unique identifier for the email to prevent replay attacks + accountSalt: bytes32(0x...), // Unique identifier derived from email and account code + isCodeExist: true, // Whether the account code exists in the proof + proof: bytes(0x...) // The zero-knowledge proof verifying the email's authenticity +})); +``` + +
+
+ + + +
+
+

_decodeKey(bytes key) → contract IDKIMRegistry registry, bytes32 accountSalt, contract IGroth16Verifier verifier

+
+

internal

+# +
+
+
+ +Decodes the key into its components. + +```solidity +bytes memory key = abi.encode(registry, accountSalt, verifier); +``` + +
+
diff --git a/docs/content/community-contracts/crosschain.mdx b/docs/content/community-contracts/crosschain.mdx new file mode 100644 index 00000000..804aa1d0 --- /dev/null +++ b/docs/content/community-contracts/crosschain.mdx @@ -0,0 +1,167 @@ +--- +title: Cross-chain messaging +--- + +Developers building contracts may require cross-chain functionality. To accomplish this, multiple protocols have implemented their own ways to process operations across chains. + +The variety of these bridges is outlined in [@norswap](https://x.com/norswap)'s [Cross-Chain Interoperability Report](https://github.com/0xFableOrg/xchain/blob/master/README.md) that proposes [a taxonomy of 7 bridge categories](https://github.com/0xFableOrg/xchain/blob/master/README.md#bridge-taxonomy). This diversity makes it difficult for developers to design cross-chain applications given the lack of portability. + +This guide will teach you how to follow [ERC-7786](https://eips.ethereum.org/EIPS/eip-7786) to establish messaging gateways across chains regardless of the underlying bridge. Developers can implement gateway contracts that process cross-chain messages and connect any crosschain protocol they want (or implement themselves). + +## ERC-7786 Gateway + +To address the lack of composability in a simple and unopinionated way, ERC-7786 proposes a standard for implementing gateways that relay messages to other chains. This generalized approach is expressive enough to enable new types of applications and can be adapted to any bridge taxonomy or specific bridge interface with standardized attributes. + +### Message passing overview + +The ERC defines a source and a destination gateway. Both are contracts that implement a protocol to send a message and process its reception respectively. These two processes are identified explicitly by the ERC-7786 specification since they define the minimal requirements for both gateways. + +* On the ***source chain***, the contract implements a standard [`sendMessage`](/community-contracts/api/crosschain#AxelarGatewaySource-sendMessage-bytes-bytes-bytes---) function and emits a [`MessageSent`](/community-contracts/api/crosschain#AxelarGatewaySource-MessageSent-bytes32-string-string-bytes-bytes---) event to signal that the message should be relayed by the underlying protocol. +* On the ***destination chain***, the gateway receives the message and passes it to the receiver contract by calling the [`receiveMessage`](/community-contracts/api/crosschain#ERC7786Receiver-receiveMessage-bytes32-bytes-bytes-) function. + +Smart contract developers only need to worry about implementing the [IERC7786GatewaySource](/community-contracts/api/crosschain#IERC7786GatewaySource) interface to send a message on the source chain and the [IERC7786GatewaySource](/community-contracts/api/crosschain#IERC7786GatewaySource) and [IERC7786Receiver](/community-contracts/api/crosschain#IERC7786Receiver) interface to receive such message on the destination chain. + +### Getting started with Axelar Network + +To start sending cross-chain messages, developers can get started with a duplex gateway powered by Axelar Network. This will allow a contract to send or receive cross-chain messages leveraging automated execution by Axelar relayers on the destination chain. + +./examples/crosschain/MyCustomAxelarGatewayDuplex.sol + +For more details of how the duplex gateway works, see [how to send and receive messages with the Axelar Network](#axelar-network) below + + +Developers can register supported chains and destination gateways using the [`registerChainEquivalence`](/community-contracts/api/crosschain#AxelarGatewayBase-registerChainEquivalence-string-string-) and [`registerRemoteGateway`](/community-contracts/api/crosschain#AxelarGatewayBase-registerRemoteGateway-string-string-) functions + + +## Cross-chain communication + +### Sending a message + +The interface for a source gateway is general enough that it allows wrapping a custom protocol to authenticate messages. Depending on the use case, developers can implement any offchain mechanism to read the standard [`MessageSent`](/community-contracts/api/crosschain#IERC7786GatewaySource-MessageSent-bytes32-string-string-bytes-bytes---) event and deliver it to the receiver on the destination chain. + +./examples/crosschain/MyERC7786GatewaySource.sol + + +The standard represents chains using [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) identifiers and accounts using [CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) identifiers for increased interoperability with non-EVM chains. Consider using the Strings library in the contracts library to process these identifiers. + + +### Receiving a message + +To successfully process a message on the destination chain, a destination gateway is required. Although ERC-7786 doesn’t define a standard interface for the destination gateway, it requires that it calls the `receiveMessage` upon message reception. + +Every cross-chain message protocol already offers a way to receive the message either through a canonical bridge or an intermediate contract. Developers can easily wrap the receiving contract into a gateway that calls the `receiveMessage` function as mandated by the ERC. + +To receive a message on a custom smart contract, OpenZeppelin Community Contracts provide an [ERC7786Receiver](/community-contracts/api/crosschain#ERC7786Receiver) implementation for developers to inherit. This way your contracts can receive a cross-chain message relayed through a known destination gateway gateway. + +./examples/crosschain/MyERC7786ReceiverContract.sol + +The standard receiving interface abstracts away the underlying protocol. This way, it is possible for a contract to send a message through an ERC-7786 compliant gateway (or through an adapter) and get it received on the destination chain without worrying about the protocol implementation details. + +### Axelar Network + +Aside from the [AxelarGatewayDuplex](/community-contracts/api/crosschain#AxelarGatewayDuplex), the library offers an implementation of the [IERC7786GatewaySource](/community-contracts/api/crosschain#IERC7786GatewaySource) interface called [AxelarGatewaySource](/community-contracts/api/crosschain#AxelarGatewaySource) that works as an adapter for sending messages in compliance with ERC-7786 + +The implementation takes a local gateway address that MUST correspond to [Axelar’s native gateways](https://axelarscan.io/resources/chains?type=evm) and has mechanisms to: + +* Keep track of equivalences between Axelar chain names and CAIP-2 identifiers +* Record a destination gateway per network using their CAIP-2 identifier + +The [AxelarGatewaySource](/community-contracts/api/crosschain#AxelarGatewaySource) implementation can be used out of the box + +./examples/crosschain/MyCustomAxelarGatewaySource.sol + +For a destination gateway, the library provides an adapter of the `AxelarExecutable` interface to receive messages and relay them to an [IERC7786Receiver](/community-contracts/api/crosschain#IERC7786Receiver). + +./examples/crosschain/MyCustomAxelarGatewayDestination.sol + +### Open Bridge + +The [ERC7786OpenBridge](/community-contracts/api/crosschain#ERC7786OpenBridge) is a special gateway that implements both [IERC7786GatewaySource](/community-contracts/api/crosschain#IERC7786GatewaySource) and [IERC7786Receiver](/community-contracts/api/crosschain#IERC7786Receiver) interfaces. It provides a way to send messages across multiple bridges simultaneously and ensures message delivery through a threshold-based confirmation system. + +The bridge maintains a list of known gateways and a confirmation threshold. When sending a message, it broadcasts to all registered gateways, and when receiving, it requires a minimum number of confirmations before executing the message. This approach increases reliability by ensuring messages are properly delivered and validated across multiple bridges. + +When sending a message, the bridge tracks the message IDs from each gateway to maintain a record of the message’s journey across different bridges: + +```solidity +function sendMessage( + string calldata destinationChain, + string memory receiver, + bytes memory payload, + bytes[] memory attributes +) public payable virtual whenNotPaused returns (bytes32 outboxId) { + + // ... Initialize variables and prepare payload ... + + // Post on all gateways + Outbox[] memory outbox = new Outbox[](_gateways.length()); + bool needsId = false; + for (uint256 i = 0; i < outbox.length; ++i) { + address gateway = _gateways.at(i); + // send message + bytes32 id = IERC7786GatewaySource(gateway).sendMessage( + destinationChain, + bridge, + wrappedPayload, + attributes + ); + // if ID, track it + if (id != bytes32(0)) { + outbox[i] = Outbox(gateway, id); + needsId = true; + } + } + + // ... Handle message tracking and return value ... +} +``` + +On the receiving end, the bridge implements a threshold-based confirmation system. Messages are only executed after receiving enough confirmations from the gateways, ensuring message validity and preventing double execution. The [`receiveMessage`](/community-contracts/api/crosschain#ERC7786OpenBridge-receiveMessage-string-string-string-bytes-bytes---) function handles this process: + +```solidity +function receiveMessage( + string calldata /**messageId**/, // gateway specific, empty or unique + string calldata sourceChain, // CAIP-2 chain identifier + string calldata sender, // CAIP-10 account address (does not include the chain identifier) + bytes calldata payload, + bytes[] calldata attributes +) public payable virtual whenNotPaused returns (bytes4) { + + // ... Validate message format and extract message ID ... + + // If call is first from a trusted gateway + if (_gateways.contains(msg.sender) && !tracker.receivedBy[msg.sender]) { + // Count number of time received + tracker.receivedBy[msg.sender] = true; + tracker.countReceived++; + emit Received(id, msg.sender); + } + + // if already executed, leave gracefully + if (tracker.executed) return IERC7786Receiver.receiveMessage.selector; + else if (tracker.executed) { + revert ERC7786OpenBridgeAlreadyExecuted(); + } + + // .. Validate sender and prepare payload for execution ... + + // If ready to execute, and not yet executed + if (tracker.countReceived >= getThreshold()) { + // prevent re-entry + tracker.executed = true; + + // ... Prepare execution context and validate state ... + bytes memory call = abi.encodeCall( + IERC7786Receiver.receiveMessage, + (uint256(id).toHexString(32), sourceChain, originalSender, unwrappedPayload, attributes) + ); + + (bool success, bytes memory returndata) = receiver.parseAddress().call(call); + + // ... Handle the result ... + } + + return IERC7786Receiver.receiveMessage.selector; +} +``` + +The bridge is designed to be configurable. As an `Ownable` contract, it allows the owner to manage the list of trusted gateways and adjust the confirmation threshold. The `_gateways` list and threshold are initially set during contract deployment using the [`_addGateway`](/community-contracts/api/crosschain#ERC7786OpenBridge-_addGateway-address-) and [`_setThreshold`](/community-contracts/api/crosschain#ERC7786OpenBridge-_setThreshold-uint8-) functions. The owner can update these settings as needed to adapt to changing requirements or add new gateways. diff --git a/docs/content/community-contracts/index.mdx b/docs/content/community-contracts/index.mdx new file mode 100644 index 00000000..6cc6cac0 --- /dev/null +++ b/docs/content/community-contracts/index.mdx @@ -0,0 +1,45 @@ +--- +title: Community Contracts +--- + +**A community-driven extension of our [Solidity library](https://docs.openzeppelin.com/contracts)**: the gold-standard of smart contract development. This library includes: + +* Extensions and modules compatible with contracts in the original package +* Alternative implementation of interfaces defined in the original package +* Contracts with third-party integrations +* Contracts built by community members, that align with OpenZeppelin offerings +* General prototypes and experiments + +Code is provided by the OpenZeppelin Contracts team, as well as by community contributors, for other developers to review, discuss, iterate on, and potentially use. + +## Overview + +### Installation + +Given this extension is intended for more experimental use cases and therefore the development process is more flexible. For such reason, the library can only be installed with Foundry using gitmodules. + +#### Foundry (git) + +```console +$ forge install OpenZeppelin/openzeppelin-community-contracts +``` + + +Make sure to add `@openzeppelin/community-contracts/=lib/openzeppelin-community-contracts/contracts/` in `remappings.txt.` + + +### Usage + +Once installed, you can use the contracts in the library by importing them: + +./examples/MyStablecoinAllowlist.sol + +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 + +Contracts in the community library are provided as is, with no particular guarantees. Given changes in this repository are more frequent, the code is not formally audited and not covered by the [bug bounty program on Immunefi](https://www.immunefi.com/bounty/openzeppelin). + +Similarly, the code has no backward compatibility guarantees. + +We kindly ask to report any issue directly to our security [contact](mailto:security@openzeppelin.org). The team will do its best to assist and mitigate any potential misuses of the library. However, keep in mind the flexibility assumed for this repository may relax our assessment. diff --git a/docs/content/community-contracts/paymasters.mdx b/docs/content/community-contracts/paymasters.mdx new file mode 100644 index 00000000..f1e1c73a --- /dev/null +++ b/docs/content/community-contracts/paymasters.mdx @@ -0,0 +1,508 @@ +--- +title: 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 [`validatePaymasterUserOp`](/community-contracts/api/utils/cryptography#PaymasterCore-validatePaymasterUserOp). The EntryPoint will use this field to determine whether it is willing to pay for the user operation or not. + +## Signed Sponsorship + +The [`PaymasterSigner`](/community-contracts/api/account#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. + + +Learn more about [signers](/contracts/5.x/accounts#selecting-a-signer) to explore different approaches to user operation sponsorship via signatures. + + +./examples/account/paymaster/PaymasterECDSASigner.sol + + +Use [`ERC4337Utils`](/contracts/5.x/api/account#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: + +```typescript +// Fund the paymaster with ETH +await eoaClient.sendTransaction( + to: paymasterECDSASigner.address, + value: parseEther("0.01"), + data: encodeFunctionData( + abi: paymasterECDSASigner.abi, + functionName: "deposit", + args: [], + ), +); +``` + + +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: + +```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 +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: userOp.initCode, + callData: userOp.callData, + accountGasLimits: userOp.accountGasLimits, + preVerificationGas: userOp.preVerificationGas, + gasFees: userOp.gasFees, + paymasterVerificationGasLimit, + paymasterPostOpGasLimit, + validAfter, + validUntil, + , +); +``` + +The time window (`validAfter` and `validUntil`) prevents replay attacks and 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: + +```typescript +userOp.paymasterAndData = encodePacked( + ["address", "uint128", "uint128", "bytes"], + [ + paymasterECDSASigner.address, + paymasterVerificationGasLimit, + paymasterPostOpGasLimit, + encodePacked( + ["uint48", "uint48", "bytes"], + [validAfter, validUntil, paymasterSignature] + ), + ] +); +``` + + +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: + +```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: EntrypointV08Abi, + 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 [`PaymasterERC20`](/community-contracts/api/account#PaymasterERC20) allows users to pay for gas fees using ERC-20 tokens. Developers must implement an [`_fetchDetails`](/community-contracts/api/account#PaymasterERC20-_fetchDetails-struct-PackedUserOperation-bytes32-) to get the token price information from an oracle of their preference. + +```solidity +function _fetchDetails( + PackedUserOperation calldata userOp, + bytes32 userOpHash +) internal view override returns (uint256 validationData, IERC20 token, uint256 tokenPrice) + // 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 [Chainlink’s price feeds](https://docs.chain.link/data-feeds/using-data-feeds). By using their [`AggregatorV3Interface`](https://docs.chain.link/data-feeds/api-reference#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: + +```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); + + // 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 + } + + function _fetchDetails( + PackedUserOperation calldata userOp, + bytes32 /* userOpHash */ + ) internal view virtual override returns (uint256 validationData, IERC20 token, uint256 tokenPrice) { + (uint256 validationData_, uint256 price) = _fetchOracleDetails(userOp); + return ( + validationData_, + USDC, + price + ); + } + + function _fetchOracleDetails( + PackedUserOperation calldata /* userOp */ + ) + internal + view + virtual + returns (uint256 validationData, uint256 tokenPrice) + { + // ... + } +} +``` + + +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. + + +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 the price of a concrete ERC-20 and ETH. An example with USDC would be: + +1. Fetch the current `ETH/USD` and `USDC/USD` prices from their respective oracles. +2. Calculate the `USDC/ETH` exchange rate using the formula: `USDC/ETH = (USDC/USD) / (ETH/USD)`. This gives us how many USDC tokens are needed to buy 1 ETH + + +The price of the ERC-20 must be scaled by [`_tokenPriceDenominator`](/community-contracts/api/account#PaymasterERC20-_tokenPriceDenominator--). + + +Here’s how an implementation of `_fetchOracleDetails` would look like using this approach: + + +Use [`ERC4337Utils.combineValidationData`](/contracts/5.x/api/account#ERC4337Utils-combineValidationData-uint256-uint256-) to merge two `validationData` values. + + +```solidity +// WARNING: Unaudited code. +// Consider performing a security review before going to production. + +using SafeCast for *; +using ERC4337Utils for *; + +function _fetchOracleDetails( + PackedUserOperation calldata /* userOp */ +) + internal + view + virtual + returns (uint256 validationData, uint256 tokenPrice) +{ + (uint256 ETHUSDValidationData, int256 ETHUSD) = _fetchPrice( + ETH_USD_ORACLE + ); + (uint256 USDCUSDValidationData, int256 USDCUSD) = _fetchPrice( + USDC_USD_ORACLE + ); + + if (ETHUSD <= 0 || USDCUSD <= 0) { + // No negative prices + return (ERC4337Utils.SIG_VALIDATION_FAILED, 0); + } + + // eth / usdc = (usdc / usd) / (eth / usd) = usdc * usd / eth * usd = usdc / eth + int256 scale = _tokenPriceDenominator().toInt256(); + int256 scaledUSDCUSD = USDCUSD * scale * (10 ** ETH_USD_ORACLE.decimals()).toInt256(); + int256 scaledUSDCETH = scaledUSDCUSD / (ETHUSD * (10 ** USDC_USD_ORACLE.decimals()).toInt256()); + + return ( + ETHUSDValidationData.combineValidationData(USDCUSDValidationData), + uint256(scaledUSDCETH) // Safe upcast + ); +} + +function _fetchPrice( + AggregatorV3Interface oracle +) internal view virtual returns (uint256 validationData, int256 price) { + ( + uint80 roundId, + int256 price_, + , + uint256 timestamp, + uint80 answeredInRound + ) = oracle.latestRoundData(); + if ( + price_ == 0 || // No data + 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, price_); +} +``` + + +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. + +```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. + +```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: EntrypointV08Abi, + address: entrypoint.address, + functionName: "handleOps", + args: [[signedUserOp], beneficiary.address], +); +``` + + +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 [`PaymasterERC20Guarantor`](/community-contracts/api/account#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 + + + +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: + +```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: + +```solidity +contract PaymasterUSDCGuaranteed is EIP712, PaymasterERC20Guarantor, Ownable { + + // 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,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(); + + // Check guarantor data (should be at least 22 bytes: 20 for address + 2 for sig length) + // If no guarantor specified, return early + if (paymasterData.length < 22 || guarantor == address(0)) { + return address(0); + } + + guarantor = address(bytes20(paymasterData[:20])); + uint16 guarantorSigLength = uint16(bytes2(paymasterData[20:22])); + + // Ensure the signature fits in the data + if (paymasterData.length < 22 + guarantorSigLength) { + return address(0); + } + + bytes calldata guarantorSignature = paymasterData[22:22 + guarantorSigLength]; + + // 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, + keccak256(bytes(userOp.paymasterData()[:20])) // Just the guarantor address part + ) + ); + } +} +``` + +With this implementation, a guarantor would sign a user operation to authorize backing it: + +```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: + +```typescript +const paymasterVerificationGasLimit = 150_000n; +const paymasterPostOpGasLimit = 300_000n; + +userOp.paymasterAndData = encodePacked( + ["address", "uint128", "uint128", "bytes"], + [ + paymasterUSDC.address, + paymasterVerificationGasLimit, + paymasterPostOpGasLimit, + encodePacked( + ["address", "bytes2", "bytes"], + [ + guarantorAddress, + toHex(guarantorSignature.replace("0x", "").length / 2, size: 2 ), + 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. + + +For production deployments, it’s often useful to implement a monitoring service that tracks paymaster usage, balances, and other metrics to ensure smooth operation. + diff --git a/docs/content/community-contracts/utilities.mdx b/docs/content/community-contracts/utilities.mdx new file mode 100644 index 00000000..6e53b8ee --- /dev/null +++ b/docs/content/community-contracts/utilities.mdx @@ -0,0 +1,56 @@ +--- +title: Utilities +--- + +Multiple libraries and general purpose utilities included in the community version of OpenZeppelin Contracts. These are only a set of utility contracts. For the full list, check out the [API Reference](/community-contracts/api/utils). + +## Cryptography + +### Validating Typed Data Signatures + +_For prior knowledge on how to validate signatures on-chain, check out the [OpenZeppelin Contracts documentation](/contracts/5.x/utilities#checking-signatures-on-chain)_ + +As opposed to validating plain-text messages, it is possible to let your users sign structured data (i.e. typed values) in a way that is still readable on their wallets. This is possible by implementing [`EIP712`](/contracts/5.x/api/utils/cryptography#EIP712), a standard way to encode structured data into a typed data hash. + +To start validating signed typed structures, just validate the [typed data hash](/contracts/5.x/api/utils/cryptography#EIP712-_hashTypedDataV4-bytes32-): + +./examples/utils/cryptography/MyContractDomain.sol + +As part of the message, EIP-712 requires implementers to include a domain separator, which is a hash that includes the current smart contract address and the chain id where it’s deployed. This way, the smart contract can be sure that the structured message was signed for its specific domain, avoiding replayability of signatures in smart contracts. + +#### Validating Nested EIP-712 Signatures + +Accounts (i.e. Smart Contract Wallets or Smart Accounts) are particularly likely to be controlled by multiple signers. As such, it’s important to make sure that signatures are: + +1. Only valid for the intended domain and account. +2. Validated in a way that’s readable for the end signer. + +On one hand, making sure that the Account signature is only valid for an specific smart contract (i.e. an application) is difficult since it requires to validate a signature whose domain is the application but also the Account itself. For these reason, the community developed [ERC-7739](https://eips.ethereum.org/EIPS/eip-7739); a defensive rehashing mechanism that binds a signature to a single domain using a nested EIP-712 approach (i.e. an EIP-712 typed structure wrapping another). + +In case your smart contract validates signatures, using [`ERC7739`](/contracts/5.x/api/utils/cryptography#ERC7739) signer will implement the [`IERC1271`](/contracts/5.x/api/interfaces#IERC1271) interface for validating smart contract signatures following the approach suggested by ERC-7739: + +./examples/utils/cryptography/ERC7739SignerECDSA.sol + +### ERC-7913 Signature Verifiers + +ERC-7913 extends the concept of signature verification to support keys that don’t have their own Ethereum address. This is particularly useful for integrating non-Ethereum cryptographic curves, hardware devices, or other identity systems into smart accounts. + +The standard defines a verifier interface that can be implemented to support different types of keys. A signer is represented as a `bytes` object that concatenates a verifier address and a key: `verifier || key`. + +[`ERC7913Utils`](/community-contracts/api/utils/cryptography#ERC7913Utils) provides functions for verifying signatures using ERC-7913 compatible verifiers: + +```solidity +using ERC7913Utils for bytes; + +function _verify(bytes memory signer, bytes32 hash, bytes memory signature) internal view returns (bool){ + return signer.isValidSignatureNow(hash, signature); +} +``` + +The verification process works as follows: + +* If `signer.length < 20`: verification fails +* If `signer.length == 20`: verification is done using [SignatureChecker](/contracts/5.x/api/utils#SignatureChecker) +* Otherwise: verification is done using an ERC-7913 verifier. + +This allows for backward compatibility with EOAs and ERC-1271 contracts while supporting new types of keys. diff --git a/docs/content/confidential-contracts/api/finance.mdx b/docs/content/confidential-contracts/api/finance.mdx new file mode 100644 index 00000000..faa9990d --- /dev/null +++ b/docs/content/confidential-contracts/api/finance.mdx @@ -0,0 +1,838 @@ +--- +title: "Finance" +description: "Smart contract finance utilities and implementations" +--- + +This directory includes primitives for on-chain confidential financial systems: + +* [`VestingWalletConfidential`](#VestingWalletConfidential): Handles the vesting of confidential tokens for a given beneficiary. Custody of multiple tokens can be given to this contract, which will release the token to the beneficiary following a given, customizable, vesting schedule. +* [`VestingWalletCliffConfidential`](#VestingWalletCliffConfidential): Variant of [`VestingWalletConfidential`](#VestingWalletConfidential) which adds a cliff period to the vesting schedule. + +For convenience, this directory also includes: + +* [`VestingWalletConfidentialFactory`](#VestingWalletConfidentialFactory): A factory which enables batch funding of vesting wallets. + +## Contracts +[`VestingWalletConfidential`](#VestingWalletConfidential) +[`VestingWalletCliffConfidential`](#VestingWalletCliffConfidential) +[`VestingWalletConfidentialFactory`](#VestingWalletConfidentialFactory) + + + +
+ +## `ERC7821WithExecutor` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/finance/ERC7821WithExecutor.sol"; +``` + +Extension of `ERC7821` that adds an [`ERC7821WithExecutor.executor`](#ERC7821WithExecutor-executor--) address that is able to execute arbitrary calls via `ERC7821.execute`. + +
+

Functions

+
+- [executor()](#ERC7821WithExecutor-executor--) +- [__ERC7821WithExecutor_init(executor_)](#ERC7821WithExecutor-__ERC7821WithExecutor_init-address-) +- [_erc7821AuthorizedExecutor(caller, mode, executionData)](#ERC7821WithExecutor-_erc7821AuthorizedExecutor-address-bytes32-bytes-) +#### ERC7821 [!toc] +- [execute(mode, executionData)](#ERC7821-execute-bytes32-bytes-) +- [supportsExecutionMode(mode)](#ERC7821-supportsExecutionMode-bytes32-) +#### IERC7821 [!toc] +#### Initializable [!toc] +- [_checkInitializing()](#Initializable-_checkInitializing--) +- [_disableInitializers()](#Initializable-_disableInitializers--) +- [_getInitializedVersion()](#Initializable-_getInitializedVersion--) +- [_isInitializing()](#Initializable-_isInitializing--) +- [_initializableStorageSlot()](#Initializable-_initializableStorageSlot--) +
+
+ +
+

Events

+
+#### ERC7821 [!toc] +#### IERC7821 [!toc] +#### Initializable [!toc] +- [Initialized(version)](#Initializable-Initialized-uint64-) +
+
+ +
+

Errors

+
+#### ERC7821 [!toc] +- [UnsupportedExecutionMode()](#ERC7821-UnsupportedExecutionMode--) +#### IERC7821 [!toc] +#### Initializable [!toc] +- [InvalidInitialization()](#Initializable-InvalidInitialization--) +- [NotInitializing()](#Initializable-NotInitializing--) +
+
+ + + +
+
+

executor() → address

+
+

public

+# +
+
+
+ +Trusted address that is able to execute arbitrary calls from the vesting wallet via `ERC7821.execute`. + +
+
+ + + +
+
+

__ERC7821WithExecutor_init(address executor_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_erc7821AuthorizedExecutor(address caller, bytes32 mode, bytes executionData) → bool

+
+

internal

+# +
+
+
+ +Access control mechanism for the `execute` function. +By default, only the contract itself is allowed to execute. + +Override this function to implement custom access control, for example to allow the +ERC-4337 entrypoint to execute. + +```solidity +function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData +) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); +} +``` + +
+
+ + + +
+ +## `VestingWalletCliffConfidential` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/finance/VestingWalletCliffConfidential.sol"; +``` + +An extension of [`VestingWalletConfidential`](#VestingWalletConfidential) that adds a cliff to the vesting schedule. The cliff is `cliffSeconds` long and +starts at the vesting start timestamp (see [`VestingWalletConfidential`](#VestingWalletConfidential)). + +
+

Functions

+
+- [cliff()](#VestingWalletCliffConfidential-cliff--) +- [__VestingWalletCliffConfidential_init(beneficiary, startTimestamp, durationSeconds, cliffSeconds)](#VestingWalletCliffConfidential-__VestingWalletCliffConfidential_init-address-uint48-uint48-uint48-) +- [__VestingWalletCliffConfidential_init_unchained(cliffSeconds)](#VestingWalletCliffConfidential-__VestingWalletCliffConfidential_init_unchained-uint48-) +- [_vestingSchedule(totalAllocation, timestamp)](#VestingWalletCliffConfidential-_vestingSchedule-euint128-uint48-) +#### VestingWalletConfidential [!toc] +- [start()](#VestingWalletConfidential-start--) +- [duration()](#VestingWalletConfidential-duration--) +- [end()](#VestingWalletConfidential-end--) +- [released(token)](#VestingWalletConfidential-released-address-) +- [releasable(token)](#VestingWalletConfidential-releasable-address-) +- [release(token)](#VestingWalletConfidential-release-address-) +- [vestedAmount(token, timestamp)](#VestingWalletConfidential-vestedAmount-address-uint48-) +- [__VestingWalletConfidential_init(beneficiary, startTimestamp, durationSeconds)](#VestingWalletConfidential-__VestingWalletConfidential_init-address-uint48-uint48-) +- [__VestingWalletConfidential_init_unchained(startTimestamp, durationSeconds)](#VestingWalletConfidential-__VestingWalletConfidential_init_unchained-uint48-uint48-) +#### ReentrancyGuardTransient [!toc] +- [_reentrancyGuardEntered()](#ReentrancyGuardTransient-_reentrancyGuardEntered--) +#### OwnableUpgradeable [!toc] +- [__Ownable_init(initialOwner)](#OwnableUpgradeable-__Ownable_init-address-) +- [__Ownable_init_unchained(initialOwner)](#OwnableUpgradeable-__Ownable_init_unchained-address-) +- [owner()](#OwnableUpgradeable-owner--) +- [_checkOwner()](#OwnableUpgradeable-_checkOwner--) +- [renounceOwnership()](#OwnableUpgradeable-renounceOwnership--) +- [transferOwnership(newOwner)](#OwnableUpgradeable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#OwnableUpgradeable-_transferOwnership-address-) +#### ContextUpgradeable [!toc] +- [__Context_init()](#ContextUpgradeable-__Context_init--) +- [__Context_init_unchained()](#ContextUpgradeable-__Context_init_unchained--) +- [_msgSender()](#ContextUpgradeable-_msgSender--) +- [_msgData()](#ContextUpgradeable-_msgData--) +- [_contextSuffixLength()](#ContextUpgradeable-_contextSuffixLength--) +#### Initializable [!toc] +- [_checkInitializing()](#Initializable-_checkInitializing--) +- [_disableInitializers()](#Initializable-_disableInitializers--) +- [_getInitializedVersion()](#Initializable-_getInitializedVersion--) +- [_isInitializing()](#Initializable-_isInitializing--) +- [_initializableStorageSlot()](#Initializable-_initializableStorageSlot--) +
+
+ +
+

Events

+
+#### VestingWalletConfidential [!toc] +- [VestingWalletConfidentialTokenReleased(token, amount)](#VestingWalletConfidential-VestingWalletConfidentialTokenReleased-address-euint64-) +#### ReentrancyGuardTransient [!toc] +#### OwnableUpgradeable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#OwnableUpgradeable-OwnershipTransferred-address-address-) +#### ContextUpgradeable [!toc] +#### Initializable [!toc] +- [Initialized(version)](#Initializable-Initialized-uint64-) +
+
+ +
+

Errors

+
+- [VestingWalletCliffConfidentialInvalidCliffDuration(cliffSeconds, durationSeconds)](#VestingWalletCliffConfidential-VestingWalletCliffConfidentialInvalidCliffDuration-uint64-uint64-) +#### VestingWalletConfidential [!toc] +#### ReentrancyGuardTransient [!toc] +- [ReentrancyGuardReentrantCall()](#ReentrancyGuardTransient-ReentrancyGuardReentrantCall--) +#### OwnableUpgradeable [!toc] +- [OwnableUnauthorizedAccount(account)](#OwnableUpgradeable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#OwnableUpgradeable-OwnableInvalidOwner-address-) +#### ContextUpgradeable [!toc] +#### Initializable [!toc] +- [InvalidInitialization()](#Initializable-InvalidInitialization--) +- [NotInitializing()](#Initializable-NotInitializing--) +
+
+ + + +
+
+

cliff() → uint64

+
+

public

+# +
+
+
+ +The timestamp at which the cliff ends. + +
+
+ + + +
+
+

__VestingWalletCliffConfidential_init(address beneficiary, uint48 startTimestamp, uint48 durationSeconds, uint48 cliffSeconds)

+
+

internal

+# +
+
+
+ +Set the duration of the cliff, in seconds. The cliff starts at the vesting +start timestamp (see [`VestingWalletConfidential.start`](#VestingWalletConfidential-start--)) and ends `cliffSeconds` later. + +
+
+ + + +
+
+

__VestingWalletCliffConfidential_init_unchained(uint48 cliffSeconds)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_vestingSchedule(euint128 totalAllocation, uint48 timestamp) → euint128

+
+

internal

+# +
+
+
+ +This function returns the amount vested, as a function of time, for +an asset given its total historical allocation. Returns 0 if the [`VestingWalletCliffConfidential.cliff`](#VestingWalletCliffConfidential-cliff--) timestamp is not met. + + +The cliff not only makes the schedule return 0, but it also ignores every possible side +effect from calling the inherited implementation (i.e. `super._vestingSchedule`). Carefully consider +this caveat if the overridden implementation of this function has any (e.g. writing to memory or reverting). + + +
+
+ + + +
+
+

VestingWalletCliffConfidentialInvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds)

+
+

error

+# +
+
+
+ +The specified cliff duration is larger than the vesting duration. + +
+
+ + + +
+ +## `VestingWalletConfidential` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/finance/VestingWalletConfidential.sol"; +``` + +A vesting wallet is an ownable contract that can receive ConfidentialFungibleTokens, and release these +assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule. + +Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning. +Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) +be immediately releasable. + +By setting the duration to 0, one can configure this contract to behave like an asset timelock that holds tokens for +a beneficiary until a specified time. + + +Since the wallet is `Ownable`, and ownership can be transferred, it is possible to sell unvested tokens. + + + +When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make +sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. + + +Confidential vesting wallet contracts can be deployed (as clones) using the [`VestingWalletConfidentialFactory`](#VestingWalletConfidentialFactory). + +
+

Functions

+
+- [start()](#VestingWalletConfidential-start--) +- [duration()](#VestingWalletConfidential-duration--) +- [end()](#VestingWalletConfidential-end--) +- [released(token)](#VestingWalletConfidential-released-address-) +- [releasable(token)](#VestingWalletConfidential-releasable-address-) +- [release(token)](#VestingWalletConfidential-release-address-) +- [vestedAmount(token, timestamp)](#VestingWalletConfidential-vestedAmount-address-uint48-) +- [__VestingWalletConfidential_init(beneficiary, startTimestamp, durationSeconds)](#VestingWalletConfidential-__VestingWalletConfidential_init-address-uint48-uint48-) +- [__VestingWalletConfidential_init_unchained(startTimestamp, durationSeconds)](#VestingWalletConfidential-__VestingWalletConfidential_init_unchained-uint48-uint48-) +- [_vestingSchedule(totalAllocation, timestamp)](#VestingWalletConfidential-_vestingSchedule-euint128-uint48-) +#### ReentrancyGuardTransient [!toc] +- [_reentrancyGuardEntered()](#ReentrancyGuardTransient-_reentrancyGuardEntered--) +#### OwnableUpgradeable [!toc] +- [__Ownable_init(initialOwner)](#OwnableUpgradeable-__Ownable_init-address-) +- [__Ownable_init_unchained(initialOwner)](#OwnableUpgradeable-__Ownable_init_unchained-address-) +- [owner()](#OwnableUpgradeable-owner--) +- [_checkOwner()](#OwnableUpgradeable-_checkOwner--) +- [renounceOwnership()](#OwnableUpgradeable-renounceOwnership--) +- [transferOwnership(newOwner)](#OwnableUpgradeable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#OwnableUpgradeable-_transferOwnership-address-) +#### ContextUpgradeable [!toc] +- [__Context_init()](#ContextUpgradeable-__Context_init--) +- [__Context_init_unchained()](#ContextUpgradeable-__Context_init_unchained--) +- [_msgSender()](#ContextUpgradeable-_msgSender--) +- [_msgData()](#ContextUpgradeable-_msgData--) +- [_contextSuffixLength()](#ContextUpgradeable-_contextSuffixLength--) +#### Initializable [!toc] +- [_checkInitializing()](#Initializable-_checkInitializing--) +- [_disableInitializers()](#Initializable-_disableInitializers--) +- [_getInitializedVersion()](#Initializable-_getInitializedVersion--) +- [_isInitializing()](#Initializable-_isInitializing--) +- [_initializableStorageSlot()](#Initializable-_initializableStorageSlot--) +
+
+ +
+

Events

+
+- [VestingWalletConfidentialTokenReleased(token, amount)](#VestingWalletConfidential-VestingWalletConfidentialTokenReleased-address-euint64-) +#### ReentrancyGuardTransient [!toc] +#### OwnableUpgradeable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#OwnableUpgradeable-OwnershipTransferred-address-address-) +#### ContextUpgradeable [!toc] +#### Initializable [!toc] +- [Initialized(version)](#Initializable-Initialized-uint64-) +
+
+ +
+

Errors

+
+#### ReentrancyGuardTransient [!toc] +- [ReentrancyGuardReentrantCall()](#ReentrancyGuardTransient-ReentrancyGuardReentrantCall--) +#### OwnableUpgradeable [!toc] +- [OwnableUnauthorizedAccount(account)](#OwnableUpgradeable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#OwnableUpgradeable-OwnableInvalidOwner-address-) +#### ContextUpgradeable [!toc] +#### Initializable [!toc] +- [InvalidInitialization()](#Initializable-InvalidInitialization--) +- [NotInitializing()](#Initializable-NotInitializing--) +
+
+ + + +
+
+

start() → uint64

+
+

public

+# +
+
+
+ +Timestamp at which the vesting starts. + +
+
+ + + +
+
+

duration() → uint64

+
+

public

+# +
+
+
+ +Duration of the vesting in seconds. + +
+
+ + + +
+
+

end() → uint64

+
+

public

+# +
+
+
+ +Timestamp at which the vesting ends. + +
+
+ + + +
+
+

released(address token) → euint128

+
+

public

+# +
+
+
+ +Amount of token already released + +
+
+ + + +
+
+

releasable(address token) → euint64

+
+

public

+# +
+
+
+ +Getter for the amount of releasable `token` tokens. `token` should be the address of an +[`IConfidentialFungibleToken`](./interfaces#IConfidentialFungibleToken) contract. + +
+
+ + + +
+
+

release(address token)

+
+

public

+# +
+
+
+ +Release the tokens that have already vested. + +Emits a [`VestingWalletConfidential.VestingWalletConfidentialTokenReleased`](#VestingWalletConfidential-VestingWalletConfidentialTokenReleased-address-euint64-) event. + +
+
+ + + +
+
+

vestedAmount(address token, uint48 timestamp) → euint128

+
+

public

+# +
+
+
+ +Calculates the amount of tokens that have been vested at the given timestamp. +Default implementation is a linear vesting curve. + +
+
+ + + +
+
+

__VestingWalletConfidential_init(address beneficiary, uint48 startTimestamp, uint48 durationSeconds)

+
+

internal

+# +
+
+
+ +Initializes the vesting wallet for a given `beneficiary` with a start time of `startTimestamp` +and an end time of `startTimestamp + durationSeconds`. + +
+
+ + + +
+
+

__VestingWalletConfidential_init_unchained(uint48 startTimestamp, uint48 durationSeconds)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_vestingSchedule(euint128 totalAllocation, uint48 timestamp) → euint128

+
+

internal

+# +
+
+
+ +This returns the amount vested, as a function of time, for an asset given its total historical allocation. + +
+
+ + + +
+
+

VestingWalletConfidentialTokenReleased(address indexed token, euint64 amount)

+
+

event

+# +
+
+ +
+ +Emitted when releasable vested tokens are released. + +
+
+ + + +
+ +## `VestingWalletConfidentialFactory` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/finance/VestingWalletConfidentialFactory.sol"; +``` + +A factory which enables batch funding of vesting wallets. + +The [`VestingWalletConfidentialFactory._deployVestingWalletImplementation`](#VestingWalletConfidentialFactory-_deployVestingWalletImplementation--), [`VestingWalletConfidentialFactory._initializeVestingWallet`](#VestingWalletConfidentialFactory-_initializeVestingWallet-address-bytes-), and [`VestingWalletConfidentialFactory._validateVestingWalletInitArgs`](#VestingWalletConfidentialFactory-_validateVestingWalletInitArgs-bytes-) +functions remain unimplemented to allow for custom implementations of the vesting wallet to be used. + +
+

Functions

+
+- [constructor()](#VestingWalletConfidentialFactory-constructor--) +- [batchFundVestingWalletConfidential(confidentialFungibleToken, vestingPlans, inputProof)](#VestingWalletConfidentialFactory-batchFundVestingWalletConfidential-address-struct-VestingWalletConfidentialFactory-VestingPlan---bytes-) +- [createVestingWalletConfidential(initArgs)](#VestingWalletConfidentialFactory-createVestingWalletConfidential-bytes-) +- [predictVestingWalletConfidential(initArgs)](#VestingWalletConfidentialFactory-predictVestingWalletConfidential-bytes-) +- [_validateVestingWalletInitArgs(initArgs)](#VestingWalletConfidentialFactory-_validateVestingWalletInitArgs-bytes-) +- [_initializeVestingWallet(vestingWalletAddress, initArgs)](#VestingWalletConfidentialFactory-_initializeVestingWallet-address-bytes-) +- [_deployVestingWalletImplementation()](#VestingWalletConfidentialFactory-_deployVestingWalletImplementation--) +- [_getCreate2VestingWalletConfidentialSalt(initArgs)](#VestingWalletConfidentialFactory-_getCreate2VestingWalletConfidentialSalt-bytes-) +
+
+ +
+

Events

+
+- [VestingWalletConfidentialFunded(vestingWalletConfidential, confidentialFungibleToken, transferredAmount, initArgs)](#VestingWalletConfidentialFactory-VestingWalletConfidentialFunded-address-address-euint64-bytes-) +- [VestingWalletConfidentialCreated(vestingWalletConfidential, initArgs)](#VestingWalletConfidentialFactory-VestingWalletConfidentialCreated-address-bytes-) +
+
+ + + +
+
+

constructor()

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

batchFundVestingWalletConfidential(address confidentialFungibleToken, struct VestingWalletConfidentialFactory.VestingPlan[] vestingPlans, bytes inputProof)

+
+

public

+# +
+
+
+ +Batches the funding of multiple confidential vesting wallets. + +Funds are sent to deterministic wallet addresses. Wallets can be created either +before or after this operation. + +Emits a [`VestingWalletConfidentialFactory.VestingWalletConfidentialFunded`](#VestingWalletConfidentialFactory-VestingWalletConfidentialFunded-address-address-euint64-bytes-) event for each funded vesting plan. + +
+
+ + + +
+
+

createVestingWalletConfidential(bytes initArgs) → address

+
+

public

+# +
+
+
+ +Creates a confidential vesting wallet. + +Emits a [`VestingWalletConfidentialFactory.VestingWalletConfidentialCreated`](#VestingWalletConfidentialFactory-VestingWalletConfidentialCreated-address-bytes-). + +
+
+ + + +
+
+

predictVestingWalletConfidential(bytes initArgs) → address

+
+

public

+# +
+
+
+ +Predicts the deterministic address for a confidential vesting wallet. + +
+
+ + + +
+
+

_validateVestingWalletInitArgs(bytes initArgs)

+
+

internal

+# +
+
+
+ +Virtual function that must be implemented to validate the initArgs bytes. + +
+
+ + + +
+
+

_initializeVestingWallet(address vestingWalletAddress, bytes initArgs)

+
+

internal

+# +
+
+
+ +Virtual function that must be implemented to initialize the vesting wallet at `vestingWalletAddress`. + +
+
+ + + +
+
+

_deployVestingWalletImplementation() → address

+
+

internal

+# +
+
+
+ +Internal function that is called once to deploy the vesting wallet implementation. + +Vesting wallet clones will be initialized by calls to the [`VestingWalletConfidentialFactory._initializeVestingWallet`](#VestingWalletConfidentialFactory-_initializeVestingWallet-address-bytes-) function. + +
+
+ + + +
+
+

_getCreate2VestingWalletConfidentialSalt(bytes initArgs) → bytes32

+
+

internal

+# +
+
+
+ +Gets create2 salt for a confidential vesting wallet. + +
+
+ + + +
+
+

VestingWalletConfidentialFunded(address indexed vestingWalletConfidential, address indexed confidentialFungibleToken, euint64 transferredAmount, bytes initArgs)

+
+

event

+# +
+
+ +
+ +Emitted for each vesting wallet funded within a batch. + +
+
+ + +
+
+

VestingWalletConfidentialCreated(address indexed vestingWalletConfidential, bytes initArgs)

+
+

event

+# +
+
+ +
+ +Emitted when a vesting wallet is deployed. + +
+
diff --git a/docs/content/confidential-contracts/api/governance.mdx b/docs/content/confidential-contracts/api/governance.mdx new file mode 100644 index 00000000..d8607c00 --- /dev/null +++ b/docs/content/confidential-contracts/api/governance.mdx @@ -0,0 +1,454 @@ +--- +title: "Governance" +description: "Smart contract governance utilities and implementations" +--- + +This directory includes primitives for on-chain confidential governance. + +* [`VotesConfidential`](#VotesConfidential): Abstract contract that keeps track of confidential voting units, enabling checkpointed voting through governance. + +## Utils +[`VotesConfidential`](#VotesConfidential) + + + +
+ +## `VotesConfidential` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/governance/utils/VotesConfidential.sol"; +``` + +A confidential votes contract tracking confidential voting power of accounts over time. +It features vote delegation to delegators. + +This contract keeps a history (checkpoints) of each account's confidential vote power. Confidential +voting power can be delegated either by calling the [`VotesConfidential.delegate`](#VotesConfidential-delegate-address-) function directly, or by providing +a signature to be used with [`VotesConfidential.delegateBySig`](#VotesConfidential-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-). Confidential voting power handles can be queried +through the public accessors [`VotesConfidential.getVotes`](#VotesConfidential-getVotes-address-) and [`VotesConfidential.getPastVotes`](#VotesConfidential-getPastVotes-address-uint256-) but can only be decrypted by accounts +allowed to access them. Ensure that [`HandleAccessManager._validateHandleAllowance`](./utils#HandleAccessManager-_validateHandleAllowance-bytes32-) is implemented properly, allowing all +necessary addresses to access voting power handles. + +By default, voting units does not account for voting power. This makes transfers of underlying +voting units cheaper. The downside is that it requires users to delegate to themselves in order +to activate checkpoints and have their voting power tracked. + +
+

Functions

+
+- [clock()](#VotesConfidential-clock--) +- [CLOCK_MODE()](#VotesConfidential-CLOCK_MODE--) +- [getVotes(account)](#VotesConfidential-getVotes-address-) +- [getPastVotes(account, timepoint)](#VotesConfidential-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#VotesConfidential-getPastTotalSupply-uint256-) +- [confidentialTotalSupply()](#VotesConfidential-confidentialTotalSupply--) +- [delegates(account)](#VotesConfidential-delegates-address-) +- [delegate(delegatee)](#VotesConfidential-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#VotesConfidential-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#VotesConfidential-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#VotesConfidential-_transferVotingUnits-address-address-euint64-) +- [_moveDelegateVotes(from, to, amount)](#VotesConfidential-_moveDelegateVotes-address-address-euint64-) +- [_validateTimepoint(timepoint)](#VotesConfidential-_validateTimepoint-uint256-) +- [_getVotingUnits()](#VotesConfidential-_getVotingUnits-address-) +#### HandleAccessManager [!toc] +- [getHandleAllowance(handle, account, persistent)](#HandleAccessManager-getHandleAllowance-bytes32-address-bool-) +- [_validateHandleAllowance(handle)](#HandleAccessManager-_validateHandleAllowance-bytes32-) +#### IERC6372 [!toc] +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +
+
+ +
+

Events

+
+- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#VotesConfidential-DelegateVotesChanged-address-euint64-euint64-) +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#VotesConfidential-DelegateChanged-address-address-address-) +#### HandleAccessManager [!toc] +#### IERC6372 [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### Nonces [!toc] +
+
+ +
+

Errors

+
+- [VotesExpiredSignature(expiry)](#VotesConfidential-VotesExpiredSignature-uint256-) +- [ERC6372InconsistentClock()](#VotesConfidential-ERC6372InconsistentClock--) +- [ERC5805FutureLookup(timepoint, clock)](#VotesConfidential-ERC5805FutureLookup-uint256-uint48-) +#### HandleAccessManager [!toc] +#### IERC6372 [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based +checkpoints (and voting), in which case [`VotesConfidential.CLOCK_MODE`](#VotesConfidential-CLOCK_MODE--) should be overridden as well to match. + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Machine-readable description of the clock as specified in ERC-6372. + +
+
+ + + +
+
+

getVotes(address account) → euint64

+
+

public

+# +
+
+
+ +Returns the current amount of votes that `account` has. + +
+
+ + + +
+
+

getPastVotes(address account, uint256 timepoint) → euint64

+
+

public

+# +
+
+
+ +Returns the amount of votes that `account` had at a specific moment in the past. If the [`VotesConfidential.clock`](#VotesConfidential-clock--) is +configured to use block numbers, this will return the value at the end of the corresponding block. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

getPastTotalSupply(uint256 timepoint) → euint64

+
+

public

+# +
+
+
+ +Returns the total supply of votes available at a specific moment in the past. If the [`VotesConfidential.clock`](#VotesConfidential-clock--) is +configured to use block numbers, this will return the value at the end of the corresponding block. + + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. +Votes that have not been delegated are still part of total supply, even though they would not participate in a +vote. + + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

confidentialTotalSupply() → euint64

+
+

public

+# +
+
+
+ +Returns the current total supply of votes as an encrypted uint64 (euint64). Must be implemented +by the derived contract. + +
+
+ + + +
+
+

delegates(address account) → address

+
+

public

+# +
+
+
+ +Returns the delegate that `account` has chosen. + +
+
+ + + +
+
+

delegate(address delegatee)

+
+

public

+# +
+
+
+ +Delegates votes from the sender to `delegatee`. + +
+
+ + + +
+
+

delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

+
+

public

+# +
+
+
+ +Delegates votes from an EOA to `delegatee` via an ECDSA signature. + +
+
+ + + +
+
+

_delegate(address account, address delegatee)

+
+

internal

+# +
+
+
+ +Delegate all of `account`'s voting units to `delegatee`. + +Emits events `IVotes-DelegateChanged` and `IVotes-DelegateVotesChanged`. + +
+
+ + + +
+
+

_transferVotingUnits(address from, address to, euint64 amount)

+
+

internal

+# +
+
+
+ +Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` +should be zero. Total supply of voting units will be adjusted with mints and burns. + + +Must be called after [`VotesConfidential.confidentialTotalSupply`](#VotesConfidential-confidentialTotalSupply--) is updated. + + +
+
+ + + +
+
+

_moveDelegateVotes(address from, address to, euint64 amount)

+
+

internal

+# +
+
+
+ +Moves delegated votes from one delegate to another. + +
+
+ + + +
+
+

_validateTimepoint(uint256 timepoint) → uint48

+
+

internal

+# +
+
+
+ +Validate that a timepoint is in the past, and return it as a uint48. + +
+
+ + + +
+
+

_getVotingUnits(address) → euint64

+
+

internal

+# +
+
+
+ +Must return the voting units held by an account. + +
+
+ + + +
+
+

DelegateVotesChanged(address indexed delegate, euint64 previousVotes, euint64 newVotes)

+
+

event

+# +
+
+ +
+ +Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units. + +
+
+ + +
+
+

DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate)

+
+

event

+# +
+
+ +
+ +Emitted when an account changes their delegate. + +
+
+ + + +
+
+

VotesExpiredSignature(uint256 expiry)

+
+

error

+# +
+
+
+ +The signature used has expired. + +
+
+ + + +
+
+

ERC6372InconsistentClock()

+
+

error

+# +
+
+
+ +The clock was incorrectly modified. + +
+
+ + + +
+
+

ERC5805FutureLookup(uint256 timepoint, uint48 clock)

+
+

error

+# +
+
+
+ +Lookup to future votes is not available. + +
+
diff --git a/docs/content/confidential-contracts/api/index.mdx b/docs/content/confidential-contracts/api/index.mdx new file mode 100644 index 00000000..2a562ff0 --- /dev/null +++ b/docs/content/confidential-contracts/api/index.mdx @@ -0,0 +1,43 @@ +--- +title: API Reference +--- + +## Core Components + +### [Token](./api/token) +Smart contract token utilities and implementations for ERC7984, a confidential token standard utilizing the Zama FHE library. + +- **[ERC7984](./api/token#ERC7984)** - Reference implementation for IERC7984 +- **[ERC7984ERC20Wrapper](./api/token#ERC7984ERC20Wrapper)** - Wrapper for converting ERC20 tokens to confidential tokens +- **[ERC7984Freezable](./api/token#ERC7984Freezable)** - Extension with freezing mechanism +- **[ERC7984ObserverAccess](./api/token#ERC7984ObserverAccess)** - Extension allowing observers to access transfer amounts +- **[ERC7984Restricted](./api/token#ERC7984Restricted)** - Extension implementing user account transfer restrictions +- **[ERC7984Votes](./api/token#ERC7984Votes)** - Extension supporting confidential votes tracking and delegation +- **[ERC7984Utils](./api/token#ERC7984Utils)** - Utility library for ERC7984 functions + +### [Finance](./api/finance) +Smart contract finance utilities and implementations for confidential financial systems. + +- **[VestingWalletConfidential](./api/finance#VestingWalletConfidential)** - Handles vesting of confidential tokens with customizable schedule +- **[VestingWalletCliffConfidential](./api/finance#VestingWalletCliffConfidential)** - Variant with cliff period +- **[VestingWalletConfidentialFactory](./api/finance#VestingWalletConfidentialFactory)** - Factory for batch funding of vesting wallets +- **[ERC7821WithExecutor](./api/finance#ERC7821WithExecutor)** - Extension adding executor address for arbitrary calls + +### [Governance](./api/governance) +Smart contract governance utilities and implementations for confidential governance systems. + +- **[VotesConfidential](./api/governance#VotesConfidential)** - Abstract contract for tracking confidential voting units with delegation + +### [Interfaces](./api/interfaces) +Core interfaces for interacting with confidential contracts. + +- **[IERC7984](./api/interfaces#IERC7984)** - Interface for confidential fungible token standard +- **[IERC7984Receiver](./api/interfaces#IERC7984Receiver)** - Interface for contracts receiving ERC7984 transfers with callbacks + +### [Utils](./api/utils) +Miscellaneous contracts and libraries with utility functions for confidential contracts. + +- **[FHESafeMath](./api/utils#FHESafeMath)** - Safe arithmetic operations for encrypted values +- **[CheckpointsConfidential](./api/utils#CheckpointsConfidential)** - Checkpoints implementation for encrypted values +- **[HandleAccessManager](./api/utils#HandleAccessManager)** - Contract for managing ciphertext handle access +- **[Checkpoints](./api/utils#Checkpoints)** - Standard checkpoints for non-encrypted values diff --git a/docs/content/confidential-contracts/api/interfaces.mdx b/docs/content/confidential-contracts/api/interfaces.mdx new file mode 100644 index 00000000..cda9fb86 --- /dev/null +++ b/docs/content/confidential-contracts/api/interfaces.mdx @@ -0,0 +1,451 @@ +--- +title: "Interfaces" +description: "Smart contract interfaces utilities and implementations" +--- + +These interfaces are available as `.sol` files and are useful to interact with third party contracts that implement them. + +* [`IConfidentialFungibleToken`](#IConfidentialFungibleToken) +* [`IConfidentialFungibleTokenReceiver`](#IConfidentialFungibleTokenReceiver) + +## Core +[`IConfidentialFungibleToken`](#IConfidentialFungibleToken) +[`IConfidentialFungibleTokenReceiver`](#IConfidentialFungibleTokenReceiver) + + + +
+ +## `IConfidentialFungibleToken` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleToken.sol"; +``` + +Draft interface for a confidential fungible token standard utilizing the Zama FHE library. + +
+

Functions

+
+- [name()](#IConfidentialFungibleToken-name--) +- [symbol()](#IConfidentialFungibleToken-symbol--) +- [decimals()](#IConfidentialFungibleToken-decimals--) +- [tokenURI()](#IConfidentialFungibleToken-tokenURI--) +- [confidentialTotalSupply()](#IConfidentialFungibleToken-confidentialTotalSupply--) +- [confidentialBalanceOf(account)](#IConfidentialFungibleToken-confidentialBalanceOf-address-) +- [isOperator(holder, spender)](#IConfidentialFungibleToken-isOperator-address-address-) +- [setOperator(operator, until)](#IConfidentialFungibleToken-setOperator-address-uint48-) +- [confidentialTransfer(to, encryptedAmount, inputProof)](#IConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes-) +- [confidentialTransfer(to, amount)](#IConfidentialFungibleToken-confidentialTransfer-address-euint64-) +- [confidentialTransferFrom(from, to, encryptedAmount, inputProof)](#IConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes-) +- [confidentialTransferFrom(from, to, amount)](#IConfidentialFungibleToken-confidentialTransferFrom-address-address-euint64-) +- [confidentialTransferAndCall(to, encryptedAmount, inputProof, data)](#IConfidentialFungibleToken-confidentialTransferAndCall-address-externalEuint64-bytes-bytes-) +- [confidentialTransferAndCall(to, amount, data)](#IConfidentialFungibleToken-confidentialTransferAndCall-address-euint64-bytes-) +- [confidentialTransferFromAndCall(from, to, encryptedAmount, inputProof, data)](#IConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-externalEuint64-bytes-bytes-) +- [confidentialTransferFromAndCall(from, to, amount, data)](#IConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-euint64-bytes-) +
+
+ +
+

Events

+
+- [OperatorSet(holder, operator, until)](#IConfidentialFungibleToken-OperatorSet-address-address-uint48-) +- [ConfidentialTransfer(from, to, amount)](#IConfidentialFungibleToken-ConfidentialTransfer-address-address-euint64-) +- [AmountDisclosed(encryptedAmount, amount)](#IConfidentialFungibleToken-AmountDisclosed-euint64-uint64-) +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the symbol of the token. + +
+
+ + + +
+
+

decimals() → uint8

+
+

external

+# +
+
+
+ +Returns the number of decimals of the token. Recommended to be 6. + +
+
+ + + +
+
+

tokenURI() → string

+
+

external

+# +
+
+
+ +Returns the token URI. + +
+
+ + + +
+
+

confidentialTotalSupply() → euint64

+
+

external

+# +
+
+
+ +Returns the confidential total supply of the token. + +
+
+ + + +
+
+

confidentialBalanceOf(address account) → euint64

+
+

external

+# +
+
+
+ +Returns the confidential balance of the account `account`. + +
+
+ + + +
+
+

isOperator(address holder, address spender) → bool

+
+

external

+# +
+
+
+ +Returns true if `spender` is currently an operator for `holder`. + +
+
+ + + +
+
+

setOperator(address operator, uint48 until)

+
+

external

+# +
+
+
+ +Sets `operator` as an operator for `holder` until the timestamp `until`. + + +An operator may transfer any amount of tokens on behalf of a holder while approved. + + +
+
+ + + +
+
+

confidentialTransfer(address to, externalEuint64 encryptedAmount, bytes inputProof) → euint64

+
+

external

+# +
+
+
+ +Transfers the encrypted amount `encryptedAmount` to `to` with the given input proof `inputProof`. + +Returns the encrypted amount that was actually transferred. + +
+
+ + + +
+
+

confidentialTransfer(address to, euint64 amount) → euint64 transferred

+
+

external

+# +
+
+
+ +Similar to #IConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes- but without an input proof. The caller +*must* already be allowed by ACL for the given `amount`. + +
+
+ + + +
+
+

confidentialTransferFrom(address from, address to, externalEuint64 encryptedAmount, bytes inputProof) → euint64

+
+

external

+# +
+
+
+ +Transfers the encrypted amount `encryptedAmount` from `from` to `to` with the given input proof +`inputProof`. `msg.sender` must be either `from` or an operator for `from`. + +Returns the encrypted amount that was actually transferred. + +
+
+ + + +
+
+

confidentialTransferFrom(address from, address to, euint64 amount) → euint64 transferred

+
+

external

+# +
+
+
+ +Similar to #IConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes- but without an input proof. +The caller *must* be already allowed by ACL for the given `amount`. + +
+
+ + + +
+
+

confidentialTransferAndCall(address to, externalEuint64 encryptedAmount, bytes inputProof, bytes data) → euint64 transferred

+
+

external

+# +
+
+
+ +Similar to #IConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes- but with a callback to `to` after +the transfer. + +The callback is made to the [`IConfidentialFungibleTokenReceiver.onConfidentialTransferReceived`](#IConfidentialFungibleTokenReceiver-onConfidentialTransferReceived-address-address-euint64-bytes-) function on the +to address with the actual transferred amount (may differ from the given `encryptedAmount`) and the given +data `data`. + +
+
+ + + +
+
+

confidentialTransferAndCall(address to, euint64 amount, bytes data) → euint64 transferred

+
+

external

+# +
+
+
+ +Similar to #IConfidentialFungibleToken-confidentialTransfer-address-euint64- but with a callback to `to` after the transfer. + +
+
+ + + +
+
+

confidentialTransferFromAndCall(address from, address to, externalEuint64 encryptedAmount, bytes inputProof, bytes data) → euint64 transferred

+
+

external

+# +
+
+
+ +Similar to #IConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes- but with a callback to `to` +after the transfer. + +
+
+ + + +
+
+

confidentialTransferFromAndCall(address from, address to, euint64 amount, bytes data) → euint64 transferred

+
+

external

+# +
+
+
+ +Similar to #IConfidentialFungibleToken-confidentialTransferFrom-address-address-euint64- but with a callback to `to` +after the transfer. + +
+
+ + + +
+
+

OperatorSet(address indexed holder, address indexed operator, uint48 until)

+
+

event

+# +
+
+ +
+ +Emitted when the expiration timestamp for an operator `operator` is updated for a given `holder`. +The operator may move any amount of tokens on behalf of the holder until the timestamp `until`. + +
+
+ + +
+
+

ConfidentialTransfer(address indexed from, address indexed to, euint64 indexed amount)

+
+

event

+# +
+
+ +
+ +Emitted when a confidential transfer is made from `from` to `to` of encrypted amount `amount`. + +
+
+ + +
+
+

AmountDisclosed(euint64 indexed encryptedAmount, uint64 amount)

+
+

event

+# +
+
+ +
+ +Emitted when an encrypted amount is disclosed. + +Accounts with access to the encrypted amount `encryptedAmount` that is also accessible to this contract +should be able to disclose the amount. This functionality is implementation specific. + +
+
+ + + +
+ +## `IConfidentialFungibleTokenReceiver` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleTokenReceiver.sol"; +``` + +Interface for contracts that can receive confidential token transfers with a callback. + +
+

Functions

+
+- [onConfidentialTransferReceived(operator, from, amount, data)](#IConfidentialFungibleTokenReceiver-onConfidentialTransferReceived-address-address-euint64-bytes-) +
+
+ + + +
+
+

onConfidentialTransferReceived(address operator, address from, euint64 amount, bytes data) → ebool

+
+

external

+# +
+
+
+ +Called upon receiving a confidential token transfer. Returns an encrypted boolean indicating success +of the callback. If false is returned, the transfer must be reversed. + +
+
diff --git a/docs/content/confidential-contracts/api/token.mdx b/docs/content/confidential-contracts/api/token.mdx new file mode 100644 index 00000000..d747f38f --- /dev/null +++ b/docs/content/confidential-contracts/api/token.mdx @@ -0,0 +1,1200 @@ +--- +title: "Token" +description: "Smart contract token utilities and implementations" +--- + +This set of interfaces, contracts, and utilities are all related to the evolving Confidential Token Standard. The standard utilizes the Zama fhEVM co-processor for manipulating FHE values. All amounts are stored on-chain as ciphertext handles (or pointers) to values stored on the co-processor. + +* [`ConfidentialFungibleToken`](#ConfidentialFungibleToken): Implementation of [`IConfidentialFungibleToken`](./interfaces#IConfidentialFungibleToken). +* [`ConfidentialFungibleTokenERC20Wrapper`](#ConfidentialFungibleTokenERC20Wrapper): Extension of [`ConfidentialFungibleToken`](#ConfidentialFungibleToken) which wraps an `ERC20` into a confidential token. The wrapper allows for free conversion in both directions at a fixed rate. +* [`ConfidentialFungibleTokenUtils`](#ConfidentialFungibleTokenUtils): A library that provides the on-transfer callback check used by [`ConfidentialFungibleToken`](#ConfidentialFungibleToken). + +## Core +[`ConfidentialFungibleToken`](#ConfidentialFungibleToken) + +## Extensions +[`ConfidentialFungibleTokenERC20Wrapper`](#ConfidentialFungibleTokenERC20Wrapper) + +## Utilities +[`ConfidentialFungibleTokenUtils`](#ConfidentialFungibleTokenUtils) + + + +
+ +## `ConfidentialFungibleToken` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/token/ConfidentialFungibleToken.sol"; +``` + +Reference implementation for [`IConfidentialFungibleToken`](./interfaces#IConfidentialFungibleToken). + +This contract implements a fungible token where balances and transfers are encrypted using the Zama fhEVM, +providing confidentiality to users. Token amounts are stored as encrypted, unsigned integers (`euint64`) +that can only be decrypted by authorized parties. + +Key features: + +- All balances are encrypted +- Transfers happen without revealing amounts +- Support for operators (delegated transfer capabilities with time bounds) +- Transfer and call pattern +- Safe overflow/underflow handling for FHE operations + +
+

Functions

+
+- [constructor(name_, symbol_, tokenURI_)](#ConfidentialFungibleToken-constructor-string-string-string-) +- [name()](#ConfidentialFungibleToken-name--) +- [symbol()](#ConfidentialFungibleToken-symbol--) +- [decimals()](#ConfidentialFungibleToken-decimals--) +- [tokenURI()](#ConfidentialFungibleToken-tokenURI--) +- [confidentialTotalSupply()](#ConfidentialFungibleToken-confidentialTotalSupply--) +- [confidentialBalanceOf(account)](#ConfidentialFungibleToken-confidentialBalanceOf-address-) +- [isOperator(holder, spender)](#ConfidentialFungibleToken-isOperator-address-address-) +- [setOperator(operator, until)](#ConfidentialFungibleToken-setOperator-address-uint48-) +- [confidentialTransfer(to, encryptedAmount, inputProof)](#ConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes-) +- [confidentialTransfer(to, amount)](#ConfidentialFungibleToken-confidentialTransfer-address-euint64-) +- [confidentialTransferFrom(from, to, encryptedAmount, inputProof)](#ConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes-) +- [confidentialTransferFrom(from, to, amount)](#ConfidentialFungibleToken-confidentialTransferFrom-address-address-euint64-) +- [confidentialTransferAndCall(to, encryptedAmount, inputProof, data)](#ConfidentialFungibleToken-confidentialTransferAndCall-address-externalEuint64-bytes-bytes-) +- [confidentialTransferAndCall(to, amount, data)](#ConfidentialFungibleToken-confidentialTransferAndCall-address-euint64-bytes-) +- [confidentialTransferFromAndCall(from, to, encryptedAmount, inputProof, data)](#ConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-externalEuint64-bytes-bytes-) +- [confidentialTransferFromAndCall(from, to, amount, data)](#ConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-euint64-bytes-) +- [discloseEncryptedAmount(encryptedAmount)](#ConfidentialFungibleToken-discloseEncryptedAmount-euint64-) +- [finalizeDiscloseEncryptedAmount(requestId, amount, signatures)](#ConfidentialFungibleToken-finalizeDiscloseEncryptedAmount-uint256-uint64-bytes---) +- [_setOperator(holder, operator, until)](#ConfidentialFungibleToken-_setOperator-address-address-uint48-) +- [_mint(to, amount)](#ConfidentialFungibleToken-_mint-address-euint64-) +- [_burn(from, amount)](#ConfidentialFungibleToken-_burn-address-euint64-) +- [_transfer(from, to, amount)](#ConfidentialFungibleToken-_transfer-address-address-euint64-) +- [_transferAndCall(from, to, amount, data)](#ConfidentialFungibleToken-_transferAndCall-address-address-euint64-bytes-) +- [_update(from, to, amount)](#ConfidentialFungibleToken-_update-address-address-euint64-) +#### IConfidentialFungibleToken [!toc] +
+
+ +
+

Events

+
+#### IConfidentialFungibleToken [!toc] +- [OperatorSet(holder, operator, until)](#IConfidentialFungibleToken-OperatorSet-address-address-uint48-) +- [ConfidentialTransfer(from, to, amount)](#IConfidentialFungibleToken-ConfidentialTransfer-address-address-euint64-) +- [AmountDisclosed(encryptedAmount, amount)](#IConfidentialFungibleToken-AmountDisclosed-euint64-uint64-) +
+
+ +
+

Errors

+
+- [ConfidentialFungibleTokenInvalidReceiver(receiver)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidReceiver-address-) +- [ConfidentialFungibleTokenInvalidSender(sender)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidSender-address-) +- [ConfidentialFungibleTokenUnauthorizedSpender(holder, spender)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedSpender-address-address-) +- [ConfidentialFungibleTokenZeroBalance(holder)](#ConfidentialFungibleToken-ConfidentialFungibleTokenZeroBalance-address-) +- [ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, user)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount-euint64-address-) +- [ConfidentialFungibleTokenUnauthorizedCaller(caller)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedCaller-address-) +- [ConfidentialFungibleTokenInvalidGatewayRequest(requestId)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidGatewayRequest-uint256-) +#### IConfidentialFungibleToken [!toc] +
+
+ + + +
+
+

constructor(string name_, string symbol_, string tokenURI_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

public

+# +
+
+
+ +Returns the symbol of the token. + +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +Returns the number of decimals of the token. Recommended to be 6. + +
+
+ + + +
+
+

tokenURI() → string

+
+

public

+# +
+
+
+ +Returns the token URI. + +
+
+ + + +
+
+

confidentialTotalSupply() → euint64

+
+

public

+# +
+
+
+ +Returns the confidential total supply of the token. + +
+
+ + + +
+
+

confidentialBalanceOf(address account) → euint64

+
+

public

+# +
+
+
+ +Returns the confidential balance of the account `account`. + +
+
+ + + +
+
+

isOperator(address holder, address spender) → bool

+
+

public

+# +
+
+
+ +Returns true if `spender` is currently an operator for `holder`. + +
+
+ + + +
+
+

setOperator(address operator, uint48 until)

+
+

public

+# +
+
+
+ +Sets `operator` as an operator for `holder` until the timestamp `until`. + + +An operator may transfer any amount of tokens on behalf of a holder while approved. + + +
+
+ + + +
+
+

confidentialTransfer(address to, externalEuint64 encryptedAmount, bytes inputProof) → euint64

+
+

public

+# +
+
+
+ +Transfers the encrypted amount `encryptedAmount` to `to` with the given input proof `inputProof`. + +Returns the encrypted amount that was actually transferred. + +
+
+ + + +
+
+

confidentialTransfer(address to, euint64 amount) → euint64

+
+

public

+# +
+
+
+ +Similar to interfaces#IConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes- but without an input proof. The caller +*must* already be allowed by ACL for the given `amount`. + +
+
+ + + +
+
+

confidentialTransferFrom(address from, address to, externalEuint64 encryptedAmount, bytes inputProof) → euint64 transferred

+
+

public

+# +
+
+
+ +Transfers the encrypted amount `encryptedAmount` from `from` to `to` with the given input proof +`inputProof`. `msg.sender` must be either `from` or an operator for `from`. + +Returns the encrypted amount that was actually transferred. + +
+
+ + + +
+
+

confidentialTransferFrom(address from, address to, euint64 amount) → euint64 transferred

+
+

public

+# +
+
+
+ +Similar to interfaces#IConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes- but without an input proof. +The caller *must* be already allowed by ACL for the given `amount`. + +
+
+ + + +
+
+

confidentialTransferAndCall(address to, externalEuint64 encryptedAmount, bytes inputProof, bytes data) → euint64 transferred

+
+

public

+# +
+
+
+ +Similar to interfaces#IConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes- but with a callback to `to` after +the transfer. + +The callback is made to the [`IConfidentialFungibleTokenReceiver.onConfidentialTransferReceived`](./interfaces#IConfidentialFungibleTokenReceiver-onConfidentialTransferReceived-address-address-euint64-bytes-) function on the +to address with the actual transferred amount (may differ from the given `encryptedAmount`) and the given +data `data`. + +
+
+ + + +
+
+

confidentialTransferAndCall(address to, euint64 amount, bytes data) → euint64 transferred

+
+

public

+# +
+
+
+ +Similar to interfaces#IConfidentialFungibleToken-confidentialTransfer-address-euint64- but with a callback to `to` after the transfer. + +
+
+ + + +
+
+

confidentialTransferFromAndCall(address from, address to, externalEuint64 encryptedAmount, bytes inputProof, bytes data) → euint64 transferred

+
+

public

+# +
+
+
+ +Similar to interfaces#IConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes- but with a callback to `to` +after the transfer. + +
+
+ + + +
+
+

confidentialTransferFromAndCall(address from, address to, euint64 amount, bytes data) → euint64 transferred

+
+

public

+# +
+
+
+ +Similar to interfaces#IConfidentialFungibleToken-confidentialTransferFrom-address-address-euint64- but with a callback to `to` +after the transfer. + +
+
+ + + +
+
+

discloseEncryptedAmount(euint64 encryptedAmount)

+
+

public

+# +
+
+
+ +Discloses an encrypted amount `encryptedAmount` publicly via an [`IConfidentialFungibleToken.AmountDisclosed`](./interfaces#IConfidentialFungibleToken-AmountDisclosed-euint64-uint64-) +event. The caller and this contract must be authorized to use the encrypted amount on the ACL. + + +This is an asynchronous operation where the actual decryption happens off-chain and +[`ConfidentialFungibleToken.finalizeDiscloseEncryptedAmount`](#ConfidentialFungibleToken-finalizeDiscloseEncryptedAmount-uint256-uint64-bytes---) is called with the result. + + +
+
+ + + +
+
+

finalizeDiscloseEncryptedAmount(uint256 requestId, uint64 amount, bytes[] signatures)

+
+

public

+# +
+
+
+ +Finalizes a disclose encrypted amount request. + +
+
+ + + +
+
+

_setOperator(address holder, address operator, uint48 until)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_mint(address to, euint64 amount) → euint64 transferred

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_burn(address from, euint64 amount) → euint64 transferred

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_transfer(address from, address to, euint64 amount) → euint64 transferred

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_transferAndCall(address from, address to, euint64 amount, bytes data) → euint64 transferred

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_update(address from, address to, euint64 amount) → euint64 transferred

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

ConfidentialFungibleTokenInvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +The given receiver `receiver` is invalid for transfers. + +
+
+ + + +
+
+

ConfidentialFungibleTokenInvalidSender(address sender)

+
+

error

+# +
+
+
+ +The given sender `sender` is invalid for transfers. + +
+
+ + + +
+
+

ConfidentialFungibleTokenUnauthorizedSpender(address holder, address spender)

+
+

error

+# +
+
+
+ +The given holder `holder` is not authorized to spend on behalf of `spender`. + +
+
+ + + +
+
+

ConfidentialFungibleTokenZeroBalance(address holder)

+
+

error

+# +
+
+
+ +The holder `holder` is trying to send tokens but has a balance of 0. + +
+
+ + + +
+
+

ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(euint64 amount, address user)

+
+

error

+# +
+
+
+ +The caller `user` does not have access to the encrypted amount `amount`. + + +Try using the equivalent transfer function with an input proof. + + +
+
+ + + +
+
+

ConfidentialFungibleTokenUnauthorizedCaller(address caller)

+
+

error

+# +
+
+
+ +The given caller `caller` is not authorized for the current operation. + +
+
+ + + +
+
+

ConfidentialFungibleTokenInvalidGatewayRequest(uint256 requestId)

+
+

error

+# +
+
+
+ +The given gateway request ID `requestId` is invalid. + +
+
+ + + +
+ +## `ConfidentialFungibleTokenERC20Wrapper` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/token/extensions/ConfidentialFungibleTokenERC20Wrapper.sol"; +``` + +A wrapper contract built on top of [`ConfidentialFungibleToken`](#ConfidentialFungibleToken) that allows wrapping an `ERC20` token +into a confidential fungible token. The wrapper contract implements the `IERC1363Receiver` interface +which allows users to transfer `ERC1363` tokens directly to the wrapper with a callback to wrap the tokens. + + +Minting assumes the full amount of the underlying token transfer has been received, hence some non-standard +tokens such as fee-on-transfer or other deflationary-type tokens are not supported by this wrapper. + + +
+

Functions

+
+- [constructor(underlying_)](#ConfidentialFungibleTokenERC20Wrapper-constructor-contract-IERC20-) +- [decimals()](#ConfidentialFungibleTokenERC20Wrapper-decimals--) +- [rate()](#ConfidentialFungibleTokenERC20Wrapper-rate--) +- [underlying()](#ConfidentialFungibleTokenERC20Wrapper-underlying--) +- [onTransferReceived(, from, amount, data)](#ConfidentialFungibleTokenERC20Wrapper-onTransferReceived-address-address-uint256-bytes-) +- [wrap(to, amount)](#ConfidentialFungibleTokenERC20Wrapper-wrap-address-uint256-) +- [unwrap(from, to, amount)](#ConfidentialFungibleTokenERC20Wrapper-unwrap-address-address-euint64-) +- [unwrap(from, to, encryptedAmount, inputProof)](#ConfidentialFungibleTokenERC20Wrapper-unwrap-address-address-externalEuint64-bytes-) +- [finalizeUnwrap(requestID, amount, signatures)](#ConfidentialFungibleTokenERC20Wrapper-finalizeUnwrap-uint256-uint64-bytes---) +- [_unwrap(from, to, amount)](#ConfidentialFungibleTokenERC20Wrapper-_unwrap-address-address-euint64-) +- [_fallbackUnderlyingDecimals()](#ConfidentialFungibleTokenERC20Wrapper-_fallbackUnderlyingDecimals--) +- [_maxDecimals()](#ConfidentialFungibleTokenERC20Wrapper-_maxDecimals--) +#### IERC1363Receiver [!toc] +#### ConfidentialFungibleToken [!toc] +- [name()](#ConfidentialFungibleToken-name--) +- [symbol()](#ConfidentialFungibleToken-symbol--) +- [tokenURI()](#ConfidentialFungibleToken-tokenURI--) +- [confidentialTotalSupply()](#ConfidentialFungibleToken-confidentialTotalSupply--) +- [confidentialBalanceOf(account)](#ConfidentialFungibleToken-confidentialBalanceOf-address-) +- [isOperator(holder, spender)](#ConfidentialFungibleToken-isOperator-address-address-) +- [setOperator(operator, until)](#ConfidentialFungibleToken-setOperator-address-uint48-) +- [confidentialTransfer(to, encryptedAmount, inputProof)](#ConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes-) +- [confidentialTransfer(to, amount)](#ConfidentialFungibleToken-confidentialTransfer-address-euint64-) +- [confidentialTransferFrom(from, to, encryptedAmount, inputProof)](#ConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes-) +- [confidentialTransferFrom(from, to, amount)](#ConfidentialFungibleToken-confidentialTransferFrom-address-address-euint64-) +- [confidentialTransferAndCall(to, encryptedAmount, inputProof, data)](#ConfidentialFungibleToken-confidentialTransferAndCall-address-externalEuint64-bytes-bytes-) +- [confidentialTransferAndCall(to, amount, data)](#ConfidentialFungibleToken-confidentialTransferAndCall-address-euint64-bytes-) +- [confidentialTransferFromAndCall(from, to, encryptedAmount, inputProof, data)](#ConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-externalEuint64-bytes-bytes-) +- [confidentialTransferFromAndCall(from, to, amount, data)](#ConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-euint64-bytes-) +- [discloseEncryptedAmount(encryptedAmount)](#ConfidentialFungibleToken-discloseEncryptedAmount-euint64-) +- [finalizeDiscloseEncryptedAmount(requestId, amount, signatures)](#ConfidentialFungibleToken-finalizeDiscloseEncryptedAmount-uint256-uint64-bytes---) +- [_setOperator(holder, operator, until)](#ConfidentialFungibleToken-_setOperator-address-address-uint48-) +- [_mint(to, amount)](#ConfidentialFungibleToken-_mint-address-euint64-) +- [_burn(from, amount)](#ConfidentialFungibleToken-_burn-address-euint64-) +- [_transfer(from, to, amount)](#ConfidentialFungibleToken-_transfer-address-address-euint64-) +- [_transferAndCall(from, to, amount, data)](#ConfidentialFungibleToken-_transferAndCall-address-address-euint64-bytes-) +- [_update(from, to, amount)](#ConfidentialFungibleToken-_update-address-address-euint64-) +#### IConfidentialFungibleToken [!toc] +
+
+ +
+

Events

+
+#### IERC1363Receiver [!toc] +#### ConfidentialFungibleToken [!toc] +#### IConfidentialFungibleToken [!toc] +- [OperatorSet(holder, operator, until)](#IConfidentialFungibleToken-OperatorSet-address-address-uint48-) +- [ConfidentialTransfer(from, to, amount)](#IConfidentialFungibleToken-ConfidentialTransfer-address-address-euint64-) +- [AmountDisclosed(encryptedAmount, amount)](#IConfidentialFungibleToken-AmountDisclosed-euint64-uint64-) +
+
+ +
+

Errors

+
+#### IERC1363Receiver [!toc] +#### ConfidentialFungibleToken [!toc] +- [ConfidentialFungibleTokenInvalidReceiver(receiver)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidReceiver-address-) +- [ConfidentialFungibleTokenInvalidSender(sender)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidSender-address-) +- [ConfidentialFungibleTokenUnauthorizedSpender(holder, spender)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedSpender-address-address-) +- [ConfidentialFungibleTokenZeroBalance(holder)](#ConfidentialFungibleToken-ConfidentialFungibleTokenZeroBalance-address-) +- [ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, user)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount-euint64-address-) +- [ConfidentialFungibleTokenUnauthorizedCaller(caller)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedCaller-address-) +- [ConfidentialFungibleTokenInvalidGatewayRequest(requestId)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidGatewayRequest-uint256-) +#### IConfidentialFungibleToken [!toc] +
+
+ + + +
+
+

constructor(contract IERC20 underlying_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +Returns the number of decimals of the token. Recommended to be 6. + +
+
+ + + +
+
+

rate() → uint256

+
+

public

+# +
+
+
+ +Returns the rate at which the underlying token is converted to the wrapped token. +For example, if the `rate` is 1000, then 1000 units of the underlying token equal 1 unit of the wrapped token. + +
+
+ + + +
+
+

underlying() → contract IERC20

+
+

public

+# +
+
+
+ +Returns the address of the underlying ERC-20 token that is being wrapped. + +
+
+ + + +
+
+

onTransferReceived(address, address from, uint256 amount, bytes data) → bytes4

+
+

public

+# +
+
+
+ +`ERC1363` callback function which wraps tokens to the address specified in `data` or +the address `from` (if no address is specified in `data`). This function refunds any excess tokens +sent beyond the nearest multiple of [`ConfidentialFungibleTokenERC20Wrapper.rate`](#ConfidentialFungibleTokenERC20Wrapper-rate--). See [`ConfidentialFungibleTokenERC20Wrapper.wrap`](#ConfidentialFungibleTokenERC20Wrapper-wrap-address-uint256-) from more details on wrapping tokens. + +
+
+ + + +
+
+

wrap(address to, uint256 amount)

+
+

public

+# +
+
+
+ +Wraps amount `amount` of the underlying token into a confidential token and sends it to +`to`. Tokens are exchanged at a fixed rate specified by [`ConfidentialFungibleTokenERC20Wrapper.rate`](#ConfidentialFungibleTokenERC20Wrapper-rate--) such that `amount / rate()` confidential +tokens are sent. Amount transferred in is rounded down to the nearest multiple of [`ConfidentialFungibleTokenERC20Wrapper.rate`](#ConfidentialFungibleTokenERC20Wrapper-rate--). + +
+
+ + + +
+
+

unwrap(address from, address to, euint64 amount)

+
+

public

+# +
+
+
+ +Unwraps tokens from `from` and sends the underlying tokens to `to`. The caller must be `from` +or be an approved operator for `from`. `amount * rate()` underlying tokens are sent to `to`. + + +This is an asynchronous function and waits for decryption to be completed off-chain before disbursing +tokens. + + +The caller *must* already be approved by ACL for the given `amount`. + + +
+
+ + + +
+
+

unwrap(address from, address to, externalEuint64 encryptedAmount, bytes inputProof)

+
+

public

+# +
+
+
+ +Variant of [`ConfidentialFungibleTokenERC20Wrapper.unwrap`](#ConfidentialFungibleTokenERC20Wrapper-unwrap-address-address-externalEuint64-bytes-) that passes an `inputProof` which approves the caller for the `encryptedAmount` +in the ACL. + +
+
+ + + +
+
+

finalizeUnwrap(uint256 requestID, uint64 amount, bytes[] signatures)

+
+

public

+# +
+
+
+ +Fills an unwrap request for a given request id related to a decrypted unwrap amount. + +
+
+ + + +
+
+

_unwrap(address from, address to, euint64 amount)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_fallbackUnderlyingDecimals() → uint8

+
+

internal

+# +
+
+
+ +Returns the default number of decimals of the underlying ERC-20 token that is being wrapped. +Used as a default fallback when [`ConfidentialFungibleTokenERC20Wrapper._tryGetAssetDecimals`](#ConfidentialFungibleTokenERC20Wrapper-_tryGetAssetDecimals-contract-IERC20-) fails to fetch decimals of the underlying +ERC-20 token. + +
+
+ + + +
+
+

_maxDecimals() → uint8

+
+

internal

+# +
+
+
+ +Returns the maximum number that will be used for [`IConfidentialFungibleToken.decimals`](./interfaces#IConfidentialFungibleToken-decimals--) by the wrapper. + +
+
+ + + +
+ +## `ConfidentialFungibleTokenVotes` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/token/extensions/ConfidentialFungibleTokenVotes.sol"; +``` + +Extension of [`ConfidentialFungibleToken`](#ConfidentialFungibleToken) supporting confidential votes tracking and delegation. + +The amount of confidential voting units an account has is equal to the confidential token balance of +that account. Voing power is taken into account when an account delegates votes to itself or to another +account. + +
+

Functions

+
+- [confidentialTotalSupply()](#ConfidentialFungibleTokenVotes-confidentialTotalSupply--) +- [_update(from, to, amount)](#ConfidentialFungibleTokenVotes-_update-address-address-euint64-) +- [_getVotingUnits(account)](#ConfidentialFungibleTokenVotes-_getVotingUnits-address-) +#### VotesConfidential [!toc] +- [clock()](#VotesConfidential-clock--) +- [CLOCK_MODE()](#VotesConfidential-CLOCK_MODE--) +- [getVotes(account)](#VotesConfidential-getVotes-address-) +- [getPastVotes(account, timepoint)](#VotesConfidential-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#VotesConfidential-getPastTotalSupply-uint256-) +- [delegates(account)](#VotesConfidential-delegates-address-) +- [delegate(delegatee)](#VotesConfidential-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#VotesConfidential-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#VotesConfidential-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#VotesConfidential-_transferVotingUnits-address-address-euint64-) +- [_moveDelegateVotes(from, to, amount)](#VotesConfidential-_moveDelegateVotes-address-address-euint64-) +- [_validateTimepoint(timepoint)](#VotesConfidential-_validateTimepoint-uint256-) +#### HandleAccessManager [!toc] +- [getHandleAllowance(handle, account, persistent)](#HandleAccessManager-getHandleAllowance-bytes32-address-bool-) +- [_validateHandleAllowance(handle)](#HandleAccessManager-_validateHandleAllowance-bytes32-) +#### IERC6372 [!toc] +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### ConfidentialFungibleToken [!toc] +- [name()](#ConfidentialFungibleToken-name--) +- [symbol()](#ConfidentialFungibleToken-symbol--) +- [decimals()](#ConfidentialFungibleToken-decimals--) +- [tokenURI()](#ConfidentialFungibleToken-tokenURI--) +- [confidentialBalanceOf(account)](#ConfidentialFungibleToken-confidentialBalanceOf-address-) +- [isOperator(holder, spender)](#ConfidentialFungibleToken-isOperator-address-address-) +- [setOperator(operator, until)](#ConfidentialFungibleToken-setOperator-address-uint48-) +- [confidentialTransfer(to, encryptedAmount, inputProof)](#ConfidentialFungibleToken-confidentialTransfer-address-externalEuint64-bytes-) +- [confidentialTransfer(to, amount)](#ConfidentialFungibleToken-confidentialTransfer-address-euint64-) +- [confidentialTransferFrom(from, to, encryptedAmount, inputProof)](#ConfidentialFungibleToken-confidentialTransferFrom-address-address-externalEuint64-bytes-) +- [confidentialTransferFrom(from, to, amount)](#ConfidentialFungibleToken-confidentialTransferFrom-address-address-euint64-) +- [confidentialTransferAndCall(to, encryptedAmount, inputProof, data)](#ConfidentialFungibleToken-confidentialTransferAndCall-address-externalEuint64-bytes-bytes-) +- [confidentialTransferAndCall(to, amount, data)](#ConfidentialFungibleToken-confidentialTransferAndCall-address-euint64-bytes-) +- [confidentialTransferFromAndCall(from, to, encryptedAmount, inputProof, data)](#ConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-externalEuint64-bytes-bytes-) +- [confidentialTransferFromAndCall(from, to, amount, data)](#ConfidentialFungibleToken-confidentialTransferFromAndCall-address-address-euint64-bytes-) +- [discloseEncryptedAmount(encryptedAmount)](#ConfidentialFungibleToken-discloseEncryptedAmount-euint64-) +- [finalizeDiscloseEncryptedAmount(requestId, amount, signatures)](#ConfidentialFungibleToken-finalizeDiscloseEncryptedAmount-uint256-uint64-bytes---) +- [_setOperator(holder, operator, until)](#ConfidentialFungibleToken-_setOperator-address-address-uint48-) +- [_mint(to, amount)](#ConfidentialFungibleToken-_mint-address-euint64-) +- [_burn(from, amount)](#ConfidentialFungibleToken-_burn-address-euint64-) +- [_transfer(from, to, amount)](#ConfidentialFungibleToken-_transfer-address-address-euint64-) +- [_transferAndCall(from, to, amount, data)](#ConfidentialFungibleToken-_transferAndCall-address-address-euint64-bytes-) +#### IConfidentialFungibleToken [!toc] +
+
+ +
+

Events

+
+#### VotesConfidential [!toc] +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#VotesConfidential-DelegateVotesChanged-address-euint64-euint64-) +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#VotesConfidential-DelegateChanged-address-address-address-) +#### HandleAccessManager [!toc] +#### IERC6372 [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### Nonces [!toc] +#### ConfidentialFungibleToken [!toc] +#### IConfidentialFungibleToken [!toc] +- [OperatorSet(holder, operator, until)](#IConfidentialFungibleToken-OperatorSet-address-address-uint48-) +- [ConfidentialTransfer(from, to, amount)](#IConfidentialFungibleToken-ConfidentialTransfer-address-address-euint64-) +- [AmountDisclosed(encryptedAmount, amount)](#IConfidentialFungibleToken-AmountDisclosed-euint64-uint64-) +
+
+ +
+

Errors

+
+#### VotesConfidential [!toc] +- [VotesExpiredSignature(expiry)](#VotesConfidential-VotesExpiredSignature-uint256-) +- [ERC6372InconsistentClock()](#VotesConfidential-ERC6372InconsistentClock--) +- [ERC5805FutureLookup(timepoint, clock)](#VotesConfidential-ERC5805FutureLookup-uint256-uint48-) +#### HandleAccessManager [!toc] +#### IERC6372 [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### ConfidentialFungibleToken [!toc] +- [ConfidentialFungibleTokenInvalidReceiver(receiver)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidReceiver-address-) +- [ConfidentialFungibleTokenInvalidSender(sender)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidSender-address-) +- [ConfidentialFungibleTokenUnauthorizedSpender(holder, spender)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedSpender-address-address-) +- [ConfidentialFungibleTokenZeroBalance(holder)](#ConfidentialFungibleToken-ConfidentialFungibleTokenZeroBalance-address-) +- [ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount(amount, user)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedUseOfEncryptedAmount-euint64-address-) +- [ConfidentialFungibleTokenUnauthorizedCaller(caller)](#ConfidentialFungibleToken-ConfidentialFungibleTokenUnauthorizedCaller-address-) +- [ConfidentialFungibleTokenInvalidGatewayRequest(requestId)](#ConfidentialFungibleToken-ConfidentialFungibleTokenInvalidGatewayRequest-uint256-) +#### IConfidentialFungibleToken [!toc] +
+
+ + + +
+
+

confidentialTotalSupply() → euint64

+
+

public

+# +
+
+
+ +Returns the confidential total supply of the token. + +
+
+ + + +
+
+

_update(address from, address to, euint64 amount) → euint64 transferred

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_getVotingUnits(address account) → euint64

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `ConfidentialFungibleTokenUtils` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/token/utils/ConfidentialFungibleTokenUtils.sol"; +``` + +Library that provides common [`ConfidentialFungibleToken`](#ConfidentialFungibleToken) utility functions. + +
+

Functions

+
+- [checkOnTransferReceived(operator, from, to, amount, data)](#ConfidentialFungibleTokenUtils-checkOnTransferReceived-address-address-address-euint64-bytes-) +
+
+ + + +
+
+

checkOnTransferReceived(address operator, address from, address to, euint64 amount, bytes data) → ebool

+
+

internal

+# +
+
+
+ +Performs a transfer callback to the recipient of the transfer `to`. Should be invoked +after all transfers "withCallback" on a [`ConfidentialFungibleToken`](#ConfidentialFungibleToken). + +The transfer callback is not invoked on the recipient if the recipient has no code (i.e. is an EOA). If the +recipient has non-zero code, it must implement +[`IConfidentialFungibleTokenReceiver.onConfidentialTransferReceived`](./interfaces#IConfidentialFungibleTokenReceiver-onConfidentialTransferReceived-address-address-euint64-bytes-) and return an `ebool` indicating +whether the transfer was accepted or not. If the `ebool` is `false`, the transfer will be reversed. + +
+
diff --git a/docs/content/confidential-contracts/api/utils.mdx b/docs/content/confidential-contracts/api/utils.mdx new file mode 100644 index 00000000..600364e3 --- /dev/null +++ b/docs/content/confidential-contracts/api/utils.mdx @@ -0,0 +1,1191 @@ +--- +title: "Utils" +description: "Smart contract utils utilities and implementations" +--- + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, and ease integrations when working with confidential contracts. + +* [`FHESafeMath`](#FHESafeMath): Implementation of safe math operations for encrypted values. +* [`CheckpointsConfidential`](#CheckpointsConfidential): Implementation of checkpoints for encrypted values. +* [`HandleAccessManager`](#HandleAccessManager): Minimal contract that adds a function to give allowance to callers for a given ciphertext handle. + +## Math + +[`FHESafeMath`](#FHESafeMath) + +## Structs + +[`CheckpointsConfidential`](#CheckpointsConfidential) + +## Other +[`HandleAccessManager`](#HandleAccessManager) + + + +
+ +## `FHESafeMath` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/utils/FHESafeMath.sol"; +``` + +Library providing safe arithmetic operations for encrypted values +to handle potential overflows in FHE operations. + +
+

Functions

+
+- [tryIncrease(oldValue, delta)](#FHESafeMath-tryIncrease-euint64-euint64-) +- [tryDecrease(oldValue, delta)](#FHESafeMath-tryDecrease-euint64-euint64-) +
+
+ + + +
+
+

tryIncrease(euint64 oldValue, euint64 delta) → ebool success, euint64 updated

+
+

internal

+# +
+
+
+ +Try to increase the encrypted value `oldValue` by `delta`. If the operation is successful, +`success` will be true and `updated` will be the new value. Otherwise, `success` will be false +and `updated` will be the original value. + +
+
+ + + +
+
+

tryDecrease(euint64 oldValue, euint64 delta) → ebool success, euint64 updated

+
+

internal

+# +
+
+
+ +Try to decrease the encrypted value `oldValue` by `delta`. If the operation is successful, +`success` will be true and `updated` will be the new value. Otherwise, `success` will be false +and `updated` will be the original value. + +
+
+ + + +
+ +## `HandleAccessManager` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/utils/HandleAccessManager.sol"; +``` + +
+

Functions

+
+- [getHandleAllowance(handle, account, persistent)](#HandleAccessManager-getHandleAllowance-bytes32-address-bool-) +- [_validateHandleAllowance(handle)](#HandleAccessManager-_validateHandleAllowance-bytes32-) +
+
+ + + +
+
+

getHandleAllowance(bytes32 handle, address account, bool persistent)

+
+

public

+# +
+
+
+ +Get handle access for the given handle `handle`. Access will be given to the +account `account` with the given persistence flag. + + +This function call is gated by `msg.sender` and validated by the +[`HandleAccessManager._validateHandleAllowance`](#HandleAccessManager-_validateHandleAllowance-bytes32-) function. + + +
+
+ + + +
+
+

_validateHandleAllowance(bytes32 handle)

+
+

internal

+# +
+
+
+ +Unimplemented function that must revert if the message sender is not allowed to call +[`HandleAccessManager.getHandleAllowance`](#HandleAccessManager-getHandleAllowance-bytes32-address-bool-) for the given handle. + +
+
+ + + +
+ +## `CheckpointsConfidential` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/utils/structs/CheckpointsConfidential.sol"; +``` + +This library defines the `Trace*` struct, for checkpointing values as they change at different points in +time, and later looking up past values by block number. + +To create a history of checkpoints, define a variable type `CheckpointsConfidential.Trace*` in your contract, and store a new +checkpoint for the current transaction block using the [`CheckpointsConfidential.push`](#CheckpointsConfidential-push-struct-CheckpointsConfidential-TraceEuint64-uint256-euint64-) function. + +
+

Functions

+
+- [push(self, key, value)](#CheckpointsConfidential-push-struct-CheckpointsConfidential-TraceEuint32-uint256-euint32-) +- [lowerLookup(self, key)](#CheckpointsConfidential-lowerLookup-struct-CheckpointsConfidential-TraceEuint32-uint256-) +- [upperLookup(self, key)](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint32-uint256-) +- [upperLookupRecent(self, key)](#CheckpointsConfidential-upperLookupRecent-struct-CheckpointsConfidential-TraceEuint32-uint256-) +- [latest(self)](#CheckpointsConfidential-latest-struct-CheckpointsConfidential-TraceEuint32-) +- [latestCheckpoint(self)](#CheckpointsConfidential-latestCheckpoint-struct-CheckpointsConfidential-TraceEuint32-) +- [length(self)](#CheckpointsConfidential-length-struct-CheckpointsConfidential-TraceEuint32-) +- [at(self, pos)](#CheckpointsConfidential-at-struct-CheckpointsConfidential-TraceEuint32-uint32-) +- [push(self, key, value)](#CheckpointsConfidential-push-struct-CheckpointsConfidential-TraceEuint64-uint256-euint64-) +- [lowerLookup(self, key)](#CheckpointsConfidential-lowerLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) +- [upperLookup(self, key)](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) +- [upperLookupRecent(self, key)](#CheckpointsConfidential-upperLookupRecent-struct-CheckpointsConfidential-TraceEuint64-uint256-) +- [latest(self)](#CheckpointsConfidential-latest-struct-CheckpointsConfidential-TraceEuint64-) +- [latestCheckpoint(self)](#CheckpointsConfidential-latestCheckpoint-struct-CheckpointsConfidential-TraceEuint64-) +- [length(self)](#CheckpointsConfidential-length-struct-CheckpointsConfidential-TraceEuint64-) +- [at(self, pos)](#CheckpointsConfidential-at-struct-CheckpointsConfidential-TraceEuint64-uint32-) +
+
+ + + +
+
+

push(struct CheckpointsConfidential.TraceEuint32 self, uint256 key, euint32 value) → euint32 oldValue, euint32 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a TraceEuint32 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint256).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct CheckpointsConfidential.TraceEuint32 self, uint256 key) → euint32

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct CheckpointsConfidential.TraceEuint32 self, uint256 key) → euint32

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct CheckpointsConfidential.TraceEuint32 self, uint256 key) → euint32

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`CheckpointsConfidential.upperLookup`](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct CheckpointsConfidential.TraceEuint32 self) → euint32

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct CheckpointsConfidential.TraceEuint32 self) → bool exists, uint256 key, euint32 value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct CheckpointsConfidential.TraceEuint32 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct CheckpointsConfidential.TraceEuint32 self, uint32 pos) → uint256 key, euint32 value

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct CheckpointsConfidential.TraceEuint64 self, uint256 key, euint64 value) → euint64 oldValue, euint64 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a TraceEuint64 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint256).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct CheckpointsConfidential.TraceEuint64 self, uint256 key) → euint64

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct CheckpointsConfidential.TraceEuint64 self, uint256 key) → euint64

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct CheckpointsConfidential.TraceEuint64 self, uint256 key) → euint64

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`CheckpointsConfidential.upperLookup`](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct CheckpointsConfidential.TraceEuint64 self) → euint64

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct CheckpointsConfidential.TraceEuint64 self) → bool exists, uint256 key, euint64 value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct CheckpointsConfidential.TraceEuint64 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct CheckpointsConfidential.TraceEuint64 self, uint32 pos) → uint256 key, euint64 value

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+ +## `Checkpoints` + + + + + +
+ +```solidity +import "@openzeppelin/confidential-contracts/utils/structs/temporary-Checkpoints.sol"; +``` + +This library defines the `Trace*` struct, for checkpointing values as they change at different points in +time, and later looking up past values by block number. See [`VotesConfidential`](./governance#VotesConfidential) as an example. + +To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new +checkpoint for the current transaction block using the [`CheckpointsConfidential.push`](#CheckpointsConfidential-push-struct-CheckpointsConfidential-TraceEuint64-uint256-euint64-) function. + +
+

Functions

+
+- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace256-uint256-uint256-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace256-uint256-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace256-uint256-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace256-uint256-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace256-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace256-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace256-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace256-uint32-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace224-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace224-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace224-uint32-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace208-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace208-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace208-uint32-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace160-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace160-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace160-uint32-) +
+
+ +
+

Errors

+
+- [CheckpointUnorderedInsertion()](#Checkpoints-CheckpointUnorderedInsertion--) +
+
+ + + +
+
+

push(struct Checkpoints.Trace256 self, uint256 key, uint256 value) → uint256 oldValue, uint256 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace256 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint256).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace256 self, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace256 self, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace256 self, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`CheckpointsConfidential.upperLookup`](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace256 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace256 self) → bool exists, uint256 _key, uint256 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace256 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace256 self, uint32 pos) → struct Checkpoints.Checkpoint256

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace224 self, uint32 key, uint224 value) → uint224 oldValue, uint224 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`CheckpointsConfidential.upperLookup`](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace224 self) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace224 self) → bool exists, uint32 _key, uint224 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace224 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace224 self, uint32 pos) → struct Checkpoints.Checkpoint224

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace208 self, uint48 key, uint208 value) → uint208 oldValue, uint208 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace208 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace208 self, uint48 key) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace208 self, uint48 key) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace208 self, uint48 key) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`CheckpointsConfidential.upperLookup`](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace208 self) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace208 self) → bool exists, uint48 _key, uint208 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace208 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace208 self, uint32 pos) → struct Checkpoints.Checkpoint208

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace160 self, uint96 key, uint160 value) → uint160 oldValue, uint160 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`CheckpointsConfidential.upperLookup`](#CheckpointsConfidential-upperLookup-struct-CheckpointsConfidential-TraceEuint64-uint256-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace160 self) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace160 self) → bool exists, uint96 _key, uint160 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace160 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace160 self, uint32 pos) → struct Checkpoints.Checkpoint160

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

CheckpointUnorderedInsertion()

+
+

error

+# +
+
+
+ +A value was attempted to be inserted on a past checkpoint. + +
+
diff --git a/docs/content/confidential-contracts/changelog.mdx b/docs/content/confidential-contracts/changelog.mdx new file mode 100644 index 00000000..5a14e01f --- /dev/null +++ b/docs/content/confidential-contracts/changelog.mdx @@ -0,0 +1,47 @@ +--- +title: Changelog +--- + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/releases/tag/v0.2.0) - 2025-08-15 + +- Upgrade all contracts to use `@fhevm/solidity` 0.7.0. ([#27](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/27)) + +### Token +- `IConfidentialFungibleToken`: Prefix `totalSupply` and `balanceOf` functions with confidential. ([#93](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/93)) +- `IConfidentialFungibleToken`: Rename `EncryptedAmountDisclosed` event to `AmountDisclosed`. ([#93](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/93)) +- `ConfidentialFungibleToken`: Change the default decimals from 9 to 6. ([#74](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/74)) +- `ConfidentialFungibleTokenERC20Wrapper`: Add an internal function to allow overriding the max decimals used for wrapped tokens. ([#89](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/89)) +- `ConfidentialFungibleTokenERC20Wrapper`: Add an internal function to allow overriding the underlying decimals fallback value. ([#133](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/133)) + +### Governance +- `VotesConfidential`: Add votes governance utility for keeping track of FHE vote delegations. ([#40](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/40)) +- `ConfidentialFungibleTokenVotes`: Add an extension of `ConfidentialFungibleToken` that implements `VotesConfidential`. ([#40](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/40)) + +### Finance +- `VestingWalletConfidential`: A vesting wallet that releases confidential tokens owned by it according to a defined vesting schedule. ([#91](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/91)) +- `VestingWalletCliffConfidential`: A variant of `VestingWalletConfidential` which adds a cliff period to the vesting schedule. ([#91](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/91)) +- `VestingWalletConfidentialFactory`: A generalized factory that allows for batch funding of confidential vesting wallets. ([#102](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/102)) + +### Misc +- `HandleAccessManager`: Minimal contract that adds a function to give allowance to callers for a given ciphertext handle. ([#143](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/143)) +- `ERC7821WithExecutor`: Add an abstract contract that inherits from `ERC7821` and adds an `executor` role. ([#102](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/102)) +- `CheckpointsConfidential`: Add a library for handling checkpoints with confidential value types. ([#60](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/60)) +- `TFHESafeMath`: Renamed to `FHESafeMath`. ([#137](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/pull/137)) + + +[Changes][v0.2.0] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/releases/tag/v0.1.0) - 2025-06-05 + +Initial release of the OpenZeppelin Confidential Contracts. + +Note: Confidential contracts are currently in a phase of rapid development--future releases of the major version 0 may not be backwards compatible. + +[Changes][v0.1.0] + + +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/compare/v0.1.0...v0.2.0 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/v0.1.0 diff --git a/docs/content/confidential-contracts/index.mdx b/docs/content/confidential-contracts/index.mdx new file mode 100644 index 00000000..c70adf4c --- /dev/null +++ b/docs/content/confidential-contracts/index.mdx @@ -0,0 +1,17 @@ +--- +title: Confidential Contracts +--- + +A library of smart contracts that use ciphertext for amount, allowing for a wide variety of confidential use-cases, such as confidential tokens, auctions, vesting, voting etc. While the contracts are not written in an opinionated method (other than using the standard encrypted values published by Zama), for testing and examples in the documentation, the [Zama fhEVM](https://github.com/zama-ai/fhevm-solidity) will be used to operate on and decrypt [FHE](https://www.zama.ai/introduction-to-homomorphic-encryption) ciphertext. + + +All contracts must set their respective co-processor configuration during construction (or initialization). This can be done automatically by inheriting contracts in [`ZamaConfig`](https://github.com/zama-ai/fhevm/blob/v0.7.12/library-solidity/config/ZamaConfig.sol#L47-L73). + + +## Security + +Contracts in the confidential contracts library are provided as is, with no particular guarantees. Given changes in this repository are more frequent, the code is not formally audited and not covered by the [bug bounty program on Immunefi](https://www.immunefi.com/bounty/openzeppelin). + +Similarly, the code has no backward compatibility guarantees. + +We kindly ask to report any issue directly to our security [contact](mailto:security@openzeppelin.org). The team will do its best to assist and mitigate any potential misuses of the library. However, keep in mind the flexibility assumed for this repository may relax our assessment. diff --git a/docs/content/confidential-contracts/token.mdx b/docs/content/confidential-contracts/token.mdx new file mode 100644 index 00000000..188d1979 --- /dev/null +++ b/docs/content/confidential-contracts/token.mdx @@ -0,0 +1,109 @@ +--- +title: ERC7984 +--- + +[`ERC7984`](/confidential-contracts/api/token#ERC7984) is a standard fungible token implementation that is similar to ERC-20, but built from the ground up with confidentiality in mind. All balance and transfer amounts are represented as ciphertext handles, ensuring that no data is leaked to the public. + +While the standard is built with inspiration from ERC-20, it is not ERC-20 compliant--the standard takes learning from all tokens built over the past 10 years (ERC-20, ERC-721, ERC-1155, ERC-6909 etc) and provides an interface for maximal functionality. + +## Usage + +### Transfer + +The token standard exposes eight different transfer functions. They are all permutations of the following options: + +* `transfer` and `transferFrom`: `transfer` moves tokens from the sender while `transferFrom` moves tokens from a specified `from` address. See [operator](#operator). +* With and without `inputProof`: An `inputProof` can be provided to prove that the sender knows the value of the ciphertext `amount` provided. +* With and without an `ERC1363` style callback: The standard implements callbacks, see the [callback](#callback) section for more details. + +Select the appropriate transfer function and generate a ciphertext using [fhevm-js](https://github.com/zama-ai/fhevm-js). If the ciphertext is a new value, or the sender does not have permission to access the ciphertext, an input-proof must be provided to show that the sender knows the value of the ciphertext. + +### Operator + +An operator is an address that has the ability to move tokens on behalf of another address by calling `transferFrom`. If Bob is an operator for Alice, Bob can move any amount of Alice’s tokens at any point in time. Operators are set using an expiration timestamp--this can be thought of as a limited duration infinite approval for an `ERC20`. Below is an example of setting Bob as an operator for Alice for 24 hours. + +```typescript +const alice: Wallet; +const expirationTimestamp = Math.round(Date.now()) + 60 * 60 * 24; // Now + 24 hours + +await tokenContract.connect(alice).setOperator(bob, expirationTimestamp); +``` + + +Operators do not have allowance to reencrypt/decrypt balance handles for other addresses. This means that operators cannot transfer full balances and can only know success after a transaction (by decrypting the transferred amount). + + + +Setting an operator for any amount of time allows the operator to _***take all of your tokens***_. Carefully vet all potential operators before giving operator approval. + + +### Callback + +The token standard exposes transfer functions with and without callbacks. It is up to the caller to decide if a callback is necessary for the transfer. For smart contracts that support it, callbacks allow the operator approval step to be skipped and directly invoke the receiver contract via a callback. + +Smart contracts that are the target of a callback must implement [`IERC7984Receiver`](/confidential-contracts/api/interfaces#IERC7984Receiver). After balances are updated for a transfer, the callback is triggered by calling the [`onConfidentialTransferReceived`](/confidential-contracts/api/interfaces#IERC7984Receiver-onConfidentialTransferReceived-address-address-euint64-bytes-) function. The function must either revert or return an `ebool` indicating success. If the callback returns false, the token transfer is reversed. + +## Examples + +### Privileged Minter/Burner + +Here is an example of a contract for a confidential fungible token with a privileged minter and burner. + +./examples/ERC7984MintableBurnable.sol + +### Swaps + +Swapping is one of the most primitive use-cases for fungible tokens. Below are examples for swapping between confidential and non-confidential tokens. + +#### Swap `ERC20` to `ERC7984` + +Swapping from a non-confidential `ERC20` to a confidential `ERC7984` is simple and actually done within the `ERC7984ERC20Wrapper`. See the excerpt from the `wrap` function below. + +```solidity +function wrap(address to, uint256 amount) public virtual { + // take ownership of the tokens + SafeERC20.safeTransferFrom(underlying(), msg.sender, address(this), amount - (amount % rate())); + + // mint confidential token + _mint(to, (amount / rate()).toUint64().asEuint64()); +} +``` + +The `ERC20` token is simply transferred in, which would revert on failure. We then transfer out the correct amount of the `ERC7984` using the internal `_mint` function, which is guaranteed to succeed. + +#### Swap `ERC7984` to `ERC7984` + +Swapping from a confidential `ERC7984` to another confidential `ERC7984` is a bit more complex although quite simple given the usage of the `FHE` library. For the sake of the example, we will swap from `fromToken` to `toToken` with a 1:1 exchange rate. + +```solidity + function swapConfidentialForConfidential( + IERC7984 fromToken, + IERC7984 toToken, + externalEuint64 amountInput, + bytes calldata inputProof + ) public virtual { + require(fromToken.isOperator(msg.sender, address(this))); + + euint64 amount = FHE.fromExternal(amountInput, inputProof); + + FHE.allowTransient(amount, address(fromToken)); + euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount); + + FHE.allowTransient(amountTransferred, address(toToken)); + toToken.confidentialTransfer(msg.sender, amountTransferred); + } +``` + +The steps are as follows: + +1. Check operator approval +2. Allow the `fromToken` to access `amount` +3. Transfer from `from` to this contract for `amount` +4. Allow the `toToken` to access `amountTransferred` +5. Transfer `amountTransferred` to `msg.sender` + +#### Swap `ERC7984` to `ERC20` + +Swapping from a confidential token to a non-confidential token is the most complex since the decrypted data must be accessed to accurately complete the request. Decryption in our example will be done off-chain and relayed back using Zama’s Gateway. Below is an example of a contract doing a 1:1 swap from a confidential token to an ERC20 token. + +./examples/SwapERC7984ToERC20.sol diff --git a/docs/content/contracts-cairo/2.x/access.mdx b/docs/content/contracts-cairo/2.x/access.mdx new file mode 100644 index 00000000..0c982822 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/access.mdx @@ -0,0 +1,513 @@ +--- +title: Access +--- + +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 +[steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/). + +## 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 for Cairo provides [OwnableComponent](/contracts-cairo/2.x/api/access#OwnableComponent) for implementing ownership in your contracts. + +### Usage + +Integrating this component into a contract first requires assigning an owner. +The implementing contract’s constructor should set the initial owner by passing the owner’s address to Ownable’s +[`initializer`](/contracts-cairo/2.x/api/access#OwnableComponent-initializer) like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_access::ownable::OwnableComponent; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl InternalImpl = OwnableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + // Set the initial owner of the contract + self.ownable.initializer(owner); + } + + (...) +} +``` + +To restrict a function’s access to the owner only, add in the `assert_only_owner` method: + +```rust +#[starknet::contract] +mod MyContract { + (...) + + #[external(v0)] + fn only_owner_allowed(ref self: ContractState) { + // This function can only be called by the owner + self.ownable.assert_only_owner(); + + (...) + } +} +``` + +### Interface + +This is the full interface of the `OwnableMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait OwnableABI { + // IOwnable + fn owner() -> ContractAddress; + fn transfer_ownership(new_owner: ContractAddress); + fn renounce_ownership(); + + // IOwnableCamelOnly + fn transferOwnership(newOwner: ContractAddress); + fn renounceOwnership(); +} +``` + +Ownable also lets you: + +* `transfer_ownership` from the owner account to a new one, and +* `renounce_ownership` for the owner to relinquish this administrative privilege, a common pattern +after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner` +will no longer be callable! + + +### Two step transfer + +The component also offers a more robust way of transferring ownership via the +[OwnableTwoStepImpl](/contracts-cairo/2.x/api/access#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl) implementation. A two step transfer mechanism helps +to prevent unintended and irreversible owner transfers. Simply replace the `OwnableMixinImpl` +with its respective two step variant: + +```rust +#[abi(embed_v0)] +impl OwnableTwoStepMixinImpl = OwnableComponent::OwnableTwoStepMixinImpl; +``` + +#### Interface + +This is the full interface of the two step `OwnableTwoStepMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait OwnableTwoStepABI { + // IOwnableTwoStep + fn owner() -> ContractAddress; + fn pending_owner() -> ContractAddress; + fn accept_ownership(); + fn transfer_ownership(new_owner: ContractAddress); + fn renounce_ownership(); + + // IOwnableTwoStepCamelOnly + fn pendingOwner() -> ContractAddress; + fn acceptOwnership(); + fn transferOwnership(newOwner: ContractAddress); + fn renounceOwnership(); +} +``` + +## Role-Based `AccessControl` + +While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of +authorization are often needed. You may want for an account to have permission to ban users from a system, but not +create new tokens. [Role-Based Access Control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) 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 [`assert_only_owner`](/contracts-cairo/2.x/api/access#OwnableComponent-assert_only_owner). This check can be enforced through [`assert_only_role`](/contracts-cairo/2.x/api/access#AccessControlComponent-assert_only_role). +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. + +### Usage + +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. See [Creating role identifiers](#creating-role-identifiers) for information +on creating identifiers. + +Here’s a simple example of implementing [AccessControl](/contracts-cairo/2.x/api/access#AccessControlComponent) on a portion of an ERC20 token contract which defines +and sets a 'minter' role: + +```rust +const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); + +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + use super::MINTER_ROLE; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress, + minter: ContractAddress + ) { + // ERC20-related initialization + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(MINTER_ROLE, minter); + } + + /// This function can only be called by a minter. + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(MINTER_ROLE); + self.erc20.mint(recipient, amount); + } +} +``` + + +Make sure you fully understand how [AccessControl](/contracts-cairo/2.x/api/access#AccessControlComponent) 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](/contracts-cairo/2.x/api/access#OwnableComponent). Where [AccessControl](/contracts-cairo/2.x/api/access#AccessControlComponent) shines the most is in scenarios where granular +permissions are required, which can be implemented by defining _multiple_ roles. + +Let’s augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: + +```rust +const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); +const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); + +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + use super::{MINTER_ROLE, BURNER_ROLE}; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress, + minter: ContractAddress, + burner: ContractAddress + ) { + // ERC20-related initialization + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(MINTER_ROLE, minter); + self.accesscontrol._grant_role(BURNER_ROLE, burner); + } + + /// This function can only be called by a minter. + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(MINTER_ROLE); + self.erc20.mint(recipient, amount); + } + + /// This function can only be called by a burner. + #[external(v0)] + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(BURNER_ROLE); + self.erc20.burn(account, amount); + } +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 roles + +The ERC20 token example above uses [`_grant_role`](/contracts-cairo/2.x/api/access#AccessControlComponent-_grant_role), +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 [`assert_only_role`](/contracts-cairo/2.x/api/access#AccessControlComponent-assert_only_role) 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 +[`grant_role`](/contracts-cairo/2.x/api/access#AccessControlComponent-grant_role) and +[`revoke_role`](/contracts-cairo/2.x/api/access#AccessControlComponent-revoke_role) 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 with the role identifier +of `0`, 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 +[`set_role_admin`](/contracts-cairo/2.x/api/access#AccessControlComponent-set_role_admin) is used to select a new admin role. + +Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: + +```rust +const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); +const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); + +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + use super::{MINTER_ROLE, BURNER_ROLE}; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress, + admin: ContractAddress + ) { + // ERC20-related initialization + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin); + } + + /// This function can only be called by a minter. + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(MINTER_ROLE); + self.erc20.mint(recipient, amount); + } + + /// This function can only be called by a burner. + #[external(v0)] + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(BURNER_ROLE); + self.erc20.burn(account, amount); + } +} +``` + + +The `grant_role` and `revoke_role` functions are automatically exposed as `external` functions +from the `AccessControlImpl` by leveraging the `#[abi(embed_v0)]` annotation. + + +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 the 'admin', that +same account can call `grant_role` to give minting or burning permission, and `revoke_role` 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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), +where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. + +### Creating role identifiers + +In the Solidity implementation of AccessControl, contracts generally refer to the +[keccak256 hash](https://docs.soliditylang.org/en/latest/units-and-global-variables.html?highlight=keccak256#mathematical-and-cryptographic-functions) +of a role as the role identifier. + +For example: + +```rust +bytes32 public constant SOME_ROLE = keccak256("SOME_ROLE") +``` + +These identifiers take up 32 bytes (256 bits). + +Cairo field elements (`felt252`) store a maximum of 252 bits. +With this discrepancy, this library maintains an agnostic stance on how contracts should create identifiers. +Some ideas to consider: + +* Use [sn_keccak](https://docs.starknet.io/architecture-and-concepts/cryptography/#starknet_keccak) instead. +* Use Cairo friendly hashing algorithms like Poseidon, which are implemented in the +[Cairo corelib](https://github.com/starkware-libs/cairo/blob/main/corelib/src/poseidon.cairo). + + +The `selector!` macro can be used to compute [sn_keccak](https://docs.starknet.io/architecture-and-concepts/cryptography/#starknet_keccak) in Cairo. + + +### Interface + +This is the full interface of the `AccessControlMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait AccessControlABI { + // IAccessControl + fn has_role(role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(role: felt252) -> felt252; + fn grant_role(role: felt252, account: ContractAddress); + fn revoke_role(role: felt252, account: ContractAddress); + fn renounce_role(role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(role: felt252) -> felt252; + fn grantRole(role: felt252, account: ContractAddress); + fn revokeRole(role: felt252, account: ContractAddress); + fn renounceRole(role: felt252, account: ContractAddress); + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +`AccessControl` also lets you `renounce_role` from the calling account. +The method expects an account as input as an extra security measure, to ensure you are +not renouncing a role from an unintended account. diff --git a/docs/content/contracts-cairo/2.x/accounts.mdx b/docs/content/contracts-cairo/2.x/accounts.mdx new file mode 100644 index 00000000..fbc2ec48 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/accounts.mdx @@ -0,0 +1,504 @@ +--- +title: Accounts +--- + +Unlike Ethereum where accounts are derived from a private key, all Starknet accounts are contracts. This means there’s no Externally Owned Account (EOA) +concept on Starknet. + +Instead, the network features native account abstraction and signature validation happens at the contract level. + +For a general overview of account abstraction, see +[Starknet’s documentation](https://docs.starknet.io/architecture-and-concepts/accounts/introduction/). +A more detailed discussion on the topic can be found in +[Starknet Shaman’s forum](https://community.starknet.io/t/starknet-account-abstraction-model-part-1/781). + + +For detailed information on the usage and implementation check the [API Reference](/contracts-cairo/2.x/api/account) section. + + +## What is an account? + +Accounts in Starknet are smart contracts, and so they can be deployed and interacted +with like any other contract, and can be extended to implement any custom logic. However, an account is a special type +of contract that is used to validate and execute transactions. For this reason, it must implement a set of entrypoints +that the protocol uses for this execution flow. The [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) proposal defines a standard interface for accounts, +supporting this execution flow and interoperability with DApps in the ecosystem. + +### ISRC6 Interface + +```rust +/// Represents a call to a target contract function. +struct Call { + to: ContractAddress, + selector: felt252, + calldata: Span +} + +/// Standard Account Interface +#[starknet::interface] +pub trait ISRC6 { + /// Executes a transaction through the account. + fn __execute__(calls: Array); + + /// Asserts whether the transaction is valid to be executed. + fn __validate__(calls: Array) -> felt252; + + /// Asserts whether a given signature for a given hash is valid. + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; +} +``` + + +The `calldata` member of the `Call` struct in the accounts has been updated to `Span` for optimization +purposes, but the interface ID remains the same for backwards compatibility. This inconsistency will be fixed in future releases. + + +[SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) adds the `is_valid_signature` method. This method is not used by the protocol, but it’s useful for +DApps to verify the validity of signatures, supporting features like Sign In with Starknet. + +SNIP-6 also defines that compliant accounts must implement the SRC5 interface following [SNIP-5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md), as +a mechanism for detecting whether a contract is an account or not through introspection. + +### ISRC5 Interface + +```rust +/// Standard Interface Detection +#[starknet::interface] +pub trait ISRC5 { + /// Queries if a contract implements a given interface. + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +[SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) compliant accounts must return `true` when queried for the ISRC6 interface ID. + +Even though these interfaces are not enforced by the protocol, it’s recommended to implement them for enabling +interoperability with the ecosystem. + +### Protocol-level methods + +The Starknet protocol uses a few entrypoints for abstracting the accounts. We already mentioned the first two +as part of the ISRC6 interface, and both are required for enabling accounts to be used for executing transactions. The rest are optional: + +1. `__validate__` verifies the validity of the transaction to be executed. This is usually used to validate signatures, +but the entrypoint implementation can be customized to feature any validation mechanism [with some limitations](https://docs.starknet.io/architecture-and-concepts/accounts/account-functions/#limitations_of_validation). +2. `__execute__` executes the transaction if the validation is successful. +3. `__validate_declare__` optional entrypoint similar to `__validate__` but for transactions +meant to declare other contracts. +4. `__validate_deploy__` optional entrypoint similar to `__validate__` but meant for [counterfactual deployments](./guides/deployment). + + +Although these entrypoints are available to the protocol for its regular transaction flow, they can also be called like any other method. + + +## Starknet Account + +Starknet native account abstraction pattern allows for the creation of custom accounts with different validation schemes, but +usually most account implementations validate transactions using the [Stark curve](https://docs.starknet.io/architecture-and-concepts/cryptography/#stark-curve) which is the most efficient way +of validating signatures since it is a STARK-friendly curve. + +OpenZeppelin Contracts for Cairo provides [AccountComponent](/contracts-cairo/2.x/api/account#AccountComponent) for implementing this validation scheme. + +### Usage + +Constructing an account contract requires integrating both [AccountComponent](/contracts-cairo/2.x/api/account#AccountComponent) and [SRC5Component](/contracts-cairo/2.x/api/introspection#SRC5Component). The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. Here’s an example of a basic contract: + +```rust +#[starknet::contract(account)] +mod MyAccount { + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Account Mixin + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +``` + +### Interface + +This is the full interface of the `AccountMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait AccountABI { + // ISRC6 + fn __execute__(calls: Array); + fn __validate__(calls: Array) -> felt252; + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IDeclarer + fn __validate_declare__(class_hash: felt252) -> felt252; + + // IDeployable + fn __validate_deploy__( + class_hash: felt252, contract_address_salt: felt252, public_key: felt252 + ) -> felt252; + + // IPublicKey + fn get_public_key() -> felt252; + fn set_public_key(new_public_key: felt252, signature: Span); + + // ISRC6CamelOnly + fn isValidSignature(hash: felt252, signature: Array) -> felt252; + + // IPublicKeyCamel + fn getPublicKey() -> felt252; + fn setPublicKey(newPublicKey: felt252, signature: Span); +} +``` + +## Ethereum Account + +Besides the Stark-curve account, OpenZeppelin Contracts for Cairo also offers Ethereum-flavored accounts that use the [secp256k1](https://en.bitcoin.it/wiki/Secp256k1) curve for signature validation. +For this the [EthAccountComponent](/contracts-cairo/2.x/api/account#EthAccountComponent) must be used. + +### Usage + +Constructing a secp256k1 account contract also requires integrating both [EthAccountComponent](/contracts-cairo/2.x/api/account#EthAccountComponent) and [SRC5Component](/contracts-cairo/2.x/api/introspection#SRC5Component). +The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. +Here’s an example of a basic contract: + +```rust +#[starknet::contract(account)] +mod MyEthAccount { + use openzeppelin_account::EthAccountComponent; + use openzeppelin_account::interface::EthPublicKey; + use openzeppelin_introspection::src5::SRC5Component; + use starknet::ClassHash; + + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // EthAccount Mixin + #[abi(embed_v0)] + impl EthAccountMixinImpl = + EthAccountComponent::EthAccountMixinImpl; + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + eth_account: EthAccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + EthAccountEvent: EthAccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: EthPublicKey) { + self.eth_account.initializer(public_key); + } +} +``` + +### Interface + +This is the full interface of the `EthAccountMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait EthAccountABI { + // ISRC6 + fn __execute__(calls: Array); + fn __validate__(calls: Array) -> felt252; + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IDeclarer + fn __validate_declare__(class_hash: felt252) -> felt252; + + // IEthDeployable + fn __validate_deploy__( + class_hash: felt252, contract_address_salt: felt252, public_key: EthPublicKey + ) -> felt252; + + // IEthPublicKey + fn get_public_key() -> EthPublicKey; + fn set_public_key(new_public_key: EthPublicKey, signature: Span); + + // ISRC6CamelOnly + fn isValidSignature(hash: felt252, signature: Array) -> felt252; + + // IEthPublicKeyCamel + fn getPublicKey() -> EthPublicKey; + fn setPublicKey(newPublicKey: EthPublicKey, signature: Span); +} + +``` + +## Deploying an account + +In Starknet there are two ways of deploying smart contracts: using the `deploy_syscall` and doing +counterfactual deployments. +The former can be easily done with the [Universal Deployer Contract (UDC)](./udc), a contract that +wraps and exposes the `deploy_syscall` to provide arbitrary deployments through regular contract calls. +But if you don’t have an account to invoke it, you will probably want to use the latter. + +To do counterfactual deployments, you need to implement another protocol-level entrypoint named +`__validate_deploy__`. Check the [counterfactual deployments](./guides/deployment) guide to learn how. + +## Sending transactions + +Let’s now explore how to send transactions through these accounts. + +### Starknet Account + +First, let’s take the example account we created before and deploy it: + +```rust +#[starknet::contract(account)] +mod MyAccount { + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Account Mixin + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +``` + +To deploy the account variant, compile the contract and declare the class hash because custom accounts are likely not declared. +This means that you’ll need an account already deployed. + +Next, create the account JSON with Starknet Foundry’s [custom account setup](https://foundry-rs.github.io/starknet-foundry/starknet/account.html#custom-account-contract) and include the `--class-hash` flag with the declared class hash. +The flag enables custom account variants. + + +The following examples use `sncast` [v0.23.0](https://github.com/foundry-rs/starknet-foundry/releases/tag/v0.23.0). + + +```bash +$ sncast \ + --url http://127.0.0.1:5050 \ + account create \ + --name my-custom-account \ + --class-hash 0x123456... +``` + +This command will output the precomputed contract address and the recommended `max-fee`. +To counterfactually deploy the account, send funds to the address and then deploy the custom account. + +```bash +$ sncast \ + --url http://127.0.0.1:5050 \ + account deploy \ + --name my-custom-account +``` + +Once the account is deployed, set the `--account` flag with the custom account name to send transactions from that account. + +```bash +$ sncast \ + --account my-custom-account \ + --url http://127.0.0.1:5050 \ + invoke \ + --contract-address 0x123... \ + --function "some_function" \ + --calldata 1 2 3 +``` + +### Ethereum Account + +First, let’s take the example account we created before and deploy it: + +```rust +#[starknet::contract(account)] +mod MyEthAccount { + use openzeppelin_account::EthAccountComponent; + use openzeppelin_account::interface::EthPublicKey; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // EthAccount Mixin + #[abi(embed_v0)] + impl EthAccountMixinImpl = + EthAccountComponent::EthAccountMixinImpl; + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + eth_account: EthAccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + EthAccountEvent: EthAccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: EthPublicKey) { + self.eth_account.initializer(public_key); + } +} +``` + +Special tooling is required in order to deploy and send transactions with an Ethereum-flavored account contract. +The following examples utilize the [StarknetJS](https://www.starknetjs.com/) library. + +Compile and declare the contract on the target network. +Next, precompute the EthAccount contract address using the declared class hash. + + +The following examples use unreleased features from StarknetJS (`starknetjs@next`) at commit [d002baea0abc1de3ac6e87a671f3dec3757437b3](https://github.com/starknet-io/starknet.js/commit/d002baea0abc1de3ac6e87a671f3dec3757437b3). + + +```javascript +import * as dotenv from 'dotenv'; +import { CallData, EthSigner, hash } from 'starknet'; +import { ABI as ETH_ABI } from '../abis/eth_account.js'; +dotenv.config(); + +const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); +const ethPubKey = await ethSigner.getPubKey(); +const ethAccountClassHash = ''; +const ethCallData = new CallData(ETH_ABI); +const ethAccountConstructorCalldata = ethCallData.compile('constructor', { + public_key: ethPubKey +}) +const salt = '0x12345'; +const deployerAddress = '0x0'; +const ethContractAddress = hash.calculateContractAddressFromHash( + salt, + ethAccountClassHash, + ethAccountConstructorCalldata, + deployerAddress +); +console.log('Pre-calculated EthAccount address: ', ethContractAddress); +``` + +Send funds to the pre-calculated EthAccount address and deploy the contract. + +```javascript +import * as dotenv from 'dotenv'; +import { Account, CallData, EthSigner, RpcProvider, stark } from 'starknet'; +import { ABI as ETH_ABI } from '../abis/eth_account.js'; +dotenv.config(); + +const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); +const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); +const ethPubKey = await ethSigner.getPubKey(); +const ethAccountAddress = '' +const ethAccount = new Account(provider, ethAccountAddress, ethSigner); + +const ethAccountClassHash = '' +const ethCallData = new CallData(ETH_ABI); +const ethAccountConstructorCalldata = ethCallData.compile('constructor', { + public_key: ethPubKey +}) +const salt = '0x12345'; +const deployPayload = { + classHash: ethAccountClassHash, + constructorCalldata: ethAccountConstructorCalldata, + addressSalt: salt, +}; + +const { suggestedMaxFee: feeDeploy } = await ethAccount.estimateAccountDeployFee(deployPayload); +const { transaction_hash, contract_address } = await ethAccount.deployAccount( + deployPayload, + { maxFee: stark.estimatedFeeToMaxFee(feeDeploy, 100) } +); +await provider.waitForTransaction(transaction_hash); +console.log('EthAccount deployed at: ', contract_address); +``` + +Once deployed, connect the EthAccount instance to the target contract which enables calls to come from the EthAccount. +Here’s what an ERC20 transfer from an EthAccount looks like. + +```javascript +import * as dotenv from 'dotenv'; +import { Account, RpcProvider, Contract, EthSigner } from 'starknet'; +dotenv.config(); + +const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); +const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); +const ethAccountAddress = '' +const ethAccount = new Account(provider, ethAccountAddress, ethSigner); + +const erc20 = new Contract(compiledErc20.abi, erc20Address, provider); + +erc20.connect(ethAccount); + +const transferCall = erc20.populate('transfer', { + recipient: recipient.address, + amount: 50n +}); +const tx = await erc20.transfer( + transferCall.calldata, { maxFee: 900_000_000_000_000 } +); +await provider.waitForTransaction(tx.transaction_hash); +``` diff --git a/docs/content/contracts-cairo/2.x/api/access.mdx b/docs/content/contracts-cairo/2.x/api/access.mdx new file mode 100644 index 00000000..71fd194c --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/access.mdx @@ -0,0 +1,867 @@ +--- +title: Access Control +--- + +This crate provides ways to restrict who can access the functions of a contract or when they can do it. + +- [Ownable](#OwnableComponent) is a simple mechanism with a single "owner" role that can be assigned to a single account. This mechanism can be useful in simple scenarios, but fine grained access needs are likely to outgrow it. +- [AccessControl](#AccessControlComponent) provides a general role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts. + +## Interfaces + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `IAccessControl` [toc] [#IAccessControl] + + +```rust +use openzeppelin_access::accesscontrol::interface::IAccessControl; +``` + +External interface of AccessControl. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x23700be02858dbe2ac4dc9c9f66d0b6b0ed81ec7f970ca6844500a56ff61751 +``` + +Functions + +- [`has_role(role, account)`](#IAccessControl-has_role) +- [`get_role_admin(role)`](#IAccessControl-get_role_admin) +- [`grant_role(role, account)`](#IAccessControl-grant_role) +- [`revoke_role(role, account)`](#IAccessControl-revoke_role) +- [`renounce_role(role, account)`](#IAccessControl-renounce_role) + +Events + +- [`RoleAdminChanged(role, previous_admin_role, new_admin_role)`](#IAccessControl-RoleAdminChanged) +- [`RoleGranted(role, account, sender)`](#IAccessControl-RoleGranted) +- [`RoleRevoked(role, account, sender)`](#IAccessControl-RoleRevoked) + + +#### Functions [!toc] [#IAccessControl-Functions] + + +Returns whether `account` can act as `role`. + + + +Returns the admin role that controls `role`. See [grant\_role](#IAccessControl-grant_role) and [revoke\_role](#IAccessControl-revoke_role). + +To change a role’s admin, use [set\_role\_admin](#AccessControlComponent-set_role_admin). + + + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [RoleGranted](#IAccessControl-RoleGranted) event. + +Requirements: + +- the caller must have `role`'s admin role. + + + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must have `role`'s admin role. + + + +Revokes `role` from the calling account. + +Roles are often managed via [grant\_role](#IAccessControl-grant_role) and [revoke\_role](#IAccessControl-revoke_role). This function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been granted `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must be `account`. + + +#### Events [!toc] [#IAccessControl-Events] + + +Emitted when `new_admin_role` is set as `role`'s admin role, replacing `previous_admin_role` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite [RoleAdminChanged](#IAccessControl-RoleAdminChanged) not being emitted signaling this. + + + +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an account with the admin role or the deployer address if `_grant_role` is called from the constructor. + + + +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + +- if using `revoke_role`, it is the admin role bearer. +- if using `renounce_role`, it is the role bearer (i.e. `account`). + + +### `IAccessControlWithDelay` [toc] [#IAccessControlWithDelay] + + +```rust +use openzeppelin_access::accesscontrol::interface::IAccessControlWithDelay; +``` + +External interface for the extended `AccessControlWithDelay` functionality. + +Functions + +- [`get_role_status(role, account)`](#IAccessControlWithDelay-get_role_status) +- [`grant_role_with_delay(role, account, delay)`](#IAccessControlWithDelay-grant_role_with_delay) + +Events + +- [`RoleGrantedWithDelay(role, account, sender, delay)`](#IAccessControlWithDelay-RoleGrantedWithDelay) + +#### Functions [!toc] [#IAccessControlWithDelay-Functions] + + +Returns the account’s status for the given role. The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +Requirements: + +- The caller must have `role`'s admin role. +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay) event. + + +#### Events [!toc] [#IAccessControlWithDelay-Events] + + +Emitted when `account` is granted `role` with a delay. + +`sender` is the account that originated the contract call, an account with the admin role or the deployer address if [\_grant\_role\_with\_delay](#AccessControlComponent-_grant_role_with_delay) is called from the constructor. + + +## Core + +### `OwnableComponent` [toc] [#OwnableComponent] + + +```rust +use openzeppelin_access::ownable::OwnableComponent; +``` + +`Ownable` provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific functions. + +This module includes the internal `assert_only_owner` to restrict a function to be used only by the owner. + +[Embeddable Mixin Implementations](../components#mixins) + +#### OwnableMixinImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableMixinImpl] + +- [`OwnableImpl`](#OwnableComponent-Embeddable-Impls-OwnableImpl) +- [`OwnableCamelOnlyImpl`](#OwnableComponent-Embeddable-Impls-OwnableCamelOnlyImpl) + +#### OwnableTwoStepMixinImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableTwoStepMixinImpl] + +- [`OwnableTwoStepImpl`](#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl) +- [`OwnableTwoStepCamelOnlyImpl`](#OwnableComponent-Embeddable-Impls-OwnableTwoStepCamelOnlyImpl) + +Embeddable Implementations + +#### OwnableImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableImpl] + +- [`owner(self)`](#OwnableComponent-owner) +- [`transfer_ownership(self, new_owner)`](#OwnableComponent-transfer_ownership) +- [`renounce_ownership(self)`](#OwnableComponent-renounce_ownership) + +#### OwnableTwoStepImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl] + +- [`owner(self)`](#OwnableComponent-two-step-owner) +- [`pending_owner(self)`](#OwnableComponent-two-step-pending_owner) +- [`accept_ownership(self)`](#OwnableComponent-two-step-accept_ownership) +- [`transfer_ownership(self, new_owner)`](#OwnableComponent-two-step-transfer_ownership) +- [`renounce_ownership(self)`](#OwnableComponent-two-step-renounce_ownership) + +#### OwnableCamelOnlyImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableCamelOnlyImpl] + +- [`transferOwnership(self, newOwner)`](#OwnableComponent-transferOwnership) +- [`renounceOwnership(self)`](#OwnableComponent-renounceOwnership) + +#### OwnableTwoStepCamelOnlyImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableTwoStepCamelOnlyImpl] + +- [`pendingOwner(self)`](#OwnableComponent-two-step-pendingOwner) +- [`acceptOwnership(self)`](#OwnableComponent-two-step-acceptOwnership) +- [`transferOwnership(self, new_owner)`](#OwnableComponent-two-step-transferOwnership) +- [`renounceOwnership(self)`](#OwnableComponent-two-step-renounceOwnership) + +Internal Implementations + +#### InternalImpl [!toc] [#OwnableComponent-InternalImpl] + +- [`initializer(self, owner)`](#OwnableComponent-initializer) +- [`assert_only_owner(self)`](#OwnableComponent-assert_only_owner) +- [`_transfer_ownership(self, new_owner)`](#OwnableComponent-_transfer_ownership) +- [`_propose_owner(self, new_owner)`](#OwnableComponent-_propose_owner) + +Events + +- [`OwnershipTransferStarted(previous_owner, new_owner)`](#OwnableComponent-OwnershipTransferStarted) +- [`OwnershipTransferred(previous_owner, new_owner)`](#OwnableComponent-OwnershipTransferred) + +#### Embeddable functions [!toc] [#OwnableComponent-Embeddable-Functions] + + +Returns the address of the current owner. + + + +Transfers ownership of the contract to a new account (`new_owner`). Can only be called by the current owner. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Leaves the contract without owner. It will not be possible to call `assert_only_owner` functions anymore. Can only be called by the current owner. + + +Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner. + + + +#### Embeddable functions (two step transfer) [!toc] [#OwnableComponent-Embeddable-Functions-Two-Step] + + +Returns the address of the current owner. + + + +Returns the address of the pending owner. + + + +Transfers ownership of the contract to the pending owner. Can only be called by the pending owner. Resets pending owner to zero address. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Starts the two step ownership transfer process, by setting the pending owner. Setting `new_owner` to the zero address is allowed, this can be used to cancel an initiated ownership transfer. + +Can only be called by the current owner. + +Emits an [OwnershipTransferStarted](#OwnableComponent-OwnershipTransferStarted) event. + + + +Leaves the contract without owner. It will not be possible to call `assert_only_owner` functions anymore. Can only be called by the current owner. + + +Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner. + + + + +See [transfer\_ownership](#OwnableComponent-transfer_ownership). + + + +See [renounce\_ownership](#OwnableComponent-renounce_ownership). + + + +See [pending\_owner](#OwnableComponent-two-step-pending_owner). + + + +See [accept\_ownership](#OwnableComponent-two-step-accept_ownership). + + + +See [transfer\_ownership](#OwnableComponent-two-step-transfer_ownership). + + + +See [renounce\_ownership](#OwnableComponent-two-step-renounce_ownership). + + +#### Internal functions [!toc] [#OwnableComponent-Internal-Functions] + + +Initializes the contract and sets `owner` as the initial owner. + +Requirements: + +- `owner` cannot be the zero address. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Panics if called by any account other than the owner. + + + +Transfers ownership of the contract to a new account (`new_owner`). Internal function without access restriction. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Sets a new pending owner in a two step transfer. + +Internal function without access restriction. + +Emits an [OwnershipTransferStarted](#OwnableComponent-OwnershipTransferStarted) event. + + +#### Events [!toc] [#OwnableComponent-Events] + + +Emitted when the pending owner is updated. + + + +Emitted when the ownership is transferred. + + +### `AccessControlComponent` [toc] [#AccessControlComponent] + + +```rust +use openzeppelin_access::accesscontrol::AccessControlComponent; +``` + +Component that allows contracts to implement role-based access control mechanisms. Roles are referred to by their `felt252` identifier: + +```rust +const MY_ROLE: felt252 = selector!("MY_ROLE"); +``` + +Roles can be used to represent a set of permissions. To restrict access to a function call, use [`assert_only_role`](#AccessControlComponent-assert_only_role): + +```rust +(...) + +#[external(v0)] +fn foo(ref self: ContractState) { + self.accesscontrol.assert_only_role(MY_ROLE); + + // Do something +} +``` + +Roles can be granted and revoked dynamically via the [grant\_role](#AccessControlComponent-grant_role), [grant\_role\_with\_delay](#IAccessControlWithDelay-grant_role_with_delay) and [revoke\_role](#AccessControlComponent-revoke_role) functions. Each role has an associated admin role, and only accounts that have a role’s admin role can call [grant\_role](#AccessControlComponent-grant_role), [grant\_role\_with\_delay](#IAccessControlWithDelay-grant_role_with_delay) and [revoke\_role](#AccessControlComponent-revoke_role). + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means that only accounts with this role will be able to grant or revoke other roles. More complex role relationships can be created by using [set\_role\_admin](#AccessControlComponent-set_role_admin). + + +The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role. Extra precautions should be taken to secure accounts that have been granted it. + + +[Embeddable Mixin Implementations](../components#mixins) + +#### AccessControlMixinImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlMixinImpl] + +- [`AccessControlImpl`](#AccessControlComponent-Embeddable-Impls-AccessControlImpl) +- [`AccessControlCamelImpl`](#AccessControlComponent-Embeddable-Impls-AccessControlCamelImpl) +- [`AccessControlWithDelayImpl`](#AccessControlComponent-Embeddable-Impls-AccessControlWithDelayImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### AccessControlImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlImpl] + +- [`has_role(self, role, account)`](#AccessControlComponent-has_role) +- [`get_role_admin(self, role)`](#AccessControlComponent-get_role_admin) +- [`grant_role(self, role, account)`](#AccessControlComponent-grant_role) +- [`revoke_role(self, role, account)`](#AccessControlComponent-revoke_role) +- [`renounce_role(self, role, account)`](#AccessControlComponent-renounce_role) + +#### AccessControlCamelImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlCamelImpl] + +- [`hasRole(self, role, account)`](#AccessControlComponent-hasRole) +- [`getRoleAdmin(self, role)`](#AccessControlComponent-getRoleAdmin) +- [`grantRole(self, role, account)`](#AccessControlComponent-grantRole) +- [`revokeRole(self, role, account)`](#AccessControlComponent-revokeRole) +- [`renounceRole(self, role, account)`](#AccessControlComponent-renounceRole) + +#### AccessControlWithDelayImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlWithDelayImpl] + +- [`get_role_status(self, role, account)`](#AccessControlComponent-get_role_status) +- [`grant_role_with_delay(self, role, account, delay)`](#AccessControlComponent-grant_role_with_delay) + +#### SRC5Impl [!toc] [#AccessControlComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#AccessControlComponent-InternalImpl] + +- [`initializer(self)`](#AccessControlComponent-initializer) +- [`assert_only_role(self, role)`](#AccessControlComponent-assert_only_role) +- [`is_role_effective(self, role, account)`](#AccessControlComponent-is_role_effective) +- [`resolve_role_status(self, role, account)`](#AccessControlComponent-resolve_role_status) +- [`is_role_granted(self, role, account)`](#AccessControlComponent-is_role_granted) +- [`set_role_admin(self, role, admin_role)`](#AccessControlComponent-set_role_admin) +- [`_grant_role(self, role, account)`](#AccessControlComponent-_grant_role) +- [`_grant_role_with_delay(self, role, account, delay)`](#AccessControlComponent-_grant_role_with_delay) +- [`_revoke_role(self, role, account)`](#AccessControlComponent-_revoke_role) + +Events + +#### IAccessControl [!toc] [#AccessControlComponent-Events-IAccessControl] + +- [`RoleAdminChanged(role, previous_admin_role, new_admin_role)`](#AccessControlComponent-RoleAdminChanged) +- [`RoleGranted(role, account, sender)`](#AccessControlComponent-RoleGranted) +- [`RoleRevoked(role, account, sender)`](#AccessControlComponent-RoleRevoked) + +#### IAccessControlWithDelay [!toc] [#AccessControlComponent-Events-IAccessControlWithDelay] + +- [`RoleGrantedWithDelay(role, account, sender, delay)`](#AccessControlComponent-RoleGrantedWithDelay) + +#### Embeddable functions [!toc] [#AccessControlComponent-Embeddable-Functions] + + +Returns whether `account` can act as `role`. + + + +Returns the admin role that controls `role`. See [grant\_role](#AccessControlComponent-grant_role) and [revoke\_role](#AccessControlComponent-revoke_role). + +To change a role’s admin, use [set\_role\_admin](#AccessControlComponent-set_role_admin). + + + +Returns the account’s status for the given role. + +The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [RoleGranted](#IAccessControl-RoleGranted) event. + +Requirements: + +- the caller must have `role`'s admin role. + +May emit a [RoleGranted](#IAccessControl-RoleGranted) event. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +Requirements: + +- The caller must have \`role’s admin role. +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay) event. + + + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must have `role`'s admin role. + +May emit a [RoleRevoked](#IAccessControl-RoleRevoked) event. + + + +Revokes `role` from the calling account. + +Roles are often managed via [grant\_role](#AccessControlComponent-grant_role) and [revoke\_role](#AccessControlComponent-revoke_role). This function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been revoked `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must be `account`. + +May emit a [RoleRevoked](#IAccessControl-RoleRevoked) event. + + + +See [ISRC5::supports\_interface](./introspection#ISRC5-supports_interface). + + + +See [has\_role](#AccessControlComponent-has_role). + + + +See [get\_role\_admin](#AccessControlComponent-get_role_admin). + + + +See [grant\_role](#AccessControlComponent-grant_role). + + + +See [revoke\_role](#AccessControlComponent-revoke_role). + + + +See [renounce\_role](#AccessControlComponent-renounce_role). + + +#### Internal functions [!toc] [#AccessControlComponent-Internal-Functions] + + +Initializes the contract by registering the [IAccessControl](#IAccessControl) interface ID. + + + +Validates that the caller can act as the given role. Otherwise it panics. + + + +Returns whether the account can act as the given role. + +The account can act as the role if it is active and the `effective_from` time is before or equal to the current time. + + +If the `effective_from` timepoint is 0, the role is effective immediately. This is backwards compatible with implementations that didn’t use delays but a single boolean flag. + + + + +Returns the account’s status for the given role. + +The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Returns whether the account has the given role granted. + + +The account may not be able to act as the role yet, if a delay was set and has not passed yet. Use `is_role_effective` to check if the account can act as the role. + + + + +Sets `admin_role` as `role`'s admin role. + +Internal function without access restriction. + +Emits a [RoleAdminChanged](#IAccessControl-RoleAdminChanged) event. + + + +Attempts to grant `role` to `account`. The function does nothing if `role` is already effective for `account`. If `role` has been granted to `account`, but is not yet active due to a time delay, the delay is removed and `role` becomes effective immediately. + +Internal function without access restriction. + +May emit a [RoleGranted](#IAccessControl-RoleGranted) event. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +The role will become effective after the given delay has passed. If the role is already active (`Effective`) for the account, the function will panic. If the role has been granted but is not yet active (being in the `Delayed` state), the existing delay will be overwritten with the new `delay`. + +Internal function without access restriction. + +Requirements: + +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay) event. + + + +Revokes `role` from `account`. + +Internal function without access restriction. + +May emit a [RoleRevoked](#IAccessControl-RoleRevoked) event. + + +#### Events [!toc] [#AccessControlComponent-Events] + + +See [IAccessControl::RoleAdminChanged](#IAccessControl-RoleAdminChanged). + + + +See [IAccessControl::RoleGranted](#IAccessControl-RoleGranted). + + + +See [IAccessControlWithDelay::RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay). + + + +See [IAccessControl::RoleRevoked](#IAccessControl-RoleRevoked). + diff --git a/docs/content/contracts-cairo/2.x/api/account.mdx b/docs/content/contracts-cairo/2.x/api/account.mdx new file mode 100644 index 00000000..a839a5ed --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/account.mdx @@ -0,0 +1,842 @@ +--- +title: Account +--- + +This crate provides components to implement account contracts that can be used for interacting with the network. + +## Interfaces + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `ISRC6` [toc] [#ISRC6] + + +```rust +use openzeppelin_account::interface::ISRC6; +``` + +Interface of the SRC6 Standard Account as defined in the [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x2ceccef7f994940b3962a6c67e0ba4fcd37df7d131417c604f91e03caecc1cd +``` + +Functions + +- [`__execute__(calls)`](#ISRC6-__execute__) +- [`__validate__(calls)`](#ISRC6-__validate__) +- [`is_valid_signature(hash, signature)`](#ISRC6-is_valid_signature) + +#### Functions [!toc] [#ISRC6-Functions] + + +Executes the list of calls as a transaction after validation. + +The `Call` struct is defined in [corelib](https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/account.cairo#L3). + + + +Validates a transaction before execution. + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Validates whether a signature is valid or not for the given message hash. + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + +### `ISRC9_V2` [toc] [#ISRC9_V2] + + +```rust +use openzeppelin_account::extensions::src9::ISRC9_V2; +``` + +Interface of the SRC9 Standard as defined in the [SNIP-9](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872 +``` + +Functions + +- [`execute_from_outside_v2(outside_execution, signature)`](#ISRC9_V2-execute_from_outside_v2) +- [`is_valid_outside_execution_nonce(nonce)`](#ISRC9_V2-is_valid_outside_execution_nonce) + +#### Functions [!toc] [#ISRC9_V2-Functions] + + +Allows anyone to submit a transaction on behalf of the account as long as they have the relevant signatures. + +This method allows reentrancy. A call to `__execute__` or `execute_from_outside_v2` can trigger another nested transaction to `execute_from_outside_v2` thus the implementation MUST verify that the provided `signature` matches the hash of `outside_execution` and that `nonce` was not already used. + +The implementation should expect version to be set to 2 in the domain separator. + +Arguments: + +- `outside_execution` - The parameters of the transaction to execute. +- `signature` - A valid signature on the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message encoding of `outside_execution`. + + + +Get the status of a given nonce. `true` if the nonce is available to use. + + +## Core + +### `AccountComponent` [toc] [#AccountComponent] + + +```rust +use openzeppelin_account::AccountComponent; +``` + +Account component implementing [`ISRC6`](#ISRC6) for signatures over the [Starknet curve](https://docs.starknet.io/architecture-and-concepts/cryptography/#stark-curve). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +[Embeddable Mixin Implementations](../components#mixins) + +#### AccountMixinImpl [!toc] [#AccountComponent-Embeddable-Impls-AccountMixinImpl] + +- [`SRC6Impl`](#AccountComponent-Embeddable-Impls-SRC6Impl) +- [`DeclarerImpl`](#AccountComponent-Embeddable-Impls-DeclarerImpl) +- [`DeployableImpl`](#AccountComponent-Embeddable-Impls-DeployableImpl) +- [`PublicKeyImpl`](#AccountComponent-Embeddable-Impls-PublicKeyImpl) +- [`SRC6CamelOnlyImpl`](#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl) +- [`PublicKeyCamelImpl`](#AccountComponent-Embeddable-Impls-PublicKeyCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### SRC6Impl [!toc] [#AccountComponent-Embeddable-Impls-SRC6Impl] + +- [`__execute__(self, calls)`](#AccountComponent-__execute__) +- [`__validate__(self, calls)`](#AccountComponent-__validate__) +- [`is_valid_signature(self, hash, signature)`](#AccountComponent-is_valid_signature) + +#### DeclarerImpl [!toc] [#AccountComponent-Embeddable-Impls-DeclarerImpl] + +- [`__validate_declare__(self, class_hash)`](#AccountComponent-__validate_declare__) + +#### DeployableImpl [!toc] [#AccountComponent-Embeddable-Impls-DeployableImpl] + +- [`__validate_deploy__(self, hash, signature)`](#AccountComponent-__validate_deploy__) + +#### PublicKeyImpl [!toc] [#AccountComponent-Embeddable-Impls-PublicKeyImpl] + +- [`get_public_key(self)`](#AccountComponent-get_public_key) +- [`set_public_key(self, new_public_key, signature)`](#AccountComponent-set_public_key) + +#### SRC6CamelOnlyImpl [!toc] [#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl] + +- [`isValidSignature(self, hash, signature)`](#AccountComponent-isValidSignature) + +#### PublicKeyCamelImpl [!toc] [#AccountComponent-Embeddable-Impls-PublicKeyCamelImpl] + +- [`getPublicKey(self)`](#AccountComponent-getPublicKey) +- [`setPublicKey(self, newPublicKey, signature)`](#AccountComponent-setPublicKey) + +#### SRC5Impl [!toc] [#AccountComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#AccountComponent-InternalImpl] + +- [`initializer(self, public_key)`](#AccountComponent-initializer) +- [`assert_only_self(self)`](#AccountComponent-assert_only_self) +- [`assert_valid_new_owner(self, current_owner, new_owner, signature)`](#AccountComponent-assert_valid_new_owner) +- [`validate_transaction(self)`](#AccountComponent-validate_transaction) +- [`_set_public_key(self, new_public_key)`](#AccountComponent-_set_public_key) +- [`_is_valid_signature(self, hash, signature)`](#AccountComponent-_is_valid_signature) + +Events + +- [`OwnerAdded(new_owner_guid)`](#AccountComponent-OwnerAdded) +- [`OwnerRemoved(removed_owner_guid)`](#AccountComponent-OwnerRemoved) + +#### Embeddable functions [!toc] [#AccountComponent-Embeddable-Functions] + + +See [ISRC6::\_\_execute\_\_](#ISRC6-__execute__). + + + +See [ISRC6::\_\_validate\_\_](#ISRC6-__validate__). + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +Validates a [`Declare` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Validates a [`DeployAccount` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#deploy_account_transaction). See [Counterfactual Deployments](../guides/deployment). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Returns the current public key of the account. + + + +Sets a new public key for the account. Only accessible by the account calling itself through `__execute__`. + +Requirements: + +- The caller must be the contract itself. +- The signature must be valid for the new owner. + +Emits both an [OwnerRemoved](#AccountComponent-OwnerRemoved) and an [OwnerAdded](#AccountComponent-OwnerAdded) event. + +The message to be signed is computed in Cairo as follows: + +```javascript +let message_hash = PoseidonTrait::new() + .update_with('StarkNet Message') + .update_with('accept_ownership') + .update_with(get_contract_address()) + .update_with(current_owner) + .finalize(); +``` + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +See [get\_public\_key](#AccountComponent-get_public_key). + + + +See [set\_public\_key](#AccountComponent-set_public_key). + + +#### Internal functions [!toc] [#AccountComponent-Internal-Functions] + + +Initializes the account with the given public key, and registers the `ISRC6` interface ID. + +Emits an [OwnerAdded](#AccountComponent-OwnerAdded) event. + + + +Validates that the caller is the account itself. Otherwise it reverts. + + + +Validates that `new_owner` accepted the ownership of the contract through a signature. + +Requirements: + +- `signature` must be valid for the new owner. + +This function assumes that `current_owner` is the current owner of the contract, and does not validate this assumption. + + + +Validates a transaction signature from the [global context](https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/info.cairo#L61). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Set the public key without validating the caller. + +Emits an [OwnerAdded](#AccountComponent-OwnerAdded) event. + +The usage of this method outside the `set_public_key` function is discouraged. + + + +Validates the provided `signature` for the `hash`, using the account's current public key. + + +#### Events [!toc] [#AccountComponent-Events] + + +Emitted when a `public_key` is added. + + + +Emitted when a `public_key` is removed. + + +### `EthAccountComponent` [toc] [#EthAccountComponent] + + +```rust +use openzeppelin_account::eth_account::EthAccountComponent; +``` + +Account component implementing [`ISRC6`](#ISRC6) for signatures over the [Secp256k1 curve](https://en.bitcoin.it/wiki/Secp256k1). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Point`. + +[Embeddable Mixin Implementations](../components#mixins) + +#### EthAccountMixinImpl [!toc] [#EthAccountComponent-Embeddable-Impls-EthAccountMixinImpl] + +- [`SRC6Impl`](#EthAccountComponent-Embeddable-Impls-SRC6Impl) +- [`DeclarerImpl`](#EthAccountComponent-Embeddable-Impls-DeclarerImpl) +- [`DeployableImpl`](#EthAccountComponent-Embeddable-Impls-DeployableImpl) +- [`PublicKeyImpl`](#EthAccountComponent-Embeddable-Impls-PublicKeyImpl) +- [`SRC6CamelOnlyImpl`](#EthAccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl) +- [`PublicKeyCamelImpl`](#EthAccountComponent-Embeddable-Impls-PublicKeyCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### SRC6Impl [!toc] [#EthAccountComponent-Embeddable-Impls-SRC6Impl] + +- [`__execute__(self, calls)`](#EthAccountComponent-__execute__) +- [`__validate__(self, calls)`](#EthAccountComponent-__validate__) +- [`is_valid_signature(self, hash, signature)`](#EthAccountComponent-is_valid_signature) + +#### DeclarerImpl [!toc] [#EthAccountComponent-Embeddable-Impls-DeclarerImpl] + +- [`__validate_declare__(self, class_hash)`](#EthAccountComponent-__validate_declare__) + +#### DeployableImpl [!toc] [#EthAccountComponent-Embeddable-Impls-DeployableImpl] + +- [`__validate_deploy__(self, hash, signature)`](#EthAccountComponent-__validate_deploy__) + +#### PublicKeyImpl [!toc] [#EthAccountComponent-Embeddable-Impls-PublicKeyImpl] + +- [`get_public_key(self)`](#EthAccountComponent-get_public_key) +- [`set_public_key(self, new_public_key, signature)`](#EthAccountComponent-set_public_key) + +#### SRC6CamelOnlyImpl [!toc] [#EthAccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl] + +- [`isValidSignature(self, hash, signature)`](#EthAccountComponent-isValidSignature) + +#### PublicKeyCamelImpl [!toc] [#EthAccountComponent-Embeddable-Impls-PublicKeyCamelImpl] + +- [`getPublicKey(self)`](#EthAccountComponent-getPublicKey) +- [`setPublicKey(self, newPublicKey, signature)`](#EthAccountComponent-setPublicKey) + +#### SRC5Impl [!toc] [#EthAccountComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#EthAccountComponent-InternalImpl] + +- [`initializer(self, public_key)`](#EthAccountComponent-initializer) +- [`assert_only_self(self)`](#EthAccountComponent-assert_only_self) +- [`assert_valid_new_owner(self, current_owner, new_owner, signature)`](#EthAccountComponent-assert_valid_new_owner) +- [`validate_transaction(self)`](#EthAccountComponent-validate_transaction) +- [`_set_public_key(self, new_public_key)`](#EthAccountComponent-_set_public_key) +- [`_is_valid_signature(self, hash, signature)`](#EthAccountComponent-_is_valid_signature) + +Events + +- [`OwnerAdded(new_owner_guid)`](#EthAccountComponent-OwnerAdded) +- [`OwnerRemoved(removed_owner_guid)`](#EthAccountComponent-OwnerRemoved) + +#### Embeddable functions [!toc] [#EthAccountComponent-Embeddable-Functions] + + +See [ISRC6::\_\_execute\_\_](#ISRC6-__execute__). + + + +See [ISRC6::\_\_validate\_\_](#ISRC6-__validate__). + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +Validates a [`Declare` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Validates a [`DeployAccount` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#deploy_account_transaction). See [Counterfactual Deployments](../guides/deployment). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Returns the current public key of the account. + + + +Sets a new public key for the account. Only accessible by the account calling itself through `__execute__`. + +Requirements: + +- The caller must be the contract itself. +- The signature must be valid for the new owner. + +Emits both an [OwnerRemoved](#EthAccountComponent-OwnerRemoved) and an [OwnerAdded](#EthAccountComponent-OwnerAdded) event. + +The message to be signed is computed in Cairo as follows: + +```javascript +let message_hash = PoseidonTrait::new() + .update_with('StarkNet Message') + .update_with('accept_ownership') + .update_with(get_contract_address()) + .update_with(current_owner.get_coordinates().unwrap_syscall()) + .finalize(); +``` + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +See [get\_public\_key](#EthAccountComponent-get_public_key). + + + +See [set\_public\_key](#EthAccountComponent-set_public_key). + + +#### Internal functions [!toc] [#EthAccountComponent-Internal-Functions] + + +Initializes the account with the given public key, and registers the `ISRC6` interface ID. + +Emits an [OwnerAdded](#EthAccountComponent-OwnerAdded) event. + + + +Validates that the caller is the account itself. Otherwise it reverts. + + + +Validates that `new_owner` accepted the ownership of the contract through a signature. + +Requirements: + +- The signature must be valid for the `new_owner`. + +This function assumes that `current_owner` is the current owner of the contract, and does not validate this assumption. + + + +Validates a transaction signature from the [global context](https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/info.cairo#L61). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Set the public key without validating the caller. + +Emits an [OwnerAdded](#EthAccountComponent-OwnerAdded) event. + +The usage of this method outside the `set_public_key` function is discouraged. + + + +Validates the provided `signature` for the `hash`, using the account's current public key. + + +#### Events [!toc] [#EthAccountComponent-Events] + +The `guid` is computed as the hash of the public key, using the poseidon hash function. + + +Emitted when a `public_key` is added. + + + +Emitted when a `public_key` is removed. + + +## Extensions + +### `SRC9Component` [toc] [#SRC9Component] + + +```rust +use openzeppelin_account::extensions::SRC9Component; +``` + +OutsideExecution component implementing [`ISRC9_V2`](#ISRC9_V2). + +This component is signature-agnostic, meaning it can be integrated into any account contract, as long as the account implements the ISRC6 interface. + +Embeddable Implementations + +#### OutsideExecutionV2Impl [!toc] [#SRC9Component-Embeddable-Impls-OutsideExecutionV2Impl] + +- [`execute_from_outside_v2(self, outside_execution, signature)`](#SRC9Component-execute_from_outside_v2) +- [`is_valid_outside_execution_nonce(self, nonce)`](#SRC9Component-is_valid_outside_execution_nonce) + +Internal Implementations + +#### InternalImpl [!toc] [#SRC9Component-InternalImpl] + +- [`initializer(self)`](#SRC9Component-initializer) + +#### Embeddable functions [!toc] [#SRC9Component-Embeddable-Functions] + + +Allows anyone to submit a transaction on behalf of the account as long as they have the relevant signatures. + +This method allows reentrancy. A call to `__execute__` or `execute_from_outside_v2` can trigger another nested transaction to `execute_from_outside_v2`. This implementation verifies that the provided `signature` matches the hash of `outside_execution` and that `nonce` was not already used. + +Arguments: + +- `outside_execution` - The parameters of the transaction to execute. +- `signature` - A valid signature on the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message encoding of `outside_execution`. + +Requirements: + +- The caller must be the `outside_execution.caller` unless 'ANY\_CALLER' is used. +- The current time must be within the `outside_execution.execute_after` and `outside_execution.execute_before` span. +- The `outside_execution.nonce` must not be used before. +- The `signature` must be valid. + + + +Returns the status of a given nonce. `true` if the nonce is available to use. + + +#### Internal functions [!toc] [#SRC9Component-Internal-Functions] + + +Initializes the account by registering the `ISRC9_V2` interface ID. + + +## Presets + +### `AccountUpgradeable` [toc] [#AccountUpgradeable] + + +```rust +use openzeppelin_presets::AccountUpgradeable; +``` + +Upgradeable account which can change its public key and declare, deploy, or call contracts. Supports outside execution by implementing [SRC9](#SRC9Component). + +[Sierra class hash](../presets) + +```text +{{AccountUpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, public_key)`](#AccountUpgradeable-constructor) + +Embedded Implementations + +AccountComponent + +- [`AccountMixinImpl`](#AccountComponent-Embeddable-Mixin-Impl) + +SRC9Component + +- [`OutsideExecutionV2Impl`](#SRC9Component-Embeddable-Impls-OutsideExecutionV2Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#AccountUpgradeable-upgrade) + +#### Constructor [!toc] [#AccountUpgradeable-Constructor] + + +Sets the account `public_key` and registers the interfaces the contract supports. + + +#### External functions [!toc] [#AccountUpgradeable-External-Functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the account contract itself. +- `new_class_hash` cannot be zero. + + +### `EthAccountUpgradeable` [toc] [#EthAccountUpgradeable] + + +```rust +use openzeppelin_presets::EthAccountUpgradeable; +``` + +Upgradeable account which can change its public key and declare, deploy, or call contracts, using Ethereum signing keys. Supports outside execution by implementing [SRC9](#SRC9Component). + +The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Point`. + +[Sierra class hash](../presets) + +```text +{{EthAccountUpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, public_key)`](#EthAccountUpgradeable-constructor) + +Embedded Implementations + +EthAccountComponent + +- [`EthAccountMixinImpl`](#EthAccountComponent-Embeddable-Mixin-Impl) + +SRC9Component + +- [`OutsideExecutionV2Impl`](#SRC9Component-Embeddable-Impls-OutsideExecutionV2Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#EthAccountUpgradeable-upgrade) + +#### Constructor [!toc] [#EthAccountUpgradeable-Constructor] + + +Sets the account `public_key` and registers the interfaces the contract supports. + + +#### External functions [!toc] [#EthAccountUpgradeable-External-Functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the account contract itself. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/2.x/api/erc1155.mdx b/docs/content/contracts-cairo/2.x/api/erc1155.mdx new file mode 100644 index 00000000..8526aaea --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/erc1155.mdx @@ -0,0 +1,783 @@ +--- +title: ERC1155 +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This module provides interfaces, presets, and utilities related to ERC1155 contracts. + +For an overview of ERC1155, read our [ERC1155 guide](../erc1155). + +## Interfaces + +### `IERC1155` [toc] [#IERC1155] + + + +```rust +use openzeppelin_token::erc1155::interface::IERC1155; +``` + +Interface of the IERC1155 standard as defined in [EIP1155](https://eips.ethereum.org/EIPS/eip-1155). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x6114a8f75559e1b39fcba08ce02961a1aa082d9256a158dd3e64964e4b1b52 +``` + +Functions + +- [`balance_of(account, token_id)`](#IERC1155-balance_of) +- [`balance_of_batch(accounts, token_ids)`](#IERC1155-balance_of_batch) +- [`safe_transfer_from(from, to, token_id, value, data)`](#IERC1155-safe_transfer_from) +- [`safe_batch_transfer_from(from, to, token_ids, values, data)`](#IERC1155-safe_batch_transfer_from) +- [`set_approval_for_all(operator, approved)`](#IERC1155-set_approval_for_all) +- [`is_approved_for_all(owner, operator)`](#IERC1155-is_approved_for_all) + +Events + +- [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle) +- [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch) +- [`ApprovalForAll(owner, operator, approved)`](#IERC1155-ApprovalForAll) +- [`URI(value, id)`](#IERC1155-URI) + +#### Functions [!toc] [#functions] + + +Returns the amount of `token_id` tokens owned by `account`. + + + +Returns a list of balances derived from the `accounts` and `token_ids` pairs. + + + +Transfers ownership of `value` amount of `token_id` from `from` if `to` is either `IERC1155Receiver` or an account. + +`data` is additional data, it has no specified format and it is passed to `to`. + +Emits a [TransferSingle](#IERC1155-TransferSingle) event. + + + +Transfers ownership of `token_ids` and `values` pairs from `from` if `to` is either `IERC1155Receiver` or an account. + +`data` is additional data, it has no specified format and it is passed to `to`. + +Emits a [TransferBatch](#IERC1155-TransferBatch) event. + + + +Enables or disables approval for `operator` to manage all of the caller's assets. + +Emits an [ApprovalForAll](#IERC1155-ApprovalForAll) event. + + + +Queries if `operator` is an authorized operator for `owner`. + + +#### Events [!toc] [#events] + + +Emitted when `value` amount of `id` token is transferred from `from` to `to` through `operator`. + + + +Emitted when a batch of `values` amount of `ids` tokens are transferred from `from` to `to` through `operator`. + + + +Emitted when `owner` enables or disables `operator` to manage all of the owner's assets. + + + +Emitted when the token URI is updated to `value` for the `id` token. + + +### `IERC1155MetadataURI` [toc] [#IERC1155MetadataURI] + + + +```rust +use openzeppelin_token::erc1155::interface::IERC1155MetadataURI; +``` + +Interface for the optional metadata function in [EIP1155](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +[SRC5 ID](./introspection#ISRC5) + +```text +0xcabe2400d5fe509e1735ba9bad205ba5f3ca6e062da406f72f113feb889ef7 +``` + +Functions + +- [`uri(token_id)`](#IERC1155MetadataURI-uri) + +#### Functions [!toc] [#functions_2] + + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. + + +### `IERC1155Receiver` [toc] [#IERC1155Receiver] + + + +```rust +use openzeppelin_token::erc1155::interface::IERC1155Receiver; +``` + +Interface for contracts that support receiving token transfers from `ERC1155` contracts. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x15e8665b5af20040c3af1670509df02eb916375cdf7d8cbaf7bd553a257515e +``` + +Functions + +- [`on_erc1155_received(operator, from, token_id, value, data)`](#IERC1155Receiver-on_erc1155_received) +- [`on_erc1155_batch_received(operator, from, token_ids, values, data)`](#IERC1155Receiver-on_erc1155_batch_received) + +#### Functions [!toc] [#functions_3] + + +This function is called whenever an ERC1155 `token_id` token is transferred to this `IERC1155Receiver` implementer via [IERC1155::safe\_transfer\_from](#IERC1155-safe_transfer_from) by `operator` from `from`. + + + +This function is called whenever multiple ERC1155 `token_ids` tokens are transferred to this `IERC1155Receiver` implementer via [IERC1155::safe\_batch\_transfer\_from](#IERC1155-safe_batch_transfer_from) by `operator` from `from`. + + +## [](#core)Core + +### `ERC1155Component` [toc] [#ERC1155Component] + + + +```rust +use openzeppelin_token::erc1155::ERC1155Component; +``` + +ERC1155 component implementing [IERC1155](#IERC1155) and [IERC1155MetadataURI](#IERC1155MetadataURI). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +See [Hooks](#ERC1155Component-Hooks) to understand how are hooks used. + +#### Hooks [!toc] [#ERC1155Component-Hooks] + +#### ERC1155HooksTrait [!toc] [#ERC1155Component-ERC1155HooksTrait] + +- [`before_update(self, from, to, token_ids, values)`](#ERC1155Component-before_update) +- [`after_update(self, from, to, token_ids, values)`](#ERC1155Component-after_update) + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC1155MixinImpl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155MixinImpl] + +- [`ERC1155Impl`](#ERC1155Component-Embeddable-Impls-ERC1155Impl) +- [`ERC1155MetadataURIImpl`](#ERC1155Component-Embeddable-Impls-ERC1155MetadataURIImpl) +- [`ERC1155CamelImpl`](#ERC1155Component-Embeddable-Impls-ERC1155CamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls-SRC5Impl) + +Embeddable Implementations + +#### ERC1155Impl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155Impl] + +- [`balance_of(self, account, token_id)`](#ERC1155Component-balance_of) +- [`balance_of_batch(self, accounts, token_ids)`](#ERC1155Component-balance_of_batch) +- [`safe_transfer_from(self, from, to, token_id, value, data)`](#ERC1155Component-safe_transfer_from) +- [`safe_batch_transfer_from(self, from, to, token_ids, values, data)`](#ERC1155Component-safe_batch_transfer_from) +- [`set_approval_for_all(self, operator, approved)`](#ERC1155Component-set_approval_for_all) +- [`is_approved_for_all(self, owner, operator)`](#ERC1155Component-is_approved_for_all) + +#### ERC1155MetadataURIImpl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155MetadataURIImpl] + +- [`uri(self, token_id)`](#ERC1155Component-uri) + +#### ERC1155CamelImpl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155CamelImpl] + +- [`balanceOf(self, account, tokenId)`](#ERC1155Component-balanceOf) +- [`balanceOfBatch(self, accounts, tokenIds)`](#ERC1155Component-balanceOfBatch) +- [`safeTransferFrom(self, from, to, tokenId, value, data)`](#ERC1155Component-safeTransferFrom) +- [`safeBatchTransferFrom(self, from, to, tokenIds, values, data)`](#ERC1155Component-safeBatchTransferFrom) +- [`setApprovalForAll(self, operator, approved)`](#ERC1155Component-setApprovalForAll) +- [`isApprovedForAll(self, owner, operator)`](#ERC1155Component-isApprovedForAll) + +Internal Functions + +#### InternalImpl [!toc] [#ERC1155Component-InternalImpl] + +- [`initializer(self, base_uri)`](#ERC1155Component-initializer) +- [`initializer_no_metadata(self)`](#ERC1155Component-initializer_no_metadata) +- [`mint_with_acceptance_check(self, to, token_id, value, data)`](#ERC1155Component-mint_with_acceptance_check) +- [`batch_mint_with_acceptance_check(self, to, token_ids, values, data)`](#ERC1155Component-batch_mint_with_acceptance_check) +- [`burn(self, from, token_id, value)`](#ERC1155Component-burn) +- [`batch_burn(self, from, token_ids, values)`](#ERC1155Component-batch_burn) +- [`update_with_acceptance_check(self, from, to, token_ids, values, data)`](#ERC1155Component-update_with_acceptance_check) +- [`update(self, from, to, token_ids, values)`](#ERC1155Component-update) +- [`_set_base_uri(self, base_uri)`](#ERC1155Component-_set_base_uri) + +Events + +IERC1155 + +- [`TransferSingle(operator, from, to, id, value)`](#ERC1155Component-TransferSingle) +- [`TransferBatch(operator, from, to, ids, values)`](#ERC1155Component-TransferBatch) +- [`ApprovalForAll(owner, operator, approved)`](#ERC1155Component-ApprovalForAll) +- [`URI(value, id)`](#ERC1155Component-URI) + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC1155Component is expected to provide an implementation of the ERC1155HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc1155::ERC1155HooksEmptyImpl` which is already available as part of the library for this purpose. + + +Function executed at the beginning of the [update](#ERC1155Component-update) function prior to any other logic. + + + +Function executed at the end of the [update](#ERC1155Component-update) function. + + +#### Embeddable functions [!toc] [#embeddable_functions] + + +Returns the amount of `token_id` tokens owned by `account`. + + + +Returns a list of balances derived from the `accounts` and `token_ids` pairs. + +Requirements: + +- `token_ids` and `accounts` must have the same length. + + + +Transfers ownership of `value` amount of `token_id` from `from` if `to` is either an account or `IERC1155Receiver`. + +`data` is additional data, it has no specified format and it is passed to `to`. + +This function can potentially allow a reentrancy attack when transferring tokens to an untrusted contract, when invoking `on_ERC1155_received` on the receiver. Ensure to follow the checks-effects-interactions pattern and consider employing reentrancy guards when interacting with untrusted contracts. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `from` is not the zero address. +- `to` is not the zero address. +- If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_received` and return the required magic value. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event. + + + +Transfers ownership of `values` and `token_ids` pairs from `from` if `to` is either an account or `IERC1155Receiver`. + +`data` is additional data, it has no specified format and it is passed to `to`. + +This function can potentially allow a reentrancy attack when transferring tokens to an untrusted contract, when invoking `on_ERC1155_batch_received` on the receiver. Ensure to follow the checks-effects-interactions pattern and consider employing reentrancy guards when interacting with untrusted contracts. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `from` is not the zero address. +- `to` is not the zero address. +- `token_ids` and `values` must have the same length. +- If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` and return the acceptance magic value. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event if the arrays contain one element, and [TransferBatch](#ERC1155Component-TransferBatch) otherwise. + + + +Enables or disables approval for `operator` to manage all of the callers assets. + +Requirements: + +- `operator` cannot be the caller. + +Emits an [ApprovalForAll](#ERC1155Component-ApprovalForAll) event. + + + +Queries if `operator` is an authorized operator for `owner`. + + + + +This implementation returns the same URI for **all** token types. It relies on the token type ID substitution mechanism [specified in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +Clients calling this function must replace the `id` substring with the actual token type ID. + + + +See [ERC1155Component::balance\_of](#ERC1155Component-balance_of). + + + +See [ERC1155Component::balance\_of\_batch](#ERC1155Component-balance_of_batch). + + + +See [ERC1155Component::safe\_transfer\_from](#ERC1155Component-safe_transfer_from). + + + +See [ERC1155Component::safe\_batch\_transfer\_from](#ERC1155Component-safe_batch_transfer_from). + + + +See [ERC1155Component::set\_approval\_for\_all](#ERC1155Component-set_approval_for_all). + + + +See [ERC1155Component::is\_approved\_for\_all](#ERC1155Component-is_approved_for_all). + + +#### Internal functions [!toc] [#internal_functions] + + +Initializes the contract by setting the token's base URI as `base_uri`, and registering the supported interfaces. This should only be used inside the contract's constructor. + +Most ERC1155 contracts expose the [IERC1155MetadataURI](#IERC1155MetadataURI) interface which is what this initializer is meant to support. If the contract DOES NOT expose the [IERC1155MetadataURI](#IERC1155MetadataURI) interface, meaning tokens do not have a URI, the contract must instead use [initializer\_no\_metadata](#ERC1155Component-initializer_no_metadata) in the constructor. Failure to abide by these instructions can lead to unexpected issues especially with UIs. + + + +Initializes the contract with no metadata by registering only the IERC1155 interface. + +This initializer should ONLY be used during construction in the very specific instance when the contract does NOT expose the [IERC1155MetadataURI](#IERC1155MetadataURI) interface. Initializing a contract with this initializer means that tokens will not have a URI. + + + +Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`. + +Requirements: + +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_received` and return the acceptance magic value. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event. + + + +Batched version of [mint\_with\_acceptance\_check](#ERC1155Component-mint_with_acceptance_check). + +Requirements: + +- `to` cannot be the zero address. +- `token_ids` and `values` must have the same length. +- If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` and return the acceptance magic value. + +Emits a [TransferBatch](#ERC1155Component-TransferBatch) event. + + + +Destroys a `value` amount of tokens of type `token_id` from `from`. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `token_id`. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event. + + + +Batched version of [burn](#ERC1155Component-burn). + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `token_id`. +- `token_ids` and `values` must have the same length. + +Emits a [TransferBatch](#ERC1155Component-TransferBatch) event. + + + +Version of `update` that performs the token acceptance check by calling `onERC1155Received` or `onERC1155BatchReceived` in the receiver if it implements `IERC1155Receiver`, otherwise by checking if it is an account. + +Requirements: + +- `to` is either an account contract or supports the `IERC1155Receiver` interface. +- `token_ids` and `values` must have the same length. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event if the arrays contain one element, and [TransferBatch](#ERC1155Component-TransferBatch) otherwise. + + + +Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` (or `to`) is the zero address. + +Requirements: + +- `token_ids` and `values` must have the same length. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event if the arrays contain one element, and [TransferBatch](#ERC1155Component-TransferBatch) otherwise. + +This function can be extended using the [ERC1155HooksTrait](#ERC1155Component-ERC1155HooksTrait), to add functionality before and/or after the transfer, mint, or burn. + +The ERC1155 acceptance check is not performed in this function. See [update\_with\_acceptance\_check](#ERC1155Component-update_with_acceptance_check) instead. + + + +Sets a new URI for all token types, by relying on the token type ID substitution mechanism [specified in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +By this mechanism, any occurrence of the `id` substring in either the URI or any of the values in the JSON file at said URI will be replaced by clients with the token type ID. + +For example, the `https://token-cdn-domain/\id\.json` URI would be interpreted by clients as `https://token-cdn-domain/000000000000...000000000000004cce0.json` for token type ID `0x4cce0`. + +Because these URIs cannot be meaningfully represented by the `URI` event, this function emits no events. + + +#### Events [!toc] [#events_2] + + +See [IERC1155::TransferSingle](#IERC1155-TransferSingle). + + + +See [IERC1155::TransferBatch](#IERC1155-TransferBatch). + + + +See [IERC1155::ApprovalForAll](#IERC1155-ApprovalForAll). + + + + +See [IERC1155::URI](#IERC1155-URI). + + +### `ERC1155ReceiverComponent` [toc] [#ERC1155ReceiverComponent] + + + +```rust +use openzeppelin_token::erc1155::ERC1155ReceiverComponent; +``` + +ERC1155Receiver component implementing [IERC1155Receiver](#IERC1155Receiver). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC1155MixinImpl [!toc] [#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155MixinImpl] + +- [`ERC1155ReceiverImpl`](#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverImpl) +- [`ERC1155ReceiverCamelImpl`](#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls-SRC5Impl) + +Embeddable Implementations + +#### ERC1155ReceiverImpl [!toc] [#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverImpl] + +- [`on_erc1155_received(self, operator, from, token_id, value, data)`](#ERC1155ReceiverComponent-on_erc1155_received) +- [`on_erc1155_batch_received(self, operator, from, token_ids, values, data)`](#ERC1155ReceiverComponent-on_erc1155_batch_received) + +#### ERC1155ReceiverCamelImpl [!toc] [#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverCamelImpl] + +- [`onERC1155Received(self, operator, from, tokenId, value, data)`](#ERC1155ReceiverComponent-onERC1155Received) +- [`onERC1155BatchReceived(self, operator, from, tokenIds, values, data)`](#ERC1155ReceiverComponent-onERC1155BatchReceived) + +Internal Functions + +#### InternalImpl [!toc] [#ERC1155ReceiverComponent-InternalImpl] + +- [`initializer(self)`](#ERC1155ReceiverComponent-initializer) + +#### Embeddable functions [!toc] [#embeddable_functions_2] + + +Returns the `IERC1155Receiver` interface ID. + + + +Returns the `IERC1155Receiver` interface ID. + + + +See [ERC1155ReceiverComponent::on\_erc1155\_received](#ERC1155ReceiverComponent-on_erc1155_received). + + + +See [ERC1155ReceiverComponent::on\_erc1155\_batch\_received](#ERC1155ReceiverComponent-on_erc1155_batch_received). + + +#### Internal functions [!toc] [#internal_functions_2] + + +Registers the `IERC1155Receiver` interface ID as supported through introspection. + + +## [](#presets)Presets + +### `ERC1155Upgradeable` [toc] [#ERC1155Upgradeable] + + + +```rust +use openzeppelin_presets::ERC1155; +``` + +Upgradeable ERC1155 contract leveraging [ERC1155Component](#ERC1155Component). + +[Sierra class hash](../presets) + +```text +{{ERC1155UpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, base_uri, recipient, token_ids, values, owner)`](#ERC1155Upgradeable-constructor) + +Embedded Implementations + +ERC1155Component + +- [`ERC1155MixinImpl`](#ERC1155Component-Embeddable-Mixin-Impl) + +OwnableMixinImpl + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#ERC1155Upgradeable-upgrade) + +#### Constructor [!toc] [#ERC1155Upgradeable-constructor-section] + + +Sets the `base_uri` for all tokens and registers the supported interfaces. Mints the `values` for `token_ids` tokens to `recipient`. Assigns `owner` as the contract owner with permissions to upgrade. + +Requirements: + +- `to` is either an account contract (supporting ISRC6) or supports the `IERC1155Receiver` interface. +- `token_ids` and `values` must have the same length. + + +#### External Functions [!toc] [#ERC1155Upgradeable-external-functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/2.x/api/erc20.mdx b/docs/content/contracts-cairo/2.x/api/erc20.mdx new file mode 100644 index 00000000..08678751 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/erc20.mdx @@ -0,0 +1,1580 @@ +--- +title: ERC20 +--- + +This module provides interfaces, presets, and utilities related to ERC20 contracts. + +For an overview of ERC20, read our [ERC20 guide](../erc20). + +## Interfaces + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `IERC20` [toc] [#IERC20] + + +```rust +use openzeppelin_token::erc20::interface::IERC20; +``` + +Interface of the IERC20 standard as defined in [EIP-20](https://eips.ethereum.org/EIPS/eip-20). + +Functions + +- [`total_supply()`](#IERC20-total_supply) +- [`balance_of(account)`](#IERC20-balance_of) +- [`allowance(owner, spender)`](#IERC20-allowance) +- [`transfer(recipient, amount)`](#IERC20-transfer) +- [`transfer_from(sender, recipient, amount)`](#IERC20-transfer_from) +- [`approve(spender, amount)`](#IERC20-approve) + +Events + +- [`Transfer(from, to, value)`](#IERC20-Transfer) +- [`Approval(owner, spender, value)`](#IERC20-Approval) + +#### Functions [!toc] [#IERC20-Functions] + + +Returns the amount of tokens in existence. + + + +Returns the amount of tokens owned by `account`. + + + +Returns the remaining number of tokens that `spender` is allowed to spend on behalf of `owner` through [transfer\_from](#transfer_from). This is zero by default. + +This value changes when [approve](#IERC20-approve) or [transfer\_from](#IERC20-transfer_from) are called. + + + +Moves `amount` tokens from the caller's token balance to `to`. Returns `true` on success, reverts otherwise. + +Emits a [Transfer](#IERC20-Transfer) event. + + + +Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. Returns `true` on success, reverts otherwise. + +Emits a [Transfer](#IERC20-Transfer) event. + + + +Sets `amount` as the allowance of `spender` over the caller's tokens. Returns `true` on success, reverts otherwise. + +Emits an [Approval](#ERC20-Approval) event. + + +#### Events [!toc] [#IERC20-Events] + + +Emitted when `value` tokens are moved from one address (`from`) to another (`to`). + +Note that `value` may be zero. + + + +Emitted when the allowance of a `spender` for an `owner` is set. `value` is the new allowance. + + +### `IERC20Metadata` [toc] [#IERC20Metadata] + + +```rust +use openzeppelin_token::erc20::interface::IERC20Metadata; +``` + +Interface for the optional metadata functions in [EIP-20](https://eips.ethereum.org/EIPS/eip-20). + +Functions + +- [`name()`](#IERC20Metadata-name) +- [`symbol()`](#IERC20Metadata-symbol) +- [`decimals()`](#IERC20Metadata-decimals) + +#### Functions [!toc] [#IERC20Metadata-Functions] + + +Returns the name of the token. + + + +Returns the ticker symbol of the token. + + + +Returns the number of decimals the token uses - e.g. `8` means to divide the token amount by `100000000` to get its user-readable representation. + +For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). + +Tokens usually opt for a value of `18`, imitating the relationship between Ether and Wei. This is the default value returned by this function. To create a custom decimals implementation, see [Customizing decimals](../erc20#customizing_decimals). + +This information is only used for *display* purposes: it in no way affects any of the arithmetic of the contract. + + +### `IERC20Permit` [toc] [#IERC20Permit] + + +```rust +use openzeppelin_token::erc20::interface::IERC20Permit; +``` + +Interface of the ERC20Permit standard to support gasless token approvals as defined in [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Functions + +- [`permit(owner, spender, amount, deadline, signature)`](#IERC20Permit-permit) +- [`nonces(owner)`](#IERC20Permit-nonces) +- [`DOMAIN_SEPARATOR()`](#IERC20Permit-DOMAIN_SEPARATOR) + +#### Functions [!toc] [#IERC20Permit-Functions] + + +Sets `amount` as the allowance of `spender` over `owner`'s tokens after validating the signature. + + + +Returns the current nonce of `owner`. A nonce value must be included whenever a signature for `permit` call is generated. + + + +Returns the domain separator used in generating a message hash for `permit` signature. The domain hashing logic follows the [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) standard. + + +### `IERC4626` [toc] [#IERC4626] + + +```rust +use openzeppelin_token::erc20::extensions::erc4626::interface::IERC4626; +``` + +Interface of the IERC4626 standard as defined in [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626). + +Functions + +- [`asset()`](#IERC4626-asset) +- [`total_assets()`](#IERC4626-total_assets) +- [`convert_to_shares(assets)`](#IERC4626-convert_to_shares) +- [`convert_to_assets(shares)`](#IERC4626-convert_to_assets) +- [`max_deposit(receiver)`](#IERC4626-max_deposit) +- [`preview_deposit(assets)`](#IERC4626-preview_deposit) +- [`deposit(assets, receiver)`](#IERC4626-deposit) +- [`max_mint(receiver)`](#IERC4626-max_mint) +- [`preview_mint(shares)`](#IERC4626-preview_mint) +- [`mint(shares, receiver)`](#IERC4626-mint) +- [`max_withdraw(owner)`](#IERC4626-max_withdraw) +- [`preview_withdraw(assets)`](#IERC4626-preview_withdraw) +- [`withdraw(assets, receiver, owner)`](#IERC4626-withdraw) +- [`max_redeem(owner)`](#IERC4626-max_redeem) +- [`preview_redeem(shares)`](#IERC4626-preview_redeem) +- [`redeem(shares, receiver, owner)`](#IERC4626-redeem) + +Events + +- [`Deposit(sender, owner, assets, shares)`](#IERC4626-Deposit) +- [`Withdraw(sender, receiver, owner, assets, shares)`](#IERC4626-Withdraw) + +#### Functions [!toc] [#IERC4626-Functions] + + +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + +Requirements: + +- MUST be an ERC20 token contract. +- MUST NOT panic. + + + +Returns the total amount of the underlying asset that is "managed" by Vault. + +Requirements: + +- SHOULD include any compounding that occurs from yield. +- MUST be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT panic. + + + +Returns the amount of shares that the Vault would exchange for the amount of `assets` provided irrespective of slippage or fees. + +Requirements: + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT panic unless due to integer overflow caused by an unreasonably large input. +- MUST round down towards 0. + +This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from. + + + +Returns the amount of assets that the Vault would exchange for the amount of `shares` provided irrespective of slippage or fees. + +Requirements: + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT panic unless due to integer overflow caused by an unreasonably large input. +- MUST round down towards 0. + +This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from. + + + +Returns the maximum amount of the underlying asset that can be deposited into the Vault for `receiver`, through a deposit call. + +Requirements: + +- MUST return a limited value if receiver is subject to some deposit limit. +- MUST return 2 \** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction i.e. [IERC4626::deposit](#IERC4626-deposit) should return the same or more shares as `preview_deposit` if called in the same transaction. +- MUST NOT account for deposit limits like those returned from [IERC4626::max\_deposit](#IERC4626-max_deposit) and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_shares](#IERC4626-convert_to_shares) and `preview_deposit` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. + + + +Mints Vault shares to `receiver` by depositing exactly amount of `assets`. + +Requirements: + +- MUST emit the [IERC4626::Deposit](#IERC4626-Deposit) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the deposit execution, and are accounted for during deposit. +- MUST panic if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). + +Most implementations will require pre-approval of the Vault with the Vault's underlying asset token. + + + +Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + +Requirements: + +- MUST return a limited value if receiver is subject to some mint limit. +- MUST return 2 \** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a `mint` call in the same transaction. I.e. [IERC4626::mint](#IERC4626-mint) should return the same or fewer assets as `preview_mint` if called in the same transaction. +- MUST NOT account for mint limits like those returned from [IERC4626::max\_mint](#IERC4626-max_mint) and should always act as though the mint would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_assets](#IERC4626-convert_to_assets) and `preview_mint` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by minting. + + + +Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + +Requirements: + +- MUST emit the [IERC4626::Deposit](#IERC4626-Deposit) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint execution, and are accounted for during mint. +- MUST panic if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). + +Most implementations will require pre-approval of the Vault with the Vault's underlying asset token. + + + +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call. + +Requirements: + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw call in the same transaction i.e. [IERC4626::withdraw](#IERC4626-withdraw) should return the same or fewer shares as `preview_withdraw` if called in the same transaction. +- MUST NOT account for withdrawal limits like those returned from [IERC4626::max\_withdraw](#IERC4626-max_withdraw) and should always act as though the withdrawal would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_shares](#IERC4626-convert_to_shares) and `preview_withdraw` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. + + + +Burns shares from owner and sends exactly assets of underlying tokens to receiver. + +Requirements: + +- MUST emit the [IERC4626::Withdraw](#IERC4626-Withdraw) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the withdraw execution, and are accounted for during withdraw. +- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). + +Some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. + + + +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a redeem call. + +Requirements: + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST return `ERC20::balance_of(owner)` if `owner` is not subject to any withdrawal limit or timelock. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call in the same transaction i.e. [IERC4626::redeem](#IERC4626-redeem) should return the same or more assets as preview\_redeem if called in the same transaction. +- MUST NOT account for redemption limits like those returned from [IERC4626::max\_redeem](#IERC4626-max_redeem) and should always act as though the redemption would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_assets](#IERC4626-convert_to_assets) and `preview_redeem` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by redeeming. + + + +Burns exactly shares from owner and sends assets of underlying tokens to receiver. + +Requirements: + +- MUST emit the [IERC4626::Withdraw](#IERC4626-Withdraw) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the redeem execution, and are accounted for during redeem. +- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). + +Some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. + + +#### Events [!toc] [#IERC4626-Events] + + +Emitted when `sender` exchanges `assets` for `shares` and transfers those `shares` to `owner`. + + + +Emitted when `sender` exchanges `shares`, owned by `owner`, for `assets` and transfers those `assets` to `receiver`. + + +## Core + +### `ERC20Component` [toc] [#ERC20Component] + + +```rust +use openzeppelin_token::erc20::ERC20Component; +``` + +ERC20 component extending [IERC20](#IERC20) and [IERC20Metadata](#IERC20Metadata). + +See [Hooks](#ERC20Component-Hooks) to understand how are hooks used. + +Hooks + +#### ERC20HooksTrait [!toc] [#ERC20Component-ERC20HooksTrait] + +- [`before_update(self, from, recipient, amount)`](#ERC20Component-before_update) +- [`after_update(self, from, recipient, amount)`](#ERC20Component-after_update) + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC20MixinImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20MixinImpl] + +- [`ERC20Impl`](#ERC20Component-Embeddable-Impls-ERC20Impl) +- [`ERC20MetadataImpl`](#ERC20Component-Embeddable-Impls-ERC20MetadataImpl) +- [`ERC20CamelOnlyImpl`](#ERC20Component-Embeddable-Impls-ERC20CamelOnlyImpl) + +Embeddable Implementations + +#### ERC20Impl [!toc] [#ERC20Component-Embeddable-Impls-ERC20Impl] + +- [`total_supply(self)`](#ERC20Component-total_supply) +- [`balance_of(self, account)`](#ERC20Component-balance_of) +- [`allowance(self, owner, spender)`](#ERC20Component-allowance) +- [`transfer(self, recipient, amount)`](#ERC20Component-transfer) +- [`transfer_from(self, sender, recipient, amount)`](#ERC20Component-transfer_from) +- [`approve(self, spender, amount)`](#ERC20Component-approve) + +#### ERC20MetadataImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20MetadataImpl] + +- [`name(self)`](#ERC20Component-name) +- [`symbol(self)`](#ERC20Component-symbol) +- [`decimals(self)`](#ERC20Component-decimals) + +#### ERC20CamelOnlyImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20CamelOnlyImpl] + +- [`totalSupply(self)`](#ERC20Component-totalSupply) +- [`balanceOf(self, account)`](#ERC20Component-balanceOf) +- [`transferFrom(self, sender, recipient, amount)`](#ERC20Component-transferFrom) + +#### ERC20PermitImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20PermitImpl] + +- [`permit(self, owner, spender, amount, deadline, signature)`](#ERC20Component-permit) +- [`nonces(self, owner)`](#ERC20Component-nonces) +- [`DOMAIN_SEPARATOR(self)`](#ERC20Component-DOMAIN_SEPARATOR) + +#### SNIP12MetadataExternalImpl [!toc] [#ERC20Component-Embeddable-Impls-SNIP12MetadataExternalImpl] + +- [`snip12_metadata(self)`](#ERC20Component-snip12_metadata) + +Internal implementations + +#### InternalImpl [!toc] [#ERC20Component-InternalImpl] + +- [`initializer(self, name, symbol)`](#ERC20Component-initializer) +- [`mint(self, recipient, amount)`](#ERC20Component-mint) +- [`burn(self, account, amount)`](#ERC20Component-burn) +- [`update(self, from, to, amount)`](#ERC20Component-update) +- [`_transfer(self, sender, recipient, amount)`](#ERC20Component-_transfer) +- [`_approve(self, owner, spender, amount)`](#ERC20Component-_approve) +- [`_spend_allowance(self, owner, spender, amount)`](#ERC20Component-_spend_allowance) + +Events + +- [`Transfer(from, to, value)`](#ERC20Component-Transfer) +- [`Approval(owner, spender, value)`](#ERC20Component-Approval) + +#### Hooks [!toc] [#ERC20Component-Hooks] + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC20Component is expected to provide an implementation of the ERC20HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc20::ERC20HooksEmptyImpl` which is already available as part of the library for this purpose. + + +Function executed at the beginning of the [update](#ERC20Component-update) function prior to any other logic. + + + +Function executed at the end of the [update](#ERC20Component-update) function. + + +#### Embeddable functions [!toc] [#ERC20Component-Embeddable-Functions] + + +See [IERC20::total\_supply](#IERC20-total_supply). + + + +See [IERC20::balance\_of](#IERC20-balance_of). + + + +See [IERC20::allowance](#IERC20-allowance). + + + +See [IERC20::transfer](#IERC20-transfer). + +Requirements: + +- `recipient` cannot be the zero address. +- The caller must have a balance of at least `amount`. + + + +See [IERC20::transfer\_from](#IERC20-transfer_from). + +Requirements: + +- `sender` cannot be the zero address. +- `sender` must have a balance of at least `amount`. +- `recipient` cannot be the zero address. +- The caller must have allowance for `sender`'s tokens of at least `amount`. + + + +See [IERC20::approve](#IERC20-approve). + +Requirements: + +- `spender` cannot be the zero address. + + + +See [IERC20Metadata::name](#IERC20Metadata-name). + + + +See [IERC20Metadata::symbol](#IERC20Metadata-symbol). + + + +See [IERC20Metadata::decimals](#IERC20Metadata-decimals). + + + +See [IERC20::total\_supply](#IERC20-total_supply). + +Supports the Cairo v0 convention of writing external methods in camelCase as discussed [here](https://github.com/OpenZeppelin/cairo-contracts/discussions/34). + + + +See [IERC20::balance\_of](#IERC20-balance_of). + +Supports the Cairo v0 convention of writing external methods in camelCase as discussed [here](https://github.com/OpenZeppelin/cairo-contracts/discussions/34). + + + +See [IERC20::transfer\_from](#IERC20-transfer_from). + +Supports the Cairo v0 convention of writing external methods in camelCase as discussed [here](https://github.com/OpenZeppelin/cairo-contracts/discussions/34). + + + +Sets `amount` as the allowance of `spender` over `owner`'s tokens after validating the signature. + +Requirements: + +- `owner` is a deployed account contract. +- `spender` is not the zero address. +- `deadline` is not a timestamp in the past. +- `signature` is a valid signature that can be validated with a call to `owner` account. +- `signature` must use the current nonce of the `owner`. + +Emits an [Approval](#ERC20-Approval) event. Every successful call increases \`owner's nonce by one. + + + +Returns the current nonce of `owner`. A nonce value must be included whenever a signature for `permit` call is generated. + + + +Returns the domain separator used in generating a message hash for `permit` signature. The domain hashing logic follows the [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) standard. + + + +Returns the domain name and version used to generate the message hash for permit signature. + +The returned tuple contains: + +- `t.0`: The name used in the [SNIP12Metadata](./utilities#snip12) implementation. +- `t.1`: The version used in the [SNIP12Metadata](./utilities#snip12) implementation. + + +#### Internal functions [!toc] [#ERC20Component-Internal-Functions] + + +Initializes the contract by setting the token name and symbol. This should be used inside of the contract's constructor. + + + +Creates an `amount` number of tokens and assigns them to `recipient`. + +Emits a [Transfer](#ERC20Component-Transfer) event with `from` being the zero address. + +Requirements: + +- `recipient` cannot be the zero address. + + + +Destroys `amount` number of tokens from `account`. + +Emits a [Transfer](#ERC20Component-Transfer) event with `to` set to the zero address. + +Requirements: + +- `account` cannot be the zero address. + + + +Transfers an `amount` of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is the zero address. + +This function can be extended using the [ERC20HooksTrait](#ERC20Component-ERC20HooksTrait), to add functionality before and/or after the transfer, mint, or burn. + +Emits a [Transfer](#ERC20Component-Transfer) event. + + + +Moves `amount` of tokens from `from` to `to`. + +This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic token fees, slashing mechanisms, etc. + +Emits a [Transfer](#ERC20Component-Transfer) event. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `from` must have a balance of at least `amount`. + + + +Sets `amount` as the allowance of `spender` over `owner`'s tokens. + +This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic allowances on behalf of other addresses. + +Emits an [Approval](#ERC20Component-Approval) event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + + + +Updates `owner`'s allowance for `spender` based on spent `amount`. + +This internal function does not update the allowance value in the case of infinite allowance. + +Possibly emits an [Approval](#ERC20Component-Approval) event. + + +#### Events [!toc] [#ERC20Component-Events] + + +See [IERC20::Transfer](#IERC20-Transfer). + + + +See [IERC20::Approval](#IERC20-Approval). + + +## Extensions + +### `ERC4626Component` [toc] [#ERC4626Component] + + +```rust +use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component; +``` + +Extension of ERC20 that implements the [IERC4626](#IERC4626) interface which allows the minting and burning of "shares" in exchange for an underlying "asset." The component leverages traits to configure fees, limits, and decimals. + +[Immutable Component Config](../components#immutable-config) + +#### constants [!toc] [#ERC4626Component-constants] + +- [`UNDERLYING_DECIMALS`](#ERC4626Component-IC-UNDERLYING_DECIMALS) +- [`DECIMALS_OFFSET`](#ERC4626Component-IC-DECIMALS_OFFSET) + +#### functions [!toc] [#ERC4626Component-functions] + +- [`validate()`](#ERC4626Component-IC-validate) + +Hooks + +#### FeeConfigTrait [!toc] [#ERC4626Component-FeeConfigTrait] + +- [`calculate_deposit_fee(self, assets, shares)`](#ERC4626Component-calculate_deposit_fee) +- [`calculate_mint_fee(self, assets, shares)`](#ERC4626Component-calculate_mint_fee) +- [`calculate_withdraw_fee(self, assets, shares)`](#ERC4626Component-calculate_withdraw_fee) +- [`calculate_redeem_fee(self, assets, shares)`](#ERC4626Component-calculate_redeem_fee) + +#### LimitConfigTrait [!toc] [#ERC4626Component-LimitConfigTrait] + +- [`deposit_limit(self, receiver)`](#ERC4626Component-deposit_limit) +- [`mint_limit(self, receiver)`](#ERC4626Component-mint_limit) +- [`withdraw_limit(self, owner)`](#ERC4626Component-withdraw_limit) +- [`redeem_limit(self, owner)`](#ERC4626Component-redeem_limit) + +#### ERC4626HooksTrait [!toc] [#ERC4626Component-ERC4626HooksTrait] + +- [`before_deposit(self, caller, receiver, assets, shares, fee)`](#ERC4626Component-before_deposit) +- [`after_deposit(self, caller, receiver, assets, shares, fee)`](#ERC4626Component-after_deposit) +- [`before_withdraw(self, caller, receiver, owner, assets, shares, fee)`](#ERC4626Component-before_withdraw) +- [`after_withdraw(self, caller, receiver, owner, assets, shares, fee)`](#ERC4626Component-after_withdraw) + +#### AssetsManagementTrait [!toc] [#ERC4626Component-AssetsManagementTrait] + +- [`get_total_assets(self)`](#ERC4626Component-get_total_assets) +- [`transfer_assets_in(self, from, assets)`](#ERC4626Component-transfer_assets_in) +- [`transfer_assets_out(self, to, assets)`](#ERC4626Component-transfer_assets_out) + +Embeddable Implementations + +#### ERC4626Impl [!toc] [#ERC4626Component-Embeddable-Impls-ERC4626Impl] + +- [`asset(self)`](#ERC4626Component-asset) +- [`total_assets(self)`](#ERC4626Component-total_assets) +- [`convert_to_shares(self, assets)`](#ERC4626Component-convert_to_shares) +- [`convert_to_assets(self, shares)`](#ERC4626Component-convert_to_assets) +- [`max_deposit(self, receiver)`](#ERC4626Component-max_deposit) +- [`preview_deposit(self, assets)`](#ERC4626Component-preview_deposit) +- [`deposit(self, assets, receiver)`](#ERC4626Component-deposit) +- [`max_mint(self, receiver)`](#ERC4626Component-max_mint) +- [`preview_mint(self, shares)`](#ERC4626Component-preview_mint) +- [`mint(self, shares, receiver)`](#ERC4626Component-mint) +- [`max_withdraw(self, owner)`](#ERC4626Component-max_withdraw) +- [`preview_withdraw(self, assets)`](#ERC4626Component-preview_withdraw) +- [`withdraw(self, assets, receiver, owner)`](#ERC4626Component-withdraw) +- [`max_redeem(self, owner)`](#ERC4626Component-max_redeem) +- [`preview_redeem(self, shares)`](#ERC4626Component-preview_redeem) +- [`redeem(self, shares, receiver, owner)`](#ERC4626Component-redeem) + +#### ERC20Impl [!toc] [#ERC4626Component-Embeddable-Impls-ERC20Impl] + +- [`total_supply(self)`](#ERC20Component-total_supply) +- [`balance_of(self, account)`](#ERC20Component-balance_of) +- [`allowance(self, owner, spender)`](#ERC20Component-allowance) +- [`transfer(self, recipient, amount)`](#ERC20Component-transfer) +- [`transfer_from(self, sender, recipient, amount)`](#ERC20Component-transfer_from) +- [`approve(self, spender, amount)`](#ERC20Component-approve) + +#### ERC4626MetadataImpl [!toc] [#ERC4626Component-Embeddable-Impls-ERC4626MetadataImpl] + +- [`name(self)`](#ERC4626Component-name) +- [`symbol(self)`](#ERC4626Component-symbol) +- [`decimals(self)`](#ERC4626Component-decimals) + +Internal functions + +#### InternalImpl [!toc] [#ERC4626Component-InternalImpl] + +- [`initializer(self, asset_address)`](#ERC4626Component-initializer) +- [`_deposit(self, caller, receiver, assets, shares)`](#ERC4626Component-_deposit) +- [`_withdraw(self, caller, receiver, owner, assets, shares)`](#ERC4626Component-_withdraw) +- [`_convert_to_shares(self, assets, rounding)`](#ERC4626Component-_convert_to_shares) +- [`_convert_to_assets(self, shares, rounding)`](#ERC4626Component-_convert_to_assets) + +#### Immutable Config [!toc] [#ERC4626Component-Immutable-Config] + + +Should match the underlying asset's decimals. The default value is `18`. + + + +Corresponds to the representational offset between `UNDERLYING_DECIMALS` and the vault decimals. The greater the offset, the more expensive it is for attackers to execute an inflation attack. + + + +Validates the given implementation of the contract's configuration. + +Requirements: + +- `UNDERLYING_DECIMALS` + `DECIMALS_OFFSET` cannot exceed 255 (max u8). + +This function is called by the contract's initializer. + + +#### Hooks [!toc] [#ERC4626Component-Hooks] + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC4626Component is expected to provide an implementation of the ERC4626HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc20::extensions::erc4626::ERC4626EmptyHooks` which is already available as part of the library for this purpose. + +#### FeeConfigTrait [!toc] [#ERC4626Component-FeeConfigTrait] + +The logic for calculating entry and exit fees is expected to be defined at the contract level. Defaults to no entry or exit fees. + +The FeeConfigTrait hooks directly into the preview methods of the ERC4626 component. The preview methods must return as close to the exact amount of shares or assets as possible if the actual (previewed) operation occurred in the same transaction (according to [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec). All operations use their corresponding preview method as the value of assets or shares being moved to or from the user. The fees calculated in FeeConfigTrait are used to adjust the final asset and share amounts used in both the preview and the actual operations. + +To transfer fees, this trait needs to be coordinated with `ERC4626Component::ERC4626Hooks`. + +See implementation examples: + +- Contract charging fees in assets: [ERC4626AssetsFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L253) +- Contract charging fees in shares: [ERC4626SharesFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L426) + + +Calculates the entry fee for a deposit during [preview\_deposit](#ERC4626Component-preview_deposit). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [after\_deposit](#ERC4626Component-after_deposit) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be minted to the fee recipient. + + + +Calculates the entry fee for a mint during [preview\_mint](#ERC4626Component-preview_mint). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [after\_deposit](#ERC4626Component-after_deposit) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be minted to the fee recipient. + + + +Calculates the exit fee for a withdraw during [preview\_withdraw](#ERC4626Component-preview_withdraw). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [before\_withdraw](#ERC4626Component-before_withdraw) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be transferred from the owner to the fee recipient. + + + +Calculates the exit fee for a redeem during [preview\_redeem](#ERC4626Component-preview_redeem). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [before\_withdraw](#ERC4626Component-before_withdraw) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be transferred from the owner to the fee recipient. + + +#### LimitConfigTrait [!toc] [#ERC4626Component-LimitConfigTrait] + +Sets limits to the target exchange type and is expected to be defined at the contract level. These limits correspond directly to the `max_` i.e. `deposit_limit` → `max_deposit`. + +The [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec states that the `max_` methods must take into account all global and user-specific limits. If an operation is disabled (even temporarily), the corresponding limit MUST be `0` and MUST NOT panic. + + +The max deposit allowed. + +Defaults (`Option::None`) to 2 \** 256 - 1. + + + +The max mint allowed. + +Defaults (`Option::None`) to 2 \** 256 - 1. + + + +The max withdraw allowed. + +Defaults (`Option::None`) to the full asset balance of `owner` converted from shares. + + + +The max redeem allowed. + +Defaults (`Option::None`) to the full asset balance of `owner`. + + +#### ERC4626HooksTrait [!toc] [#ERC4626Component-ERC4626HooksTrait] + +Allows contracts to hook logic into deposit and withdraw transactions. This is where contracts can transfer fees. + +ERC4626 preview methods must be inclusive of any entry or exit fees. Fees are calculated using [FeeConfigTrait](#ERC4626Component-FeeConfigTrait) methods and automatically adjust the final asset and share amounts. Fee transfers are handled in `ERC4626HooksTrait` methods. + +Special care must be taken when calling external contracts in these hooks. In that case, consider implementing reentrancy protections. For example, in the `withdraw` flow, the `withdraw_limit` is checked **before** the `before_withdraw` hook is invoked. If this hook performs a reentrant call that invokes `withdraw` again, the subsequent check on `withdraw_limit` will be done before the first withdrawal's core logic (e.g., burning shares and transferring assets) is executed. This could lead to bypassing withdrawal constraints or draining funds. + +See the [ERC4626AssetsFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L253) and [ERC4626SharesFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L426) examples. + + +Hooks into [\_deposit](#ERC4626Component-_deposit). + +Executes logic before transferring assets and minting shares. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will spend and receive, respectively. Asset fees are included in assets; share fees are excluded from shares. + + + +Hooks into [\_deposit](#ERC4626Component-_deposit). + +Executes logic after transferring assets and minting shares. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will spend and receive, respectively. Asset fees are included in assets; share fees are excluded from shares. + + + +Hooks into [\_withdraw](#ERC4626Component-_withdraw). + +Executes logic before burning shares and transferring assets. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will receive and spend, respectively. Asset fees are excluded from assets; share fees are included in shares. + + + +Hooks into [\_withdraw](#ERC4626Component-_withdraw). + +Executes logic after burning shares and transferring assets. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will receive and spend, respectively. Asset fees are excluded from assets; share fees are included in shares. + + +#### AssetsManagementTrait [!toc] [#ERC4626Component-AssetsManagementTrait] + +Defines how the ERC4626 vault manages its underlying assets. This trait provides the core asset management functionality for the vault, abstracting the actual storage and transfer mechanisms. It enables two primary implementation patterns: + +1. **Self-managed assets**: The vault contract holds assets directly on its own address. This is the default behavior provided by `ERC4626SelfAssetsManagement` implementation. +2. **External vault**: Assets are managed by an external contract, allowing for more complex asset management strategies. The exact implementation is expected to be defined by the contract implementing the ERC4626 component. + +The trait methods are called during deposit, withdrawal, and total assets calculations, ensuring that the vault's share pricing remains accurate regardless of the underlying asset management strategy. + +Implementations must ensure that `get_total_assets` returns the actual amount of assets that can be withdrawn by users. Inaccurate reporting can lead to incorrect share valuations and potential economic attacks. + +See implementation examples: + +- Self-managed vault: [ERC4626SelfAssetsManagement](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20/extensions/erc4626/erc4626.cairo#L760). +- External vault: [ERC4626ExternalAssetsManagement](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L92). + + +Returns the total amount of underlying assets under the vault's management. Used for share price calculations and determining the vault's total value. + +This method should return the actual amount of assets that the vault controls and that can be used to satisfy withdrawal requests. For self-managed vaults, this is typically the vault contract's token balance. For external vaults, this should include any assets deposited in external protocols, minus any that are locked or unredeemable. + +The accuracy of this method is critical for proper vault operation: - Overreporting can lead to share dilution and user losses. - Underreporting can lead to share inflation and potential economic attacks. + + + +Transfers assets from an external address into the vault's management. Called during `deposit` and `mint` operations. + +This method should handle the actual transfer of underlying assets from the `from` address into the vault's control. For self-managed vaults, this typically means transferring tokens to the vault contract's address. For external vaults, this might involve transferring into an external contract. + +Requirements: + +- MUST transfer exactly `assets` amount of the underlying token. +- SHOULD revert if the transfer fails or insufficient allowance/balance. + + + +Transfers assets from the vault's management to an external address. Called during withdraw and redeem operations. + +This method should handle the actual transfer of underlying assets from the vault's control to the `to` address. For self-managed vaults, this typically means transferring tokens from the vault contract's address. For external vaults, this might involve withdrawing from an external contract first. + +Requirements: + +- MUST transfer exactly `assets` amount of the underlying token. +- SHOULD revert if insufficient assets are available or transfer fails. + + +#### Embeddable functions [!toc] [#ERC4626Component-Embeddable-Functions] + + +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + + + +Returns the total amount of the underlying asset that is "managed" by Vault. + + + +Returns the amount of shares that the Vault would exchange for the amount of assets provided irrespective of slippage or fees. + +As per the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec, this may panic *only* if there's an overflow from an unreasonably large input. + + + +Returns the amount of assets that the Vault would exchange for the amount of shares provided irrespective of slippage or fees. + +As per the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec, this may panic *only* if there's an overflow from an unreasonably large input. + + + +Returns the maximum amount of the underlying asset that can be deposited into the Vault for the `receiver`, through a [deposit](#ERC4626Component-deposit) call. + +The default max deposit value is 2 \** 256 - 1. + +This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::deposit\_limit](#ERC4626Component-deposit_limit). + + + +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions. + +The default deposit preview value is the full amount of shares. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_deposit\_fee](#ERC4626Component-calculate_deposit_fee). + +This method must be inclusive of entry fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Mints Vault shares to `receiver` by depositing exactly `assets` of underlying tokens. Returns the amount of newly-minted shares. + +Requirements: + +- `assets` is less than or equal to the max deposit amount for `receiver`. + +Emits a [Deposit](#IERC4626-Deposit) event. + + + +Returns the maximum amount of the Vault shares that can be minted for `receiver` through a [mint](#ERC4626Component-mint) call. + +The default max mint value is 2 \** 256 - 1. + +This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::mint\_limit](#ERC4626Component-mint_limit). + + + +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions. + +The default mint preview value is the full amount of assets. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_mint\_fee](#ERC4626Component-calculate_mint_fee). + +This method must be inclusive of entry fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Mints exactly Vault `shares` to `receiver` by depositing amount of underlying tokens. Returns the amount deposited assets. + +Requirements: + +- `shares` is less than or equal to the max shares amount for `receiver`. + +Emits a [Deposit](#IERC4626-Deposit) event. + + + +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a [withdraw](#ERC4626Component-withdraw) call. + +The default max withdraw value is the full balance of assets for `owner` (converted from shares). This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::withdraw\_limit](#ERC4626Component-withdraw_limit). + +With customized limits, the maximum withdraw amount will either be the custom limit itself or `owner`'s total asset balance, whichever value is less. + + + +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions. + +The default withdraw preview value is the full amount of shares. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_withdraw\_fee](#ERC4626Component-calculate_withdraw_fee). + +This method must be inclusive of exit fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Burns shares from `owner` and sends exactly `assets` of underlying tokens to `receiver`. + +Requirements: + +- `assets` is less than or equal to the max withdraw amount of `owner`. + +Emits a [Withdraw](#IERC4626-Withdraw) event. + + + +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a [redeem](#ERC4626Component-redeem) call. + +The default max redeem value is the full balance of assets for `owner`. This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::redeem\_limit](#ERC4626Component-redeem_limit). + +With customized limits, the maximum redeem amount will either be the custom limit itself or `owner`'s total asset balance, whichever value is less. + + + +Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions. + +The default redeem preview value is the full amount of assets. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_redeem\_fee](#ERC4626Component-calculate_redeem_fee). + +This method must be inclusive of exit fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Burns exactly `shares` from `owner` and sends assets of underlying tokens to `receiver`. + +Requirements: + +- `shares` is less than or equal to the max redeem amount of `owner`. + +Emits a [Withdraw](#IERC4626-Withdraw) event. + + + +Returns the name of the token. + + + +Returns the ticker symbol of the token, usually a shorter version of the name. + + + +Returns the cumulative number of decimals which includes both `UNDERLYING_DECIMALS` and `OFFSET_DECIMALS`. Both of which must be defined in the [ImmutableConfig](#ERC4626Component-Immutable-Config) inside the implementing contract. + + +#### Internal functions [!toc] [#ERC4626Component-Internal-Functions] + + +Validates the [ImmutableConfig](#ERC4626Component-Immutable-Config) constants and sets the `asset_address` to the vault. This should be set in the contract's constructor. + +Requirements: + +- `asset_address` cannot be the zero address. + + + +Internal logic for [deposit](#ERC4626Component-deposit) and [mint](#ERC4626Component-mint). + +Transfers `assets` from `caller` to the Vault contract then mints `shares` to `receiver`. Fees can be transferred in the `ERC4626Hooks::after_deposit` hook which is executed after assets are transferred and shares are minted. + +Requirements: + +- [ERC20::transfer\_from](#ERC20Component-transfer_from) must return true. + +Emits two [ERC20::Transfer](#ERC20Component-Transfer) events (`ERC20::mint` and `ERC20::transfer_from`). + +Emits a [Deposit](#IERC4626-Deposit) event. + + + +Internal logic for [withdraw](#ERC4626Component-withdraw) and [redeem](#ERC4626Component-redeem). + +Burns `shares` from `owner` and then transfers `assets` to `receiver`. Fees can be transferred in the `ERC4626Hooks::before_withdraw` hook which is executed before shares are burned and assets are transferred. + +Requirements: + +- [ERC20::transfer](#ERC20Component-transfer) must return true. + +Emits two [ERC20::Transfer](#ERC20Component-Transfer) events (`ERC20::burn` and `ERC20::transfer`). + +Emits a [Withdraw](#IERC4626-Withdraw) event. + + + +Internal conversion function (from assets to shares) with support for `rounding` direction. + + + +Internal conversion function (from shares to assets) with support for `rounding` direction. + + +## Presets + +### `ERC20Upgradeable` [toc] [#ERC20Upgradeable] + + +```rust +use openzeppelin_presets::ERC20Upgradeable; +``` + +Upgradeable ERC20 contract leveraging [ERC20Component](#ERC20Component) with a fixed-supply mechanism for token distribution. + +[Sierra class hash](../presets) + +```text +{{ERC20UpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, name, symbol, fixed_supply, recipient, owner)`](#ERC20Upgradeable-constructor) + +Embedded Implementations + +#### ERC20MixinImpl [!toc] [#ERC20Upgradeable-Embedded-Impls-ERC20MixinImpl] + +- [`ERC20MixinImpl`](#ERC20Component-Embeddable-Mixin-Impl) + +#### OwnableMixinImpl [!toc] [#ERC20Upgradeable-Embedded-Impls-OwnableMixinImpl] + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#ERC20Upgradeable-upgrade) + +#### Constructor [!toc] [#ERC20Upgradeable-Constructor] + + +Sets the `name` and `symbol` and mints `fixed_supply` tokens to `recipient`. Assigns `owner` as the contract owner with permissions to upgrade. + + +#### External functions [!toc] [#ERC20Upgradeable-External-Functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/2.x/api/erc721.mdx b/docs/content/contracts-cairo/2.x/api/erc721.mdx new file mode 100644 index 00000000..9436efa2 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/erc721.mdx @@ -0,0 +1,1154 @@ +--- +title: ERC721 +--- + +This module provides interfaces, presets, and utilities related to ERC721 contracts. + +For an overview of ERC721, read our [ERC721 guide](../erc721). + +## Interfaces + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `IERC721` [toc] [#IERC721] + + +```rust +use openzeppelin_token::erc721::interface::IERC721; +``` + +Interface of the IERC721 standard as defined in [EIP721](https://eips.ethereum.org/EIPS/eip-721). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943 +``` + +Functions + +- [`balance_of(account)`](#IERC721-balance_of) +- [`owner_of(token_id)`](#IERC721-owner_of) +- [`safe_transfer_from(from, to, token_id, data)`](#IERC721-safe_transfer_from) +- [`transfer_from(from, to, token_id)`](#IERC721-transfer_from) +- [`approve(to, token_id)`](#IERC721-approve) +- [`set_approval_for_all(operator, approved)`](#IERC721-set_approval_for_all) +- [`get_approved(token_id)`](#IERC721-get_approved) +- [`is_approved_for_all(owner, operator)`](#IERC721-is_approved_for_all) + +Events + +- [`Approval(owner, approved, token_id)`](#IERC721-Approval) +- [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll) +- [`Transfer(from, to, token_id)`](#IERC721-Transfer) + +#### Functions [!toc] [#IERC721-Functions] + + +Returns the number of NFTs owned by `account`. + + + +Returns the owner address of `token_id`. + + + +Transfer ownership of `token_id` from `from` to `to`, checking first that `to` is aware of the ERC721 protocol to prevent tokens being locked forever. For information regarding how contracts communicate their awareness of the ERC721 protocol, see [Receiving Tokens](../erc721#receiving_tokens). + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Transfer ownership of `token_id` from `from` to `to`. + +Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 transfers or else they may be permanently lost. Usage of [IERC721::safe\_transfer\_from](#IERC721-safe_transfer_from) prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Change or reaffirm the approved address for an NFT. + +Emits an [Approval](#IERC721-Approval) event. + + + +Enable or disable approval for `operator` to manage all of the caller's assets. + +Emits an [ApprovalForAll](#IERC721-ApprovalForAll) event. + + + +Returns the address approved for `token_id`. + + + +Query if `operator` is an authorized operator for `owner`. + + +#### Events [!toc] [#IERC721-Events] + + +Emitted when `owner` enables `approved` to manage the `token_id` token. + + + +Emitted when `owner` enables or disables `operator` to manage the `token_id` token. + + + +Emitted when `token_id` token is transferred from `from` to `to`. + + +### `IERC721Metadata` [toc] [#IERC721Metadata] + + +```rust +use openzeppelin_token::erc721::interface::IERC721Metadata; +``` + +Interface for the optional metadata functions in [EIP721](https://eips.ethereum.org/EIPS/eip-721). + +[SRC5 ID](./introspection#ISRC5) + +```text +0xabbcd595a567dce909050a1038e055daccb3c42af06f0add544fa90ee91f25 +``` + +Functions + +- [`name()`](#IERC721Metadata-name) +- [`symbol()`](#IERC721Metadata-symbol) +- [`token_uri(token_id)`](#IERC721Metadata-token_uri) + +#### Functions [!toc] [#IERC721Metadata-Functions] + + +Returns the NFT name. + + + +Returns the NFT ticker symbol. + + + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. + + +### `IERC721Receiver` [toc] [#IERC721Receiver] + + +```rust +use openzeppelin_token::erc721::interface::IERC721Receiver; +``` + +Interface for contracts that support receiving `safe_transfer_from` transfers. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc +``` + +Functions + +- [`on_erc721_received(operator, from, token_id, data)`](#IERC721Receiver-on_erc721_received) + +#### Functions [!toc] [#IERC721Receiver-Functions] + + +Whenever an IERC721 `token_id` token is transferred to this non-account contract via [IERC721::safe\_transfer\_from](#IERC721-safe_transfer_from) by `operator` from `from`, this function is called. + + +### `IERC721Enumerable` [toc] [#IERC721Enumerable] + + +Interface for the optional enumerable functions in [EIP721](https://eips.ethereum.org/EIPS/eip-721). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x16bc0f502eeaf65ce0b3acb5eea656e2f26979ce6750e8502a82f377e538c87 +``` + +Functions + +- [`total_supply()`](#IERC721Enumerable-total_supply) +- [`token_by_index(index)`](#IERC721Enumerable-token_by_index) +- [`token_of_owner_by_index(owner, index)`](#IERC721Enumerable-token_of_owner_by_index) + +#### Functions [!toc] [#IERC721Enumerable-Functions] + + +Returns the total amount of tokens stored by the contract. + + + +Returns a token id at a given `index` of all the tokens stored by the contract. Use along with [IERC721Enumerable::total\_supply](#IERC721Enumerable-total_supply) to enumerate all tokens. + + + +Returns the token id owned by `owner` at a given `index` of its token list. Use along with [IERC721::balance\_of](#IERC721-balance_of) to enumerate all of `owner`'s tokens. + + +## Core + +### `ERC721Component` [toc] [#ERC721Component] + + +```rust +use openzeppelin_token::erc721::ERC721Component; +``` + +ERC721 component implementing [IERC721](#IERC721) and [IERC721Metadata](#IERC721Metadata). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +See [Hooks](#ERC721Component-Hooks) to understand how are hooks used. + +Hooks + +#### ERC721HooksTrait [!toc] [#ERC721Component-ERC721HooksTrait] + +- [`before_update(self, to, token_id, auth)`](#ERC721Component-before_update) +- [`after_update(self, to, token_id, auth)`](#ERC721Component-after_update) + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC721MixinImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721MixinImpl] + +- [`ERC721Impl`](#ERC721Component-Embeddable-Impls-ERC721Impl) +- [`ERC721MetadataImpl`](#ERC721Component-Embeddable-Impls-ERC721MetadataImpl) +- [`ERC721CamelOnlyImpl`](#ERC721Component-Embeddable-Impls-ERC721CamelOnlyImpl) +- [`ERC721MetadataCamelOnlyImpl`](#ERC721Component-Embeddable-Impls-ERC721MetadataCamelOnlyImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### ERC721Impl [!toc] [#ERC721Component-Embeddable-Impls-ERC721Impl] + +- [`balance_of(self, account)`](#ERC721Component-balance_of) +- [`owner_of(self, token_id)`](#ERC721Component-owner_of) +- [`safe_transfer_from(self, from, to, token_id, data)`](#ERC721Component-safe_transfer_from) +- [`transfer_from(self, from, to, token_id)`](#ERC721Component-transfer_from) +- [`approve(self, to, token_id)`](#ERC721Component-approve) +- [`set_approval_for_all(self, operator, approved)`](#ERC721Component-set_approval_for_all) +- [`get_approved(self, token_id)`](#ERC721Component-get_approved) +- [`is_approved_for_all(self, owner, operator)`](#ERC721Component-is_approved_for_all) + +#### ERC721MetadataImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721MetadataImpl] + +- [`name(self)`](#ERC721Component-name) +- [`symbol(self)`](#ERC721Component-symbol) +- [`token_uri(self, token_id)`](#ERC721Component-token_uri) + +#### ERC721CamelOnlyImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721CamelOnlyImpl] + +- [`balanceOf(self, account)`](#ERC721Component-balanceOf) +- [`ownerOf(self, tokenId)`](#ERC721Component-ownerOf) +- [`safeTransferFrom(self, from, to, tokenId, data)`](#ERC721Component-safeTransferFrom) +- [`transferFrom(self, from, to, tokenId)`](#ERC721Component-transferFrom) +- [`setApprovalForAll(self, operator, approved)`](#ERC721Component-setApprovalForAll) +- [`getApproved(self, tokenId)`](#ERC721Component-getApproved) +- [`isApprovedForAll(self, owner, operator)`](#ERC721Component-isApprovedForAll) + +#### ERC721MetadataCamelOnlyImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721MetadataCamelOnlyImpl] + +- [`tokenURI(self, tokenId)`](#ERC721Component-tokenURI) + +#### SRC5Impl [!toc] [#ERC721Component-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal functions + +#### InternalImpl [!toc] [#ERC721Component-InternalImpl] + +- [`initializer(self, name, symbol, base_uri)`](#ERC721Component-initializer) +- [`initializer_no_metadata(self)`](#ERC721Component-initializer_no_metadata) +- [`exists(self, token_id)`](#ERC721Component-exists) +- [`transfer(self, from, to, token_id)`](#ERC721Component-transfer) +- [`mint(self, to, token_id)`](#ERC721Component-mint) +- [`safe_transfer(self, from, to, token_id, data)`](#ERC721Component-safe_transfer) +- [`safe_mint(self, to, token_id, data)`](#ERC721Component-safe_mint) +- [`burn(self, token_id)`](#ERC721Component-burn) +- [`update(self, to, token_id, auth)`](#ERC721Component-update) +- [`_owner_of(self, token_id)`](#ERC721Component-_owner_of) +- [`_require_owned(self, token_id)`](#ERC721Component-_require_owned) +- [`_approve(self, to, token_id, auth)`](#ERC721Component-_approve) +- [`_approve_with_optional_event(self, to, token_id, auth, emit_event)`](#ERC721Component-_approve_with_optional_event) +- [`_set_approval_for_all(self, owner, operator, approved)`](#ERC721Component-_set_approval_for_all) +- [`_set_base_uri(self, base_uri)`](#ERC721Component-_set_base_uri) +- [`_base_uri(self)`](#ERC721Component-_base_uri) +- [`_is_authorized(self, owner, spender, token_id)`](#ERC721Component-_is_authorized) +- [`_check_authorized(self, owner, spender, token_id)`](#ERC721Component-_check_authorized) + +Events + +IERC721 + +- [`Approval(owner, approved, token_id)`](#ERC721Component-Approval) +- [`ApprovalForAll(owner, operator, approved)`](#ERC721Component-ApprovalForAll) +- [`Transfer(from, to, token_id)`](#ERC721Component-Transfer) + +#### Hooks [!toc] [#ERC721Component-Hooks] + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC721Component is expected to provide an implementation of the ERC721HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc721::ERC721HooksEmptyImpl` which is already available as part of the library for this purpose. + + +Function executed at the beginning of the [update](#ERC721Component-update) function prior to any other logic. + + + +Function executed at the end of the [update](#ERC721Component-update) function. + + +#### [](#embeddable_functions)Embeddable functions [!toc] [#embeddable_functions] + + +See [IERC721::balance\_of](#IERC721-balance_of). + + + +See [IERC721::owner\_of](#IERC721-owner_of). + +Requirements: + +- `token_id` exists. + + + +See [IERC721::safe\_transfer\_from](#IERC721-safe_transfer_from). + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `to` is not the zero address. +- `from` is not the zero address. +- `token_id` exists. +- `to` is either an account contract or supports the [IERC721Receiver](#IERC721Receiver) interface. + + + +See [IERC721::transfer\_from](#IERC721-transfer_from). + +Requirements: + +- Caller either approved or the `token_id` owner. +- `to` is not the zero address. +- `from` is not the zero address. +- `token_id` exists. + + + +See [IERC721::approve](#IERC721-approve). + +Requirements: + +- The caller is either an approved operator or the `token_id` owner. +- `to` cannot be the token owner or the zero address. +- `token_id` exists. + + + +See [IERC721::set\_approval\_for\_all](#IERC721-set_approval_for_all). + +Requirements: + +- `operator` is not the zero address. + + + +See [IERC721::get\_approved](#IERC721-get_approved). + +Requirements: + +- `token_id` exists. + + + +See [IERC721::is\_approved\_for\_all](#IERC721-is_approved_for_all). + + + +See [IERC721Metadata::name](#IERC721Metadata-name). + + + +See [IERC721Metadata::symbol](#IERC721Metadata-symbol). + + + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. If a base URI is set, the resulting URI for each token will be the concatenation of the base URI and the token ID. For example, the base URI `https://token-cdn-domain/` would be returned as `https://token-cdn-domain/123` for token ID `123`. + +If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. + + + +See [ERC721Component::balance\_of](#ERC721Component-balance_of). + + + +See [ERC721Component::owner\_of](#ERC721Component-owner_of). + + + +See [ERC721Component::safe\_transfer\_from](#ERC721Component-safe_transfer_from). + + + +See [ERC721Component::transfer\_from](#ERC721Component-transfer_from). + + + +See [ERC721Component::set\_approval\_for\_all](#ERC721Component-set_approval_for_all). + + + +See [ERC721Component::get\_approved](#ERC721Component-get_approved). + + + +See [ERC721Component::is\_approved\_for\_all](#ERC721Component-is_approved_for_all). + + + +See [ERC721Component::token\_uri](#ERC721Component-token_uri). + + +#### [](#internal_functions)Internal functions [!toc] [#internal_functions] + + +Initializes the contract by setting the token name and symbol. This should be used inside the contract's constructor. + +Most ERC721 contracts expose the [IERC721Metadata](#IERC721Metadata) interface which is what this initializer is meant to support. If the contract DOES NOT expose the [IERC721Metadata](#IERC721Metadata) interface, meaning the token does not have a name, symbol, or URI, the contract must instead use [initializer\_no\_metadata](#ERC721Component-initializer_no_metadata) in the constructor. Failure to abide by these instructions can lead to unexpected issues especially with UIs. + + + +Initializes the contract with no metadata by registering only the IERC721 interface. + +This initializer should ONLY be used during construction in the very specific instance when the contract does NOT expose the [IERC721Metadata](#IERC721Metadata) interface. Initializing a contract with this initializer means that tokens will not have a name, symbol, or URI. + + + +Internal function that returns whether `token_id` exists. + +Tokens start existing when they are minted ([mint](#ERC721-mint)), and stop existing when they are burned ([burn](#ERC721-burn)). + + + +Transfers `token_id` from `from` to `to`. + +Internal function without access restriction. + +This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + +Requirements: + +- `to` is not the zero address. +- `from` is the token owner. +- `token_id` exists. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Mints `token_id` and transfers it to `to`. Internal function without access restriction. + +This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + +Requirements: + +- `to` is not the zero address. +- `token_id` does not exist. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Transfers ownership of `token_id` from `from` if `to` is either an account or `IERC721Receiver`. + +`data` is additional data, it has no specified format and is forwarded in `IERC721Receiver::on_erc721_received` to `to`. + +This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + +Requirements: + +- `to` cannot be the zero address. +- `from` must be the token owner. +- `token_id` exists. +- `to` is either an account contract or supports the `IERC721Receiver` interface. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Mints `token_id` if `to` is either an account or `IERC721Receiver`. + +`data` is additional data, it has no specified format and is forwarded in `IERC721Receiver::on_erc721_received` to `to`. + +This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + +Requirements: + +- `token_id` does not exist. +- `to` is either an account contract or supports the `IERC721Receiver` interface. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Destroys `token_id`. The approval is cleared when the token is burned. + +This internal function does not check if the caller is authorized to operate on the token. + +Requirements: + +- `token_id` exists. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner (or `to`) is the zero address. Returns the owner of the `token_id` before the update. + +The `auth` argument is optional. If the value passed is non-zero, then this function will check that `auth` is either the owner of the token, or approved to operate on the token (by the owner). + +Emits a [Transfer](#IERC721-Transfer) event. + +This function can be extended using the `ERC721HooksTrait`, to add functionality before and/or after the transfer, mint, or burn. + + + +Internal function that returns the owner address of `token_id`. + + + +Version of [\_owner\_of](#ERC721Component-_owner_of) that panics if owner is the zero address. + + + +Approve `to` to operate on `token_id` + +The `auth` argument is optional. If the value passed is non-zero, then this function will check that `auth` is either the owner of the token, or approved to operate on all tokens held by this owner. + +Emits an [Approval](#IERC721-Approval) event. + + + +Variant of [\_approve](#ERC721Component-_approve) with an optional flag to enable or disable the `Approval` event. The event is not emitted in the context of transfers. + +If `auth` is zero and `emit_event` is false, this function will not check that the token exists. + +Requirements: + +- if `auth` is non-zero, it must be either the owner of the token or approved to operate on all of its tokens. + +May emit an [Approval](#IERC721-Approval) event. + + + +Enables or disables approval for `operator` to manage all of the `owner` assets. + +Requirements: + +- `operator` is not the zero address. + +Emits an [Approval](#IERC721-Approval) event. + + + +Internal function that sets the `base_uri`. + + + +Base URI for computing [token\_uri](#IERC721Metadata-token_uri). + +If set, the resulting URI for each token will be the concatenation of the base URI and the token ID. Returns an empty `ByteArray` if not set. + + + +Returns whether `spender` is allowed to manage `owner`'s tokens, or `token_id` in particular (ignoring whether it is owned by `owner`). + +This function assumes that `owner` is the actual owner of `token_id` and does not verify this assumption. + + + +Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. +- `spender` must be the owner of `token_id` or be approved to operate on it. + +This function assumes that `owner` is the actual owner of `token_id` and does not verify this assumption. + + +#### [](#events_2)Events [!toc] [#events_2] + + +See [IERC721::Approval](#IERC721-Approval). + + + +See [IERC721::ApprovalForAll](#IERC721-ApprovalForAll). + + + +See [IERC721::Transfer](#IERC721-Transfer). + + +### `ERC721ReceiverComponent` [toc] [#ERC721ReceiverComponent] + + +```rust +use openzeppelin_token::erc721::ERC721ReceiverComponent; +``` + +ERC721Receiver component implementing [IERC721Receiver](#IERC721Receiver). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERCReceiverMixinImpl [!toc] [#ERC721ReceiverComponent-Embeddable-Impls-ERCReceiverMixinImpl] + +- [`ERC721ReceiverImpl`](#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl) +- [`ERC721ReceiverCamelImpl`](#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### ERC721ReceiverImpl [!toc] [#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl] + +- [`on_erc721_received(self, operator, from, token_id, data)`](#ERC721ReceiverComponent-on_erc721_received) + +#### ERC721ReceiverCamelImpl [!toc] [#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl] + +- [`onERC721Received(self, operator, from, tokenId, data)`](#ERC721ReceiverComponent-onERC721Received) + +Internal Functions + +#### InternalImpl [!toc] [#ERC721ReceiverComponent-InternalImpl] + +- [`initializer(self)`](#ERC721ReceiverComponent-initializer) + +#### [](#embeddable_functions_2)Embeddable functions [!toc] [#embeddable_functions_2] + + +Returns the `IERC721Receiver` interface ID. + + + +See [ERC721ReceiverComponent::on\_erc721\_received](#ERC721ReceiverComponent-on_erc721_received). + + +#### [](#internal_functions_2)Internal functions [!toc] [#internal_functions_2] + + +Registers the `IERC721Receiver` interface ID as supported through introspection. + + +## Extensions + +### `ERC721EnumerableComponent` [toc] [#ERC721EnumerableComponent] + + +```rust +use openzeppelin_token::erc721::extensions::ERC721EnumerableComponent; +``` + +Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract as well as all token ids owned by each account. This extension allows contracts to publish their entire list of NFTs and make them discoverable. + +Implementing [ERC721Component](#ERC721Component) is a requirement for this component to be implemented. + +To properly track token ids, this extension requires that the [ERC721EnumerableComponent::before\_update](#ERC721EnumerableComponent-before_update) function is called before every transfer, mint, or burn operation. For this, the [ERC721HooksTrait::before\_update](#ERC721Component-before_update) hook must be used. Here's how the hook should be implemented in a contract: + +```[ +#[starknet::contract] +mod ERC721EnumerableContract { + (...) + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.erc721_enumerable.before_update(to, token_id); + } + } +} +``` + +Embeddable Implementations + +#### ERC721EnumerableImpl [!toc] [#ERC721EnumerableComponent-Embeddable-Impls-ERC721EnumerableImpl] + +- [`total_supply(self)`](#ERC721EnumerableComponent-total_supply) +- [`token_by_index(self, index)`](#ERC721EnumerableComponent-token_by_index) +- [`token_of_owner_by_index(self, address, index)`](#ERC721EnumerableComponent-token_of_owner_by_index) + +Internal functions + +#### InternalImpl [!toc] [#ERC721EnumerableComponent-InternalImpl] + +- [`initializer(self)`](#ERC721EnumerableComponent-initializer) +- [`before_update(self, to, token_id)`](#ERC721EnumerableComponent-before_update) +- [`all_tokens_of_owner(self, owner)`](#ERC721EnumerableComponent-all_tokens_of_owner) +- [`_add_token_to_owner_enumeration(self, to, token_id)`](#ERC721EnumerableComponent-_add_token_to_owner_enumeration) +- [`_add_token_to_all_tokens_enumeration(self, token_id)`](#ERC721EnumerableComponent-_add_token_to_all_tokens_enumeration) +- [`_remove_token_from_owner_enumeration(self, from, token_id)`](#ERC721EnumerableComponent-_remove_token_from_owner_enumeration) +- [`_remove_token_from_all_tokens_enumeration(self, token_id)`](#ERC721EnumerableComponent-_remove_token_from_all_tokens_enumeration) + +#### [](#ERC721EnumerableComponent-Embeddable-functions)Embeddable functions [!toc] [#ERC721EnumerableComponent-Embeddable-functions] + + +Returns the current amount of votes that `account` has. + + + +See [IERC721Enumerable::token\_by\_index](#IERC721Enumerable-token_by_index). + +Requirements: + +- `index` is less than the total token supply. + + + +See [IERC721Enumerable::token\_of\_owner\_by\_index](#IERC721Enumerable-token_of_owner_by_index). + +Requirements: + +- `index` is less than `owner`'s token balance. +- `owner` is not the zero address. + + +#### [](#ERC721EnumerableComponent-Internal-functions)Internal functions [!toc] [#ERC721EnumerableComponent-Internal-functions] + + +Registers the `IERC721Enumerable` interface ID as supported through introspection. + + + +Updates the ownership and token-tracking data structures. + +When a token is minted (or burned), `token_id` is added to (or removed from) the token-tracking structures. + +When a token is transferred, minted, or burned, the ownership-tracking data structures reflect the change in ownership of `token_id`. + +This must be added to the implementing contract's [ERC721HooksTrait::before\_update](#ERC721Component-before_update) hook. + + + +Returns a list of all token ids owned by the specified `owner`. This function provides a more efficient alternative to calling `ERC721::balance_of` and iterating through tokens with `ERC721Enumerable::token_of_owner_by_index`. + +Requirements: + +- `owner` is not the zero address. + + + +Adds token to this extension's ownership-tracking data structures. + + + +Adds token to this extension's token-tracking data structures. + + + +Removes a token from this extension's ownership-tracking data structures. + +This has 0(1) time complexity but alters the indexed order of owned tokens by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. + + + +Removes `token_id` from this extension's token-tracking data structures. + +This has 0(1) time complexity but alters the indexed order by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. + + +## Presets + +### `ERC721Upgradeable` [toc] [#ERC721Upgradeable] + + +```rust +use openzeppelin_presets::ERC721Upgradeable; +``` + +Upgradeable ERC721 contract leveraging [ERC721Component](#ERC721Component). + +[Sierra class hash](../presets) + +```text +{{ERC721UpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, name, symbol, recipient, token_ids, base_uri, owner)`](#ERC721Upgradeable-constructor) + +Embedded Implementations + +ERC721MixinImpl + +- [`ERC721MixinImpl`](#ERC721Component-Embeddable-Mixin-Impl) + +OwnableMixinImpl + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#ERC721Upgradeable-upgrade) + +#### [](#ERC721Upgradeable-constructor-section)Constructor [!toc] [#ERC721Upgradeable-constructor-section] + + +Sets the `name` and `symbol`. Mints `token_ids` tokens to `recipient` and sets the `base_uri`. Assigns `owner` as the contract owner with permissions to upgrade. + + +#### [](#ERC721Upgradeable-external-functions)External functions [!toc] [#ERC721Upgradeable-external-functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/2.x/api/finance.mdx b/docs/content/contracts-cairo/2.x/api/finance.mdx new file mode 100644 index 00000000..a41e0c64 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/finance.mdx @@ -0,0 +1,328 @@ +--- +title: Finance +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate includes primitives for financial systems. + +## Interfaces + +### `IVesting` [toc] [#IVesting] + + + +```rust +use openzeppelin_finance::vesting::interface::IVesting; +``` + +Common interface for contracts implementing the vesting functionality. + +Functions + +- [`start()`](#IVesting-start) +- [`cliff()`](#IVesting-cliff) +- [`duration()`](#IVesting-duration) +- [`end()`](#IVesting-end) +- [`released(token)`](#IVesting-released) +- [`releasable(token)`](#IVesting-releasable) +- [`vested_amount(token, timestamp)`](#IVesting-vested_amount) +- [`release(token)`](#IVesting-release) + +Events + +- [`AmountReleased(token, amount)`](#IVesting-AmountReleased) + +#### Functions [!toc] [#IVesting-Functions] + + +Returns the timestamp marking the beginning of the vesting period. + + + +Returns the timestamp marking the end of the cliff period. + + + +Returns the total duration of the vesting period. + + + +Returns the timestamp marking the end of the vesting period. + + + +Returns the already released amount for a given `token`. + + + +Returns the amount of a given `token` that can be released at the time of the call. + + + +Returns the total vested amount of a specified `token` at a given `timestamp`. + + + +Releases the amount of a given `token` that has already vested and returns that amount. + +May emit an [AmountReleased](#IVesting-AmountReleased) event. + + +#### Events [!toc] [#IVesting-Events] + + +Emitted when vested tokens are released to the beneficiary. + + +## [](#vesting)Vesting + +### `VestingComponent` [toc] [#VestingComponent] + + + +```rust +use openzeppelin_finance::vesting::VestingComponent; +``` + +Vesting component implementing the [`IVesting`](#IVesting) interface. + +Vesting Schedule Trait Implementations + +#### functions [!toc] [#VestingComponent-Vesting-Schedule-functions] + +- [`calculate_vested_amount(self, token, total_allocation, timestamp, start, duration, cliff)`](#VestingComponent-calculate_vested_amount) + +Embeddable Implementations + +#### VestingImpl [!toc] [#VestingComponent-Embeddable-Impls-VestingImpl] + +- [`start(self)`](#VestingComponent-start) +- [`cliff(self)`](#VestingComponent-cliff) +- [`duration(self)`](#VestingComponent-duration) +- [`end(self)`](#VestingComponent-end) +- [`released(self, token)`](#VestingComponent-released) +- [`releasable(self, token)`](#VestingComponent-releasable) +- [`vested_amount(self, token, timestamp)`](#VestingComponent-vested_amount) +- [`release(self, token)`](#VestingComponent-release) + +Internal implementations + +#### InternalImpl [!toc] [#VestingComponent-InternalImpl] + +- [`initializer(self, start, duration, cliff_duration)`](#VestingComponent-initializer) +- [`resolve_vested_amount(self, token, timestamp)`](#VestingComponent-resolve_vested_amount) + +A trait that defines the logic for calculating the vested amount based on a given timestamp. + +You can read more about the trait's purpose and how to use it [here](../finance#vesting-schedule). + + +Calculates and returns the vested amount at a given `timestamp` based on the core vesting parameters. + + +#### Functions [!toc] [#VestingComponent-Functions] + + +Returns the timestamp marking the beginning of the vesting period. + + + +Returns the timestamp marking the end of the cliff period. + + + +Returns the total duration of the vesting period. + + + +Returns the timestamp marking the end of the vesting period. + + + +Returns the already released amount for a given `token`. + + + +Returns the amount of a given `token` that can be released at the time of the call. + + + +Returns the total vested amount of a specified `token` at a given `timestamp`. + + + +Releases the amount of a given `token` that has already vested and returns that amount. + +If the releasable amount is zero, this function won't emit the event or attempt to transfer the tokens. + +Requirements: + +- `transfer` call to the `token` must return `true` indicating a successful transfer. + +May emit an [AmountReleased](#IVesting-AmountReleased) event. + + +#### Internal functions [!toc] [#VestingComponent-Internal-Functions] + + +Initializes the component by setting the vesting `start`, `duration` and `cliff_duration`. To prevent reinitialization, this should only be used inside of a contract's constructor. + +Requirements: + +- `cliff_duration` must be less than or equal to `duration`. + + + +Returns the vested amount that's calculated using the [VestingSchedule](#VestingComponent-Vesting-Schedule) trait implementation. + + +### `LinearVestingSchedule` [toc] [#LinearVestingSchedule] + + + +```rust +use openzeppelin_finance::vesting::LinearVestingSchedule; +``` + +Defines the logic for calculating the vested amount, incorporating a cliff period. It returns 0 before the cliff ends. After the cliff period, the vested amount returned is directly proportional to the time passed since the start of the vesting schedule. + +## [](#presets)Presets + +### `VestingWallet` [toc] [#VestingWallet] + + + +```rust +use openzeppelin::presets::VestingWallet; +``` + +A non-upgradable contract leveraging [VestingComponent](#VestingComponent) and [OwnableComponent](./access#OwnableComponent). + +The contract is intentionally designed to be non-upgradable to ensure that neither the vesting initiator nor the vesting beneficiary can modify the vesting schedule without the consent of the other party. + +[Sierra class hash](../presets) + +```text +{{VestingWalletClassHash}} +``` + +Constructor + +- [`constructor(self, beneficiary, start, duration, cliff_duration)`](#VestingWallet-constructor) + +Embedded Implementations + +VestingComponent + +- [`VestingImpl`](#VestingComponent-Embeddable-Impls-VestingImpl) + +OwnableComponent + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +#### Constructor [!toc] [#VestingWallet-constructor-section] + + +Initializes the vesting component by setting the vesting `start`, `duration` and `cliff_duration`. Assigns `beneficiary` as the contract owner and the vesting beneficiary. + +Requirements: + +- `cliff_duration` must be less than or equal to `duration`. + diff --git a/docs/content/contracts-cairo/2.x/api/governance.mdx b/docs/content/contracts-cairo/2.x/api/governance.mdx new file mode 100644 index 00000000..f0bd318f --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/governance.mdx @@ -0,0 +1,4059 @@ +--- +title: Governance +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate includes primitives for on-chain governance. + +## Interfaces + +### `IGovernor` [toc] [#IGovernor] + + + +```rust +use openzeppelin_governance::governor::interface::IGovernor; +``` + +Interface of a governor contract. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x1100a1f8546595b5bd75a6cd8fcc5b015370655e66f275963321c5cd0357ac9 +``` + +Functions + +- [`name()`](#IGovernor-name) +- [`version()`](#IGovernor-version) +- [`COUNTING_MODE()`](#IGovernor-COUNTING_MODE) +- [`hash_proposal(calls, description_hash)`](#IGovernor-hash_proposal) +- [`state(proposal_id)`](#IGovernor-state) +- [`proposal_threshold()`](#IGovernor-proposal_threshold) +- [`proposal_snapshot(proposal_id)`](#IGovernor-proposal_snapshot) +- [`proposal_deadline(proposal_id)`](#IGovernor-proposal_deadline) +- [`proposal_proposer(proposal_id)`](#IGovernor-proposal_proposer) +- [`proposal_eta(proposal_id)`](#IGovernor-proposal_eta) +- [`proposal_needs_queuing(proposal_id)`](#IGovernor-proposal_needs_queuing) +- [`voting_delay()`](#IGovernor-voting_delay) +- [`voting_period()`](#IGovernor-voting_period) +- [`quorum(timepoint)`](#IGovernor-quorum) +- [`get_votes(account, timepoint)`](#IGovernor-get_votes) +- [`get_votes_with_params(account, timepoint, params)`](#IGovernor-get_votes_with_params) +- [`has_voted(proposal_id, account)`](#IGovernor-has_voted) +- [`propose(calls, description)`](#IGovernor-propose) +- [`queue(calls, description_hash)`](#IGovernor-queue) +- [`execute(calls, description_hash)`](#IGovernor-execute) +- [`cancel(proposal_id, description_hash)`](#IGovernor-cancel) +- [`cast_vote(proposal_id, support)`](#IGovernor-cast_vote) +- [`cast_vote_with_reason(proposal_id, support, reason)`](#IGovernor-cast_vote_with_reason) +- [`cast_vote_with_reason_and_params(proposal_id, support, reason, params)`](#IGovernor-cast_vote_with_reason_and_params) +- [`cast_vote_by_sig(proposal_id, support, reason, signature)`](#IGovernor-cast_vote_by_sig) +- [`cast_vote_with_reason_and_params_by_sig(proposal_id, support, reason, params, signature)`](#IGovernor-cast_vote_with_reason_and_params_by_sig) +- [`nonces(voter)`](#IGovernor-nonces) +- [`relay(call)`](#IGovernor-relay) + +Events + +- [`ProposalCreated(proposal_id, proposer, calls, signatures, vote_start, vote_end, description)`](#IGovernor-ProposalCreated) +- [`ProposalQueued(proposal_id, eta_seconds)`](#IGovernor-ProposalQueued) +- [`ProposalExecuted(proposal_id)`](#IGovernor-ProposalExecuted) +- [`ProposalCanceled(proposal_id)`](#IGovernor-ProposalCanceled) +- [`VoteCast(voter, proposal_id, support, weight, reason)`](#IGovernor-VoteCast) +- [`VoteCastWithParams(voter, proposal_id, support, weight, reason, params)`](#IGovernor-VoteCastWithParams) + +#### Functions [!toc] [#IGovernor-Functions] + + +Name of the governor instance (used in building the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +Version of the governor instance (used in building [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +A description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + + +The string can be decoded by the standard [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) JavaScript class. + + + + +Hashing function used to (re)build the proposal id from the proposal details. + + + +Returns the state of a proposal, given its id. + + + +The number of votes required in order for a voter to become a proposer. + + + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the following block. + + + +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is possible to cast a vote during this block. + + + +The account that created a proposal. + + + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the executor's clock which may be different. In most cases this will be a timestamp. + + + +Whether a proposal needs to be queued before execution. This indicates if the proposal needs to go through a timelock. + + + +Delay between when a proposal is created and when the vote starts. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + + + +Delay between when a vote starts and when it ends. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +The `voting_delay` can delay the start of the vote. This must be considered when setting the voting duration compared to the voting delay. + +This value is stored when the proposal is submitted so that possible changes to the value do not affect proposals that have already been submitted. + + + +Minimum number of votes required for a proposal to be successful. + +The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows the quorum to scale depending on values such as the total supply of a token at this timepoint. + + + +Returns the voting power of an `account` at a specific `timepoint`. + +This can be implemented in a number of ways, for example by reading the delegated balance from one (or multiple) `ERC20Votes` tokens. + + + +Returns the voting power of an `account` at a specific `timepoint`, given additional encoded parameters. + + + +Returns whether an `account` has cast a vote on a proposal. + + + +Creates a new proposal. Vote starts after a delay specified by `voting_delay` and lasts for a duration specified by `voting_period`. + +The state of the Governor and targets may change between the proposal creation and its execution. This may be the result of third party actions on the targeted contracts, or other governor proposals. For example, the balance of this contract could be updated or its access control permissions may be modified, possibly compromising the proposal's ability to execute successfully (e.g. the governor doesn't have enough value to cover a proposal with multiple transfers). + +Returns the id of the proposal. + + + +Queue a proposal. Some governors require this step to be performed before execution can happen. If queuing is not necessary, this function may revert. + +Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + +Returns the id of the proposal. + + + +Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the deadline to be reached. Depending on the governor it might also be required that the proposal was queued and that some delay passed. + +Some modules can modify the requirements for execution, for example by adding an additional timelock (See `timelock_controller`). + +Returns the id of the proposal. + + + +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. before the vote starts. + +Returns the id of the proposal. + + + +Cast a vote on a proposal. + +Returns the weight of the vote. + + + +Cast a vote on a proposal with a `reason`. + +Returns the weight of the vote. + + + +Cast a vote on a proposal with a reason and additional encoded parameters. + +Returns the weight of the vote. + + + +Cast a vote on a proposal using the voter's signature. + +Returns the weight of the vote. + + + +Cast a vote on a proposal with a reason and additional encoded parameters using the `voter`'s signature. + +Returns the weight of the vote. + + + +Returns the next unused nonce for an address. + + + +Relays a transaction or function call to an arbitrary target. + +In cases where the governance executor is some contract other than the governor itself, like when using a timelock, this function can be invoked in a governance proposal to recover tokens that were sent to the governor contract by mistake. + +If the executor is simply the governor itself, use of `relay` is redundant. + + +#### Events [!toc] [#IGovernor-Events] + + +Emitted when a proposal is created. + + + +Emitted when a proposal is queued. + + + +Emitted when a proposal is executed. + + + +Emitted when a proposal is canceled. + + + +Emitted when a vote is cast. + + + +Emitted when a vote is cast with params. + + +### `IMultisig` [toc] [#IMultisig] + + + +```rust +use openzeppelin_governance::multisig::interface::IMultisig; +``` + +Interface of a multisig contract. + +Functions + +- [`get_quorum()`](#IMultisig-get_quorum) +- [`is_signer(signer)`](#IMultisig-is_signer) +- [`get_signers()`](#IMultisig-get_signers) +- [`is_confirmed(id)`](#IMultisig-is_confirmed) +- [`is_confirmed_by(id, signer)`](#IMultisig-is_confirmed_by) +- [`is_executed(id)`](#IMultisig-is_executed) +- [`get_submitted_block(id)`](#IMultisig-get_submitted_block) +- [`get_transaction_state(id)`](#IMultisig-get_transaction_state) +- [`get_transaction_confirmations(id)`](#IMultisig-get_transaction_confirmations) +- [`hash_transaction(to, selector, calldata, salt)`](#IMultisig-hash_transaction) +- [`hash_transaction_batch(calls, salt)`](#IMultisig-hash_transaction_batch) +- [`add_signers(new_quorum, signers_to_add)`](#IMultisig-add_signers) +- [`remove_signers(new_quorum, signers_to_remove)`](#IMultisig-remove_signers) +- [`replace_signer(signer_to_remove, signer_to_add)`](#IMultisig-replace_signer) +- [`change_quorum(new_quorum)`](#IMultisig-change_quorum) +- [`submit_transaction(to, selector, calldata, salt)`](#IMultisig-submit_transaction) +- [`submit_transaction_batch(calls, salt)`](#IMultisig-submit_transaction_batch) +- [`confirm_transaction(id)`](#IMultisig-confirm_transaction) +- [`revoke_confirmation(id)`](#IMultisig-revoke_confirmation) +- [`execute_transaction(to, selector, calldata, salt)`](#IMultisig-execute_transaction) +- [`execute_transaction_batch(calls, salt)`](#IMultisig-execute_transaction_batch) + +Events + +- [`SignerAdded(signer)`](#IMultisig-SignerAdded) +- [`SignerRemoved(signer)`](#IMultisig-SignerRemoved) +- [`QuorumUpdated(old_quorum, new_quorum)`](#IMultisig-QuorumUpdated) +- [`TransactionSubmitted(id, signer)`](#IMultisig-TransactionSubmitted) +- [`TransactionConfirmed(id, signer)`](#IMultisig-TransactionConfirmed) +- [`ConfirmationRevoked(id, signer)`](#IMultisig-ConfirmationRevoked) +- [`TransactionExecuted(id)`](#IMultisig-TransactionExecuted) +- [`CallSalt(id, salt)`](#IMultisig-CallSalt) + +#### Functions [!toc] [#IMultisig-Functions] + + +Returns the current quorum value. The quorum is the minimum number of confirmations required to approve a transaction. + + + +Returns whether the given `signer` is registered. Only registered signers can submit, confirm, or execute transactions. + + + +Returns the list of all current signers. + + + +Returns whether the transaction with the given `id` has been confirmed. + + + +Returns whether the transaction with the given `id` has been confirmed by the specified `signer`. + + + +Returns whether the transaction with the given `id` has been executed. + + + +Returns the block number when the transaction with the given `id` was submitted. + + + +Returns the current state of the transaction with the given `id`. + + + +Returns the number of confirmations from registered signers for the transaction with the specified `id`. + + + +Returns the computed identifier of a transaction containing a single call. + + + +Returns the computed identifier of a transaction containing a batch of calls. + + + +Adds new signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after addition. + +Emits a [SignerAdded](#IMultisig-SignerAdded) event for each signer added. + +Emits a [QuorumUpdated](#IMultisig-QuorumUpdated) event if the quorum changes. + + + +Removes signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after removal. + +Emits a [SignerRemoved](#IMultisig-SignerRemoved) event for each signer removed. + +Emits a [QuorumUpdated](#IMultisig-QuorumUpdated) event if the quorum changes. + + + +Replaces an existing signer with a new signer. + +Requirements: + +- The caller must be the contract itself. +- `signer_to_remove` must be an existing signer. +- `signer_to_add` must not be an existing signer. + +Emits a [SignerRemoved](#IMultisig-SignerRemoved) event for the removed signer. + +Emits a [SignerAdded](#IMultisig-SignerAdded) event for the new signer. + + + +Updates the quorum value to `new_quorum` if it differs from the current quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be non-zero. +- `new_quorum` must be less than or equal to the total number of signers. + +Emits a [QuorumUpdated](#IMultisig-QuorumUpdated) event if the quorum changes. + + + +Submits a new transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#IMultisig-TransactionSubmitted) event. + +Emits a [CallSalt](#IMultisig-CallSalt) event if `salt` is not zero. + + + + +Submits a new batch transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#IMultisig-TransactionSubmitted) event. + +Emits a [CallSalt](#IMultisig-CallSalt) event if `salt` is not zero. + + + +Confirms a transaction with the given `id`. + +Requirements: + +- The caller must be a registered signer. +- The transaction must exist and not be executed. +- The caller must not have already confirmed the transaction. + +Emits a [TransactionConfirmed](#IMultisig-TransactionConfirmed) event. + + + +Revokes a previous confirmation for a transaction with the given `id`. + +Requirements: + +- The transaction must exist and not be executed. +- The caller must have previously confirmed the transaction. + +Emits a [ConfirmationRevoked](#IMultisig-ConfirmationRevoked) event. + + + +Executes a confirmed transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#IMultisig-TransactionExecuted) event. + + + +Executes a confirmed batch transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#IMultisig-TransactionExecuted) event. + + +#### Events [!toc] [#IMultisig-Events] + + +Emitted when a new `signer` is added. + + + +Emitted when a `signer` is removed. + + + +Emitted when the `quorum` value is updated. + + + +Emitted when a new transaction is submitted by a `signer`. + + + +Emitted when a transaction is confirmed by a `signer`. + + + +Emitted when a `signer` revokes his confirmation. + + + +Emitted when a transaction is executed. + + + +Emitted when a new transaction is submitted with non-zero salt. + + +### `ITimelock` [toc] [#ITimelock] + + + +```rust +use openzeppelin_governance::timelock::interface::ITimelock; +``` + +Interface of a timelock contract. + +Functions + +- [`is_operation(id)`](#ITimelock-is_operation) +- [`is_operation_pending(id)`](#ITimelock-is_operation_pending) +- [`is_operation_ready(id)`](#ITimelock-is_operation_ready) +- [`is_operation_done(id)`](#ITimelock-is_operation_done) +- [`get_timestamp(id)`](#ITimelock-get_timestamp) +- [`get_operation_state(id)`](#ITimelock-get_operation_state) +- [`get_min_delay()`](#ITimelock-get_min_delay) +- [`hash_operation(call, predecessor, salt)`](#ITimelock-hash_operation) +- [`hash_operation_batch(calls, predecessor, salt)`](#ITimelock-hash_operation_batch) +- [`schedule(call, predecessor, salt, delay)`](#ITimelock-schedule) +- [`schedule_batch(calls, predecessor, salt, delay)`](#ITimelock-schedule_batch) +- [`cancel(id)`](#ITimelock-cancel) +- [`execute(call, predecessor, salt)`](#ITimelock-execute) +- [`execute_batch(calls, predecessor, salt)`](#ITimelock-execute_batch) +- [`update_delay(new_delay)`](#ITimelock-update_delay) + +Events + +- [`CallScheduled(id, index, call, predecessor, delay)`](#ITimelock-CallScheduled) +- [`CallExecuted(id, index, call)`](#ITimelock-CallExecuted) +- [`CallSalt(id, salt)`](#ITimelock-CallSalt) +- [`CallCancelled(id)`](#ITimelock-CallCancelled) +- [`MinDelayChanged(old_duration, new_duration)`](#ITimelock-MinDelayChanged) + +#### Functions [!toc] [#ITimelock-Functions] + + +Returns whether `id` corresponds to a registered operation. This includes the OperationStates: `Waiting`, `Ready`, and `Done`. + + + +Returns whether the `id` OperationState is pending or not. Note that a pending operation may be either `Waiting` or `Ready`. + + + +Returns whether the `id` OperationState is `Ready` or not. + + + +Returns whether the `id` OperationState is `Done` or not. + + + +Returns the timestamp at which `id` becomes `Ready`. + +`0` means the OperationState is `Unset` and `1` means the OperationState is `Done`. + + + + +Returns the current state of the operation with the given `id`. + +The possible states are: + +- `Unset`: the operation has not been scheduled or has been canceled. +- `Waiting`: the operation has been scheduled and is pending the scheduled delay. +- `Ready`: the timer has expired, and the operation is eligible for execution. +- `Done`: the operation has been executed. + + + +Returns the minimum delay in seconds for an operation to become valid. This value can be changed by executing an operation that calls `update_delay`. + + + +Returns the identifier of an operation containing a single transaction. + + + +Returns the identifier of an operation containing a batch of transactions. + + + +Schedule an operation containing a single transaction. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. + +Emits [CallScheduled](#ITimelock-CallScheduled) event. Emits [CallSalt](#ITimelock-CallSalt) event if `salt` is not zero. + + + +Schedule an operation containing a batch of transactions. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. + +Emits one [CallScheduled](#ITimelock-CallScheduled) event for each transaction in the batch. Emits [CallSalt](#ITimelock-CallSalt) event if `salt` is not zero. + + + +Cancels an operation. A canceled operation returns to `Unset` OperationState. + +Requirements: + +- The caller must have the `CANCELLER_ROLE` role. +- `id` must be a pending operation. + +Emits a [CallCancelled](#ITimelock-CallCancelled) event. + + + +Execute a (Ready) operation containing a single Call. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#ITimelock-CallExecuted) event. + +This function can reenter, but it doesn't pose a risk because [`_after_call(self: @ContractState, id: felt252)` internal](#TimelockControllerComponent-_after_call) checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Execute a (Ready) operation containing a batch of Calls. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#ITimelock-CallExecuted) event for each Call. + +This function can reenter, but it doesn't pose a risk because `_after_call` checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Changes the minimum timelock duration for future operations. + +Requirements: + +- The caller must be the timelock itself. This can only be achieved by scheduling and later executing an operation where the timelock is the target and the data is the serialized call to this function. + +Emits a [MinDelayChanged](#ITimelock-MinDelayChanged) event. + + +#### Events [!toc] [#ITimelock-Events] + + +Emitted when `call` is scheduled as part of operation `id`. + + + +Emitted when `call` is performed as part of operation `id`. + + + +Emitted when a new proposal is scheduled with non-zero salt. + + + +Emitted when operation `id` is cancelled. + + + +Emitted when the minimum delay for future operations is modified. + + +### `IVotes` [toc] [#IVotes] + + + +```rust +use openzeppelin_governance::votes::interface::IVotes; +``` + +Common interface for Votes-enabled contracts. + +Functions + +- [`get_votes(account)`](#IVotes-get_votes) +- [`get_past_votes(account, timepoint)`](#IVotes-get_past_votes) +- [`get_past_total_supply(timepoint)`](#IVotes-get_past_total_supply) +- [`delegates(account)`](#IVotes-delegates) +- [`delegate(delegatee)`](#IVotes-delegate) +- [`delegate_by_sig(delegator, delegatee, nonce, expiry, signature)`](#IVotes-delegate_by_sig) +- [`clock()`](#IVotes-clock) +- [`CLOCK_MODE()`](#IVotes-CLOCK_MODE) + +#### Functions [!toc] [#IVotes-Functions] + + +Returns the current amount of votes that `account` has. + + + +Returns the amount of votes that `account` had at a specific moment in the past. + + + +Returns the total supply of votes available at a specific moment in the past. + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. Votes that have not been delegated are still part of total supply, even though they would not participate in a vote. + + + +Returns the delegate that `account` has chosen. + + + +Delegates votes from the sender to `delegatee`. + + + +Delegates votes from `delegator` to `delegatee` through a [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message signature validation. + + + +Returns the current timepoint determined by the contract's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the contract is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + +## [](#governor)Governor + +This modular system of Governor components allows the deployment of easily customizable on-chain voting protocols. + +For a walkthrough of how to implement a Governor, check the [Governor](../governance/governor) page. + +### `GovernorComponent` [toc] [#GovernorComponent] + + + +```rust +use openzeppelin_governance::governor::GovernorComponent; +``` + +Core of the governance system. + +The extension traits presented below are what make the GovernorComponent a modular and configurable system. The embeddable and internal implementations depends on these trait. They can be implemented locally in the contract, or through the provided library [component extensions](#governor_extensions). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +Extensions traits + +#### GovernorSettingsTrait [!toc] [#GovernorComponent-GovernorSettingsTrait] + +- [`voting_delay(self)`](#GovernorComponent-GovernorSettingsTrait-voting_delay) +- [`voting_period(self)`](#GovernorComponent-GovernorSettingsTrait-voting_period) +- [`proposal_threshold(self)`](#GovernorComponent-GovernorSettingsTrait-proposal_threshold) + +#### GovernorQuorumTrait [!toc] [#GovernorComponent-GovernorQuorumTrait] + +- [`quorum(self, timepoint)`](#GovernorComponent-GovernorQuorumTrait-quorum) + +#### GovernorCountingTrait [!toc] [#GovernorComponent-GovernorCountingTrait] + +- [`counting_mode(self)`](#GovernorComponent-GovernorCountingTrait-counting_mode) +- [`count_vote(self, proposal_id, account, support, total_weight, params)`](#GovernorComponent-GovernorCountingTrait-count_vote) +- [`has_voted(self, proposal_id, account)`](#GovernorComponent-GovernorCountingTrait-has_voted) +- [`quorum_reached(self, proposal_id)`](#GovernorComponent-GovernorCountingTrait-quorum_reached) +- [`vote_succeeded(self, proposal_id)`](#GovernorComponent-GovernorCountingTrait-vote_succeeded) + +#### GovernorVotesTrait [!toc] [#GovernorComponent-GovernorVotesTrait] + +- [`clock(self)`](#GovernorComponent-GovernorVotesTrait-clock) +- [`CLOCK_MODE(self)`](#GovernorComponent-GovernorVotesTrait-CLOCK_MODE) +- [`get_votes(self, account, timepoint, params)`](#GovernorComponent-GovernorVotesTrait-get_votes) + +#### GovernorExecutionTrait [!toc] [#GovernorComponent-GovernorExecutionTrait] + +- [`state(self, proposal_id)`](#GovernorComponent-GovernorExecutionTrait-state) +- [`executor(self)`](#GovernorComponent-GovernorExecutionTrait-executor) +- [`execute_operations(self, proposal_id, calls, description_hash)`](#GovernorComponent-GovernorExecutionTrait-execute_operations) +- [`queue_operations(self, proposal_id, calls, description_hash)`](#GovernorComponent-GovernorExecutionTrait-queue_operations) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorComponent-GovernorExecutionTrait-proposal_needs_queuing) +- [`cancel_operations(self, proposal_id, description_hash)`](#GovernorComponent-GovernorExecutionTrait-cancel_operations) + +Embeddable Implementations + +#### GovernorImpl [!toc] [#GovernorComponent-GovernorImpl] + +- [`name(self)`](#GovernorComponent-name) +- [`version(self)`](#GovernorComponent-version) +- [`COUNTING_MODE(self)`](#GovernorComponent-COUNTING_MODE) +- [`hash_proposal(self, calls, description_hash)`](#GovernorComponent-hash_proposal) +- [`state(self, proposal_id)`](#GovernorComponent-state) +- [`proposal_threshold(self)`](#GovernorComponent-proposal_threshold) +- [`proposal_snapshot(self, proposal_id)`](#GovernorComponent-proposal_snapshot) +- [`proposal_deadline(self, proposal_id)`](#GovernorComponent-proposal_deadline) +- [`proposal_proposer(self, proposal_id)`](#GovernorComponent-proposal_proposer) +- [`proposal_eta(self, proposal_id)`](#GovernorComponent-proposal_eta) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorComponent-proposal_needs_queuing) +- [`voting_delay(self)`](#GovernorComponent-voting_delay) +- [`voting_period(self)`](#GovernorComponent-voting_period) +- [`quorum(self, timepoint)`](#GovernorComponent-quorum) +- [`get_votes(self, account, timepoint)`](#GovernorComponent-get_votes) +- [`get_votes_with_params(self, account, timepoint, params)`](#GovernorComponent-get_votes_with_params) +- [`has_voted(self, proposal_id, account)`](#GovernorComponent-has_voted) +- [`propose(self, calls, description)`](#GovernorComponent-propose) +- [`queue(self, calls, description_hash)`](#GovernorComponent-queue) +- [`execute(self, calls, description_hash)`](#GovernorComponent-execute) +- [`cancel(self, proposal_id, description_hash)`](#GovernorComponent-cancel) +- [`cast_vote(self, proposal_id, support)`](#GovernorComponent-cast_vote) +- [`cast_vote_with_reason(self, proposal_id, support, reason)`](#GovernorComponent-cast_vote_with_reason) +- [`cast_vote_with_reason_and_params(self, proposal_id, support, reason, params)`](#GovernorComponent-cast_vote_with_reason_and_params) +- [`cast_vote_by_sig(self, proposal_id, support, reason, signature)`](#GovernorComponent-cast_vote_by_sig) +- [`cast_vote_with_reason_and_params_by_sig(self, proposal_id, support, reason, params, signature)`](#GovernorComponent-cast_vote_with_reason_and_params_by_sig) +- [`nonces(self, voter)`](#GovernorComponent-nonces) +- [`relay(self, call)`](#GovernorComponent-relay) + +Internal Implementations + +#### InternalImpl [!toc] [#GovernorComponent-InternalImpl] + +- [`initializer(self)`](#GovernorComponent-initializer) +- [`get_proposal(self, proposal_id)`](#GovernorComponent-get_proposal) +- [`is_valid_description_for_proposer(self, proposer, description)`](#GovernorComponent-is_valid_description_for_proposer) +- [`_hash_proposal(self, calls, description_hash)`](#GovernorComponent-_hash_proposal) +- [`_proposal_snapshot(self, proposal_id)`](#GovernorComponent-_proposal_snapshot) +- [`_proposal_deadline(self, proposal_id)`](#GovernorComponent-_proposal_deadline) +- [`_proposal_proposer(self, proposal_id)`](#GovernorComponent-_proposal_proposer) +- [`_proposal_eta(self, proposal_id)`](#GovernorComponent-_proposal_eta) + +#### InternalExtendedImpl [!toc] [#GovernorComponent-InternalExtendedImpl] + +- [`assert_only_governance(self)`](#GovernorComponent-assert_only_governance) +- [`validate_state(self, proposal_id, allowed_states)`](#GovernorComponent-validate_state) +- [`use_nonce(self, voter)`](#GovernorComponent-use_nonce) +- [`_get_votes(self, account, timepoint, params)`](#GovernorComponent-_get_votes) +- [`_proposal_threshold(self)`](#GovernorComponent-_proposal_threshold) +- [`_state(self, proposal_id)`](#GovernorComponent-_state) +- [`_propose(self, calls, description, proposer)`](#GovernorComponent-_propose) +- [`_cancel(self, proposal_id, description_hash)`](#GovernorComponent-_cancel) +- [`_count_vote(self, proposal_id, account, support, total_weight, params)`](#GovernorComponent-_count_vote) +- [`_cast_vote(self, proposal_id, voter, support, reason, params)`](#GovernorComponent-_cast_vote) + +Events + +- [`ProposalCreated(proposal_id, proposer, calls, signatures, vote_start, vote_end, description)`](#GovernorComponent-ProposalCreated) +- [`ProposalQueued(proposal_id)`](#GovernorComponent-ProposalQueued) +- [`ProposalExecuted(proposal_id)`](#GovernorComponent-ProposalExecuted) +- [`ProposalCanceled(proposal_id)`](#GovernorComponent-ProposalCanceled) +- [`VoteCast(voter, proposal_id, support, weight, reason)`](#GovernorComponent-VoteCast) +- [`VoteCastWithParams(voter, proposal_id, support, weight, reason, params)`](#GovernorComponent-VoteCastWithParams) + +#### Extensions traits functions [!toc] [#GovernorComponent-Extensions-Traits] + + +Must return the delay, in number of timepoints, between when the proposal is created and when the vote starts. This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + + + +Must return the delay, in number of timepoints, between the vote start and vote end. + + + +Must return the minimum number of votes that an account must have to create a proposal. + + + +Must return the minimum number of votes required for a proposal to succeed. + + + +Must return a description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs to show correct vote options and interpret the results. See [COUNTING\_MODE](#GovernorComponent-COUNTING_MODE) for more details. + + + +Must register a vote for `proposal_id` by `account` with a given `support`, voting `weight` and voting `params`. + +Support is generic and can represent various things depending on the voting system used. + + + +Must return whether an account has cast a vote on a proposal. + + + +Must return whether the minimum quorum has been reached for a proposal. + + + +Must return whether a proposal has succeeded or not. + + + +Returns the current timepoint determined by the governor's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the governor is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + + +Must return the voting power of an account at a specific timepoint with the given parameters. + + + +Must return the state of a proposal at the current time. + +The state can be either: + +- `Pending`: The proposal does not exist yet. +- `Active`: The proposal is active. +- `Canceled`: The proposal has been canceled. +- `Defeated`: The proposal has been defeated. +- `Succeeded`: The proposal has succeeded. +- `Queued`: The proposal has been queued. +- `Executed`: The proposal has been executed. + + + +Must return the address through which the governor executes action. Should be used to specify whether the module execute actions through another contract such as a timelock. + +MUST be the governor itself, or an instance of TimelockController with the governor as the only proposer, canceller, and executor. + +When the executor is not the governor itself (i.e. a timelock), it can call functions that are restricted with the `assert_only_governance` guard, and also potentially execute transactions on behalf of the governor. Because of this, this module is designed to work with the TimelockController as the unique potential external executor. + + + + +Execution mechanism. Can be used to modify the way operations are executed (for example adding a vault/timelock). + + + +Queuing mechanism. Can be used to modify the way queuing is performed (for example adding a vault/timelock). + +Requirements: + +- Must return a timestamp that describes the expected ETA for execution. If the returned value is 0, the core will consider queueing did not succeed, and the public `queue` function will revert. + + + +Must return whether proposals need to be queued before execution. This usually indicates if the proposal needs to go through a timelock. + + + +Cancel mechanism. Can be used to modify the way canceling is performed (for example adding a vault/timelock). + + +#### Embeddable functions [!toc] [#GovernorComponent-Embeddable-Functions] + + +Name of the governor instance (used in building the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +Version of the governor instance (used in building [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +A description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +The string can be decoded by the standard [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) JavaScript class. + + + + +Hashing function used to (re)build the proposal id from the proposal details. + + + +Returns the state of a proposal, given its id. + + + +The number of votes required in order for a voter to become a proposer. + + + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the following block. + + + +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is possible to cast a vote during this block. + + + +The account that created a proposal. + + + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the executor's clock which may be different. In most cases this will be a timestamp. + + + +Whether a proposal needs to be queued before execution. This indicates if the proposal needs to go through a timelock. + + + +Delay between when a proposal is created and when the vote starts. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + + + +Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +The `voting_delay` can delay the start of the vote. This must be considered when setting the voting duration compared to the voting delay. + +This value is stored when the proposal is submitted so that possible changes to the value do not affect proposals that have already been submitted. + + + + +Minimum number of votes required for a proposal to be successful. + +The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows the quorum to scale depending on values such as the total supply of a token at this timepoint. + + + +Returns the voting power of an `account` at a specific `timepoint`. + +This can be implemented in a number of ways, for example by reading the delegated balance from one (or multiple) `ERC20Votes` tokens. + + + +Returns the voting power of an account at a specific timepoint, given additional encoded parameters. + + + +Returns whether an account has cast a vote on a proposal. + + + +Creates a new proposal. Voting starts after the delay specified by `voting_delay` and lasts for a duration specified by `voting_period`. Returns the id of the proposal. + +This function has opt-in frontrunning protection, described in `is_valid_description_for_proposer`. + +The state of the Governor and targets may change between the proposal creation and its execution. This may be the result of third party actions on the targeted contracts, or other governor proposals. For example, the balance of this contract could be updated or its access control permissions may be modified, possibly compromising the proposal's ability to execute successfully (e.g. the governor doesn't have enough value to cover a proposal with multiple transfers). + +Requirements: + +- The proposer must be authorized to submit the proposal. +- The proposer must have enough votes to submit the proposal if `proposal_threshold` is greater than zero. +- The proposal must not already exist. + +Emits a [ProposalCreated](#GovernorComponent-ProposalCreated) event. + + + +Queues a proposal. Some governors require this step to be performed before execution can happen. If queuing is not necessary, this function may revert. Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Succeeded` state. +- The queue operation must return a non-zero ETA. + +Emits a [ProposalQueued](#GovernorComponent-ProposalQueued) event. + + + + +Executes a successful proposal. This requires the quorum to be reached, the vote to be successful, and the deadline to be reached. Depending on the governor it might also be required that the proposal was queued and that some delay passed. + +Some modules can modify the requirements for execution, for example by adding an additional timelock (See `timelock_controller`). + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Succeeded` or `Queued` state. + +Emits a [ProposalExecuted](#GovernorComponent-ProposalExecuted) event. + + + +Cancels a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. before the vote starts. + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Pending` state. +- The caller must be the proposer of the proposal. + +Emits a [ProposalCanceled](#GovernorComponent-ProposalCanceled) event. + + + +Cast a vote. + +Requirements: + +- The proposal must be active. + +Emits a [VoteCast](#GovernorComponent-VoteCast) event. + + + +Cast a vote with a `reason`. + +Requirements: + +- The proposal must be active. + +Emits a [VoteCast](#GovernorComponent-VoteCast) event. + + + +Cast a vote with a `reason` and additional serialized `params`. + +Requirements: + +- The proposal must be active. + +Emits either: + +- [VoteCast](#GovernorComponent-VoteCast) event if no params are provided. +- [VoteCastWithParams](#GovernorComponent-VoteCastWithParams) event otherwise. + + + +Cast a vote using the `voter`'s signature. + +Requirements: + +- The proposal must be active. +- The nonce in the signed message must match the account's current nonce. +- `voter` must implement `SRC6::is_valid_signature`. +- `signature` must be valid for the message hash. + +Emits a [VoteCast](#GovernorComponent-VoteCast) event. + + + +Cast a vote with a `reason` and additional serialized `params` using the `voter`'s signature. + +Requirements: + +- The proposal must be active. +- The nonce in the signed message must match the account's current nonce. +- `voter` must implement `SRC6::is_valid_signature`. +- `signature` must be valid for the message hash. + +Emits either: + +- [VoteCast](#GovernorComponent-VoteCast) event if no params are provided. +- [VoteCastWithParams](#GovernorComponent-VoteCastWithParams) event otherwise. + + + +Returns the next unused nonce for an address. + + + +Relays a transaction or function call to an arbitrary target. + +In cases where the governance executor is some contract other than the governor itself, like when using a timelock, this function can be invoked in a governance proposal to recover tokens that were sent to the governor contract by mistake. + +If the executor is simply the governor itself, use of `relay` is redundant. + + +#### Internal functions [!toc] [#GovernorComponent-Internal-Functions] + + +Initializes the contract by registering the supported interface id. + + + +Returns the proposal object given its id. + + + +Checks if the proposer is authorized to submit a proposal with the given description. + +If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string (case insensitive), then the submission of this proposal will only be authorized to said address. + +This is used for frontrunning protection. By adding this pattern at the end of their proposal, one can ensure that no other address can submit the same proposal. An attacker would have to either remove or change that part, which would result in a different proposal id. + +In Starknet, the Sequencer ensures the order of transactions, but frontrunning can still be achieved by nodes, and potentially other actors in the future with sequencer decentralization. + +If the description does not match this pattern, it is unrestricted and anyone can submit it. This includes: + +- If the `0x???` part is not a valid hex string. +- If the `0x???` part is a valid hex string, but does not contain exactly 64 hex digits. +- If it ends with the expected suffix followed by newlines or other whitespace. +- If it ends with some other similar suffix, e.g. `#other=abc`. +- If it does not end with any such suffix. + + + +Returns the proposal id computed from the given parameters. + +The proposal id is computed as a Pedersen hash of: + +- The array of calls being proposed +- The description hash + + + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the following block. + + + +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is possible to cast a vote during this block. + + + +The account that created a proposal. + + + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the executor's clock which may be different. In most cases this will be a timestamp. + + + +Asserts that the caller is the governance executor. + +When the executor is not the governor itself (i.e. a timelock), it can call functions that are restricted with this modifier, and also potentially execute transactions on behalf of the governor. Because of this, this module is designed to work with the TimelockController as the unique potential external executor. The timelock MUST have the governor as the only proposer, canceller, and executor. + + + +Validates that a proposal is in the expected state. Otherwise it panics. + + + +Consumes a nonce, returns the current value, and increments nonce. + + + +Internal wrapper for `GovernorVotesTrait::get_votes`. + + + +Internal wrapper for `GovernorProposeTrait::proposal_threshold`. + + + +Returns the state of a proposal, given its id. + +Requirements: + +- The proposal must exist. + + + +Internal propose mechanism. Returns the proposal id. + +Requirements: + +- The proposal must not already exist. + +Emits a [ProposalCreated](#GovernorComponent-ProposalCreated) event. + + + +Internal cancel mechanism with minimal restrictions. + +A proposal can be cancelled in any state other than Canceled or Executed. + +Once cancelled, a proposal can't be re-submitted. + + + +Internal wrapper for `GovernorCountingTrait::count_vote`. + + + +Internal vote-casting mechanism. + +Checks that the vote is pending and that it has not been cast yet. This function retrieves the voting weight using `get_votes` and then calls the `_count_vote` internal function. + +Emits either: + +- [VoteCast](#GovernorComponent-VoteCast) event if no params are provided. +- [VoteCastWithParams](#GovernorComponent-VoteCastWithParams) event otherwise. + + +#### Events [!toc] [#GovernorComponent-Events] + + +Emitted when a proposal is created. + + + +Emitted when a proposal is queued. + + + +Emitted when a proposal is executed. + + + +Emitted when a proposal is canceled. + + + +Emitted when a vote is cast. + + + +Emitted when a vote is cast with params. + + +## [](#governor_extensions)Governor extensions + +The Governor component can (and must) be extended by implementing the [extensions traits](#GovernorComponent-Extensions-Traits-Traits) to add the desired functionality. This can be achieved by directly implementing the traits on your contract, or by using a set of ready-to-use extensions provided by the library, which are presented below. + +### `GovernorCoreExecutionComponent` [toc] [#GovernorCoreExecutionComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorCoreExecutionComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) providing an execution mechanism directly through the Governor itself. For a timelocked execution mechanism, see [GovernorTimelockExecutionComponent](#GovernorTimelockExecutionComponent). + +Extension traits implementations + +#### GovernorExecution [!toc] [#GovernorCoreExecutionComponent-GovernorExecution] + +- [`state(self, proposal_id)`](#GovernorCoreExecutionComponent-state) +- [`executor(self)`](#GovernorCoreExecutionComponent-executor) +- [`execute_operations(self, proposal_id, calls, description_hash)`](#GovernorCoreExecutionComponent-execute_operations) +- [`queue_operations(self, proposal_id, calls, description_hash)`](#GovernorCoreExecutionComponent-queue_operations) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorCoreExecutionComponent-proposal_needs_queuing) +- [`cancel_operations(self, proposal_id, description_hash)`](#GovernorCoreExecutionComponent-cancel_operations) + +#### Extension traits functions [!toc] [#GovernorCoreExecutionComponent-Extension-Traits-Functions] + + +Returns the state of a proposal given its id. + +Requirements: + +- The proposal must exist. + + + +Returns the executor address. + +In this case, it returns the governor contract address since execution is performed directly through it. + + + +Executes the proposal's operations directly through the governor contract. + + + +In this implementation, queuing is not required so it returns 0. + + + +In this implementation, it always returns false. + + + +Cancels a proposal's operations. + + +### `GovernorCountingSimpleComponent` [toc] [#GovernorCountingSimpleComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorCountingSimpleComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for simple vote counting with three options. + +Extension traits implementations + +#### GovernorCounting [!toc] [#GovernorCountingSimpleComponent-GovernorCounting] + +- [`counting_mode(self)`](#GovernorCountingSimpleComponent-counting_mode) +- [`count_vote(self, proposal_id, account, support, total_weight, params)`](#GovernorCountingSimpleComponent-count_vote) +- [`has_voted(self, proposal_id, account)`](#GovernorCountingSimpleComponent-has_voted) +- [`quorum_reached(self, proposal_id)`](#GovernorCountingSimpleComponent-quorum_reached) +- [`vote_succeeded(self, proposal_id)`](#GovernorCountingSimpleComponent-vote_succeeded) + +#### Extension traits functions [!toc] [#GovernorCountingSimpleComponent-Extension-Traits-Functions] + + +Returns `"support=bravo&quorum=for,abstain"`. + +- `support=bravo` indicates that the support follows the Governor Bravo format where voters can vote For, Against, or Abstain +- `quorum=for,abstain` indicates that both For and Abstain votes count toward quorum + + + +Records a vote for a proposal. + +The support value follows the `VoteType` enum (0=Against, 1=For, 2=Abstain). + +Returns the weight that was counted. + + + +Returns whether an account has cast a vote on a proposal. + + + + +Returns whether a proposal has reached quorum. + +In this implementation, both For and Abstain votes count toward quorum. + + + +Returns whether a proposal has succeeded. + +In this implementation, the For votes must be strictly greater than Against votes. + + +### `GovernorSettingsComponent` [toc] [#GovernorSettingsComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorSettingsComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for settings that are updatable through governance. + +Extension traits implementations + +#### GovernorSettings [!toc] [#GovernorSettingsComponent-GovernorSettings] + +- [`voting_delay(self)`](#GovernorSettingsComponent-voting_delay) +- [`voting_period(self)`](#GovernorSettingsComponent-voting_period) +- [`proposal_threshold(self)`](#GovernorSettingsComponent-proposal_threshold) + +Embeddable implementations + +#### GovernorSettingsAdminImpl [!toc] [#GovernorSettingsComponent-GovernorSettingsAdminImpl] + +- [`set_voting_delay(self, new_voting_delay)`](#GovernorSettingsComponent-set_voting_delay) +- [`set_voting_period(self, new_voting_period)`](#GovernorSettingsComponent-set_voting_period) +- [`set_proposal_threshold(self, new_proposal_threshold)`](#GovernorSettingsComponent-set_proposal_threshold) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorSettingsComponent-InternalImpl] + +- [`initializer(self, new_voting_delay, new_voting_period, new_proposal_threshold)`](#GovernorSettingsComponent-initializer) +- [`assert_only_governance(self)`](#GovernorSettingsComponent-assert_only_governance) +- [`_set_voting_delay(self, new_voting_delay)`](#GovernorSettingsComponent-_set_voting_delay) +- [`_set_voting_period(self, new_voting_period)`](#GovernorSettingsComponent-_set_voting_period) +- [`_set_proposal_threshold(self, new_proposal_threshold)`](#GovernorSettingsComponent-_set_proposal_threshold) + +Events + +- [`VotingDelayUpdated(old_voting_delay, new_voting_delay)`](#GovernorSettingsComponent-VotingDelayUpdated) +- [`VotingPeriodUpdated(old_voting_period, new_voting_period)`](#GovernorSettingsComponent-VotingPeriodUpdated) +- [`ProposalThresholdUpdated(old_proposal_threshold, new_proposal_threshold)`](#GovernorSettingsComponent-ProposalThresholdUpdated) + +#### Extension traits functions [!toc] [#GovernorSettingsComponent-ExtensionTraitsFunctions] + + +Returns the delay, between when a proposal is created and when voting starts. + + + +Returns the time period, during which votes can be cast. + + + +Returns the minimum number of votes required for an account to create a proposal. + + +#### Embeddable functions [!toc] [#GovernorSettingsComponent-EmbeddableFunctions] + + +Sets the voting delay. + +Requirements: + +- Caller must be the governance executor. + +This function does not emit an event if the new voting delay is the same as the old one. + +May emit a [VotingDelayUpdated](#GovernorSettingsComponent-VotingDelayUpdated) event. + + + +Sets the voting period. + +This function does not emit an event if the new voting period is the same as the old one. + +Requirements: + +- Caller must be the governance executor. +- `new_voting_period` must be greater than 0. + +May emit a [VotingPeriodUpdated](#GovernorSettingsComponent-VotingPeriodUpdated) event. + + + +Sets the proposal threshold. + +This function does not emit an event if the new proposal threshold is the same as the old one. + +Requirements: + +- Caller must be the governance executor. + +May emit a [ProposalThresholdUpdated](#GovernorSettingsComponent-ProposalThresholdUpdated) event. + + +#### Internal functions [!toc] [#GovernorSettingsComponent-InternalFunctions] + + +Initializes the component by setting the default values. + +Requirements: + +- `new_voting_period` must be greater than 0. + +Emits a [VotingDelayUpdated](#GovernorSettingsComponent-VotingDelayUpdated), [VotingPeriodUpdated](#GovernorSettingsComponent-VotingPeriodUpdated), and [ProposalThresholdUpdated](#GovernorSettingsComponent-ProposalThresholdUpdated) event. + + + +Asserts that the caller is the governance executor. + + + +Internal function to update the voting delay. + +This function does not emit an event if the new voting delay is the same as the old one. + +May emit a [VotingDelayUpdated](#GovernorSettingsComponent-VotingDelayUpdated) event. + + + +Internal function to update the voting period. + +Requirements: + +- `new_voting_period` must be greater than 0. + +This function does not emit an event if the new voting period is the same as the old one. + +May emit a [VotingPeriodUpdated](#GovernorSettingsComponent-VotingPeriodUpdated) event. + + + +Internal function to update the proposal threshold. + +This function does not emit an event if the new proposal threshold is the same as the old one. + +May emit a [ProposalThresholdUpdated](#GovernorSettingsComponent-ProposalThresholdUpdated) event. + + +#### Events [!toc] [#GovernorSettingsComponent-Events] + + +Emitted when the voting delay is updated. + + + +Emitted when the voting period is updated. + + + +Emitted when the proposal threshold is updated. + + +### `GovernorVotesComponent` [toc] [#GovernorVotesComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorVotesComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for voting weight extraction from a token with the [IVotes](#IVotes) extension. + +Extension traits implementations + +#### GovernorVotes [!toc] [#GovernorVotesComponent-GovernorVotes] + +- [`clock(self)`](#GovernorVotesComponent-clock) +- [`CLOCK_MODE(self)`](#GovernorVotesComponent-CLOCK_MODE) +- [`get_votes(self, account, timepoint, params)`](#GovernorVotesComponent-get_votes) + +Embeddable implementations + +#### VotesTokenImpl [!toc] [#GovernorVotesComponent-VotesTokenImpl] + +- [`token(self)`](#GovernorVotesComponent-token) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorVotesComponent-InternalImpl] + +- [`initializer(self, votes_token)`](#GovernorVotesComponent-initializer) + +#### Extension traits functions [!toc] [#GovernorVotesComponent-ExtensionTraitsFunctions] + + +Returns the current timepoint determined by the governor's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the governor is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + + +Returns the voting power of `account` at a specific `timepoint` using the votes token. + + +#### Embeddable functions [!toc] [#GovernorVotesComponent-EmbeddableFunctions] + + +Returns the votes token that voting power is sourced from. + + +#### Internal functions [!toc] [#GovernorVotesComponent-InternalFunctions] + + +Initializes the component by setting the votes token. + +Requirements: + +- `votes_token` must not be zero. + + +### `GovernorVotesQuorumFractionComponent` [toc] [#GovernorVotesQuorumFractionComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for voting weight extraction from a token with the [IVotes](#IVotes) extension and a quorum expressed as a fraction of the total supply. + +Extension traits implementations + +#### GovernorQuorum [!toc] [#GovernorVotesQuorumFractionComponent-GovernorQuorum] + +- [`quorum(self, timepoint)`](#GovernorVotesQuorumFractionComponent-quorum) + +#### GovernorVotes [!toc] [#GovernorVotesQuorumFractionComponent-GovernorVotes] + +- [`clock(self)`](#GovernorVotesQuorumFractionComponent-clock) +- [`CLOCK_MODE(self)`](#GovernorVotesQuorumFractionComponent-CLOCK_MODE) +- [`get_votes(self, account, timepoint, params)`](#GovernorVotesQuorumFractionComponent-get_votes) + +Embeddable implementations + +#### QuorumFractionImpl [!toc] [#GovernorVotesQuorumFractionComponent-QuorumFractionImpl] + +- [`token(self)`](#GovernorVotesQuorumFractionComponent-token) +- [`current_quorum_numerator(self)`](#GovernorVotesQuorumFractionComponent-current_quorum_numerator) +- [`quorum_numerator(self, timepoint)`](#GovernorVotesQuorumFractionComponent-quorum_numerator) +- [`quorum_denominator(self)`](#GovernorVotesQuorumFractionComponent-quorum_denominator) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorVotesQuorumFractionComponent-InternalImpl] + +- [`initializer(self, votes_token, quorum_numerator)`](#GovernorVotesQuorumFractionComponent-initializer) +- [`update_quorum_numerator(self, new_quorum_numerator)`](#GovernorVotesQuorumFractionComponent-update_quorum_numerator) + +Events + +- [`QuorumNumeratorUpdated(old_quorum_numerator, new_quorum_numerator)`](#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated) + +#### Extension traits functions [!toc] [#GovernorVotesQuorumFractionComponent-ExtensionTraitsFunctions] + + +It is computed as a percentage of the votes token total supply at a given `timepoint` in the past. + + + +Returns the current timepoint determined by the governor's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the governor is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + + +Returns the voting power of `account` at a specific `timepoint` using the votes token. + + +#### Embeddable functions [!toc] [#GovernorVotesQuorumFractionComponent-EmbeddableFunctions] + + +Returns the address of the votes token used for voting power extraction. + + + +Returns the current quorum numerator value. + + + +Returns the quorum numerator value at a specific `timepoint` in the past. + + + +Returns the quorum denominator value. + + +#### Internal functions [!toc] [#GovernorVotesQuorumFractionComponent-InternalFunctions] + + +Initializes the component by setting the votes token and the initial quorum numerator value. + +Requirements: + +- `votes_token` must not be zero. +- `quorum_numerator` must be less than `quorum_denominator`. + +Emits a [QuorumNumeratorUpdated](#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated) event. + + + +Updates the quorum numerator. + +This function does not emit an event if the new quorum numerator is the same as the old one. + +Requirements: + +- `new_quorum_numerator` must be less than `quorum_denominator`. + +May emit a [QuorumNumeratorUpdated](#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated) event. + + +#### Events [!toc] [#GovernorVotesQuorumFractionComponent-Events] + + +Emitted when the quorum numerator is updated. + + +### `GovernorTimelockExecutionComponent` [toc] [#GovernorTimelockExecutionComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) that binds the execution process to an instance of a contract implementing [TimelockControllerComponent](#TimelockControllerComponent). This adds a delay, enforced by the timelock to all successful proposals (in addition to the voting duration). + +The Governor needs the [PROPOSER, EXECUTOR, and CANCELLER roles](../governance/timelock#roles) to work properly. + +Using this model means the proposal will be operated by the timelock and not by the governor. Thus, the assets and permissions must be attached to the timelock. Any asset sent to the governor will be inaccessible from a proposal, unless executed via `Governor::relay`. + +Setting up the timelock to have additional proposers or cancellers besides the governor is very risky, as it grants them the ability to: 1) execute operations as the timelock, and thus possibly performing operations or accessing funds that are expected to only be accessible through a vote, and 2) block governance proposals that have been approved by the voters, effectively executing a Denial of Service attack. + +Extension traits implementations + +#### GovernorExecution [!toc] [#GovernorTimelockExecutionComponent-GovernorExecution] + +- [`state(self, proposal_id)`](#GovernorTimelockExecutionComponent-state) +- [`executor(self)`](#GovernorTimelockExecutionComponent-executor) +- [`execute_operations(self, proposal_id, calls, description_hash)`](#GovernorTimelockExecutionComponent-execute_operations) +- [`queue_operations(self, proposal_id, calls, description_hash)`](#GovernorTimelockExecutionComponent-queue_operations) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorTimelockExecutionComponent-proposal_needs_queuing) +- [`cancel_operations(self, proposal_id, description_hash)`](#GovernorTimelockExecutionComponent-cancel_operations) + +Embeddable implementations + +#### TimelockedImpl [!toc] [#GovernorTimelockExecutionComponent-TimelockedImpl] + +- [`timelock(self)`](#GovernorTimelockExecutionComponent-timelock) +- [`get_timelock_id(self, proposal_id)`](#GovernorTimelockExecutionComponent-get_timelock_id) +- [`update_timelock(self, new_timelock)`](#GovernorTimelockExecutionComponent-update_timelock) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorTimelockExecutionComponent-InternalImpl] + +- [`initializer(self, timelock_controller)`](#GovernorTimelockExecutionComponent-initializer) +- [`assert_only_governance(self)`](#GovernorTimelockExecutionComponent-assert_only_governance) +- [`timelock_salt(self, description_hash)`](#GovernorTimelockExecutionComponent-timelock_salt) +- [`get_timelock_dispatcher(self)`](#GovernorTimelockExecutionComponent-get_timelock_dispatcher) +- [`_update_timelock(self, new_timelock)`](#GovernorTimelockExecutionComponent-_update_timelock) + +Events + +- [`TimelockUpdated(old_timelock, new_timelock)`](#GovernorTimelockExecutionComponent-TimelockUpdated) + +#### Extension traits functions [!toc] [#GovernorTimelockExecutionComponent-ExtensionTraitsFunctions] + + +Returns the state of a proposal given its id. + +Requirements: + +- The proposal must exist. + + + +Returns the executor address. + +In this module, the executor is the timelock controller. + + + +Runs the already queued proposal through the timelock. + + + +Queue a proposal to the timelock. + +Returns the eta for the execution of the queued proposal. + + + +In this implementation, it always returns true. + + + +Cancels the timelocked proposal if it has already been queued. + + +#### Embeddable functions [!toc] [#GovernorTimelockExecutionComponent-EmbeddableFunctions] + + +Returns the timelock controller address. + + + +Returns the timelock proposal id for a given proposal id. + + + +Updates the associated timelock. + +Requirements: + +- The caller must be the governance. + +Emits a [TimelockUpdated](#GovernorTimelockExecutionComponent-TimelockUpdated) event. + + +#### Internal functions [!toc] [#GovernorTimelockExecutionComponent-InternalFunctions] + + +Initializes the timelock controller. + +Requirements: + +- The timelock must not be the zero address. + + + +Ensures the caller is the executor (the timelock controller in this case). + + + +Computes the `TimelockController` operation salt as the XOR of the governor address and `description_hash`. + +It is computed with the governor address itself to avoid collisions across governor instances using the same timelock. + + + +Returns a dispatcher for interacting with the timelock controller. + + + +Internal function to update the timelock controller address. + +Emits a [TimelockUpdated](#GovernorTimelockExecutionComponent-TimelockUpdated) event. + + +#### Events [!toc] [#GovernorTimelockExecutionComponent-Events] + + +Emitted when the timelock controller is updated. + + +## [](#multisig)Multisig + +A Multisig module enhances security and decentralization by requiring multiple signers to approve and execute transactions. Features include configurable quorum, signer management, and self-administration, ensuring collective decision-making and transparency for critical operations. + +### `MultisigComponent` [toc] [#MultisigComponent] + + + +```rust +use openzeppelin_governance::multisig::MultisigComponent; +``` + +Component that implements [IMultisig](#IMultisig) and provides functionality for multisignature wallets, including transaction management, quorum handling, and signer operations. + +Embeddable Implementations + +#### MultisigImpl [!toc] [#MultisigComponent-MultisigImpl] + +- [`get_quorum(self)`](#MultisigComponent-get_quorum) +- [`is_signer(self, signer)`](#MultisigComponent-is_signer) +- [`get_signers(self)`](#MultisigComponent-get_signers) +- [`is_confirmed(self, id)`](#MultisigComponent-is_confirmed) +- [`is_confirmed_by(self, id, signer)`](#MultisigComponent-is_confirmed_by) +- [`is_executed(self, id)`](#MultisigComponent-is_executed) +- [`get_submitted_block(self, id)`](#MultisigComponent-get_submitted_block) +- [`get_transaction_state(self, id)`](#MultisigComponent-get_transaction_state) +- [`get_transaction_confirmations(self, id)`](#MultisigComponent-get_transaction_confirmations) +- [`hash_transaction(self, to, selector, calldata, salt)`](#MultisigComponent-hash_transaction) +- [`hash_transaction_batch(self, calls, salt)`](#MultisigComponent-hash_transaction_batch) +- [`add_signers(ref self, new_quorum, signers_to_add)`](#MultisigComponent-add_signers) +- [`remove_signers(ref self, new_quorum, signers_to_remove)`](#MultisigComponent-remove_signers) +- [`replace_signer(ref self, signer_to_remove, signer_to_add)`](#MultisigComponent-replace_signer) +- [`change_quorum(ref self, new_quorum)`](#MultisigComponent-change_quorum) +- [`submit_transaction(ref self, to, selector, calldata, salt)`](#MultisigComponent-submit_transaction) +- [`submit_transaction_batch(ref self, calls, salt)`](#MultisigComponent-submit_transaction_batch) +- [`confirm_transaction(ref self, id)`](#MultisigComponent-confirm_transaction) +- [`revoke_confirmation(ref self, id)`](#MultisigComponent-revoke_confirmation) +- [`execute_transaction(ref self, to, selector, calldata, salt)`](#MultisigComponent-execute_transaction) +- [`execute_transaction_batch(ref self, calls, salt)`](#MultisigComponent-execute_transaction_batch) + +Internal Implementations + +#### InternalImpl [!toc] [#MultisigComponent-InternalImpl] + +- [`initializer(ref self, quorum, signers)`](#MultisigComponent-initializer) +- [`resolve_tx_state(self, id)`](#MultisigComponent-resolve_tx_state) +- [`assert_one_of_signers(self, caller)`](#MultisigComponent-assert_one_of_signers) +- [`assert_tx_exists(self, id)`](#MultisigComponent-assert_tx_exists) +- [`assert_only_self(self)`](#MultisigComponent-assert_only_self) +- [`_add_signers(ref self, new_quorum, signers_to_add)`](#MultisigComponent-_add_signers) +- [`_remove_signers(ref self, new_quorum, signers_to_remove)`](#MultisigComponent-_remove_signers) +- [`_replace_signer(ref self, signer_to_remove, signer_to_add)`](#MultisigComponent-_replace_signer) +- [`_change_quorum(ref self, new_quorum)`](#MultisigComponent-_change_quorum) + +Events + +- [`SignerAdded(signer)`](#MultisigComponent-SignerAdded) +- [`SignerRemoved(signer)`](#MultisigComponent-SignerRemoved) +- [`QuorumUpdated(old_quorum, new_quorum)`](#MultisigComponent-QuorumUpdated) +- [`TransactionSubmitted(id, signer)`](#MultisigComponent-TransactionSubmitted) +- [`TransactionConfirmed(id, signer)`](#MultisigComponent-TransactionConfirmed) +- [`ConfirmationRevoked(id, signer)`](#MultisigComponent-ConfirmationRevoked) +- [`TransactionExecuted(id)`](#MultisigComponent-TransactionExecuted) +- [`CallSalt(id, salt)`](#MultisigComponent-CallSalt) + +#### Embeddable functions [!toc] [#MultisigComponent-EmbeddableFunctions] + + +Returns the current quorum value. + + + +Checks if a given `signer` is registered. + + + +Returns a list of all current signers. + + + +Returns whether the transaction with the given `id` has been confirmed. A confirmed transaction has received the required number of confirmations (quorum). + + + +Returns whether the transaction with the given `id` has been confirmed by the specified `signer`. + + + +Returns whether the transaction with the given `id` has been executed. + + + +Returns the block number when the transaction with the given `id` was submitted. + + + +Returns the current state of the transaction with the given `id`. + +The possible states are: + +- `NotFound`: the transaction does not exist. +- `Pending`: the transaction exists but hasn't reached the required confirmations. +- `Confirmed`: the transaction has reached the required confirmations but hasn't been executed. +- `Executed`: the transaction has been executed. + + + +Returns the number of confirmations from registered signers for the transaction with the specified `id`. + + + +Returns the computed identifier of a transaction containing a single call. + + + +Returns the computed identifier of a transaction containing a batch of calls. + + + +Adds new signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after addition. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for each signer added. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Removes signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after removal. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for each signer removed. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Replaces an existing signer with a new signer. + +Requirements: + +- The caller must be the contract itself. +- `signer_to_remove` must be an existing signer. +- `signer_to_add` must not be an existing signer. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for the removed signer. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for the new signer. + + + +Updates the quorum value to `new_quorum`. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be non-zero. +- `new_quorum` must be less than or equal to the total number of signers. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Submits a new transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#MultisigComponent-TransactionSubmitted) event. + +Emits a [CallSalt](#MultisigComponent-CallSalt) event if `salt` is not zero. + + + +Submits a new batch transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#MultisigComponent-TransactionSubmitted) event. + +Emits a [CallSalt](#MultisigComponent-CallSalt) event if `salt` is not zero. + + + +Confirms a transaction with the given `id`. + +Requirements: + +- The caller must be a registered signer. +- The transaction must exist and not be executed. +- The caller must not have already confirmed the transaction. + +Emits a [TransactionConfirmed](#MultisigComponent-TransactionConfirmed) event. + + + +Revokes a previous confirmation for a transaction with the given `id`. + +Requirements: + +- The transaction must exist and not be executed. +- The caller must have previously confirmed the transaction. + +Emits a [ConfirmationRevoked](#MultisigComponent-ConfirmationRevoked) event. + + + +Executes a confirmed transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#MultisigComponent-TransactionExecuted) event. + + + +Executes a confirmed batch transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#MultisigComponent-TransactionExecuted) event. + + +#### Internal functions [!toc] [#MultisigComponent-InternalFunctions] + + +Initializes the Multisig component with the initial `quorum` and `signers`. This function must be called during contract initialization to set up the initial state. + +Requirements: + +- `quorum` must be non-zero and less than or equal to the number of `signers`. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for each signer added. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event. + + + +Resolves and returns the current state of the transaction with the given `id`. + +The possible states are: + +- `NotFound`: the transaction does not exist. +- `Pending`: the transaction exists but hasn't reached the required confirmations. +- `Confirmed`: the transaction has reached the required confirmations but hasn't been executed. +- `Executed`: the transaction has been executed. + + + +Asserts that the `caller` is one of the registered signers. + +Requirements: + +- The `caller` must be a registered signer. + + + +Asserts that a transaction with the given `id` exists. + +Requirements: + +- The transaction with the given `id` must have been submitted. + + + +Asserts that the caller is the contract itself. + +Requirements: + +- The caller must be the contract's own address. + + + +Adds new signers and updates the quorum. + +Requirements: + +- Each signer address must be non-zero. +- `new_quorum` must be non-zero and less than or equal to the total number of signers after addition. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for each new signer added. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Removes existing signers and updates the quorum. + +Requirements: + +- `new_quorum` must be non-zero and less than or equal to the total number of signers after removal. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for each signer removed. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Replaces an existing signer with a new signer. + +Requirements: + +- `signer_to_remove` must be an existing signer. +- `signer_to_add` must not be an existing signer. +- `signer_to_add` must be a non-zero address. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for the removed signer. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for the new signer. + + + +Updates the quorum value to `new_quorum` if it differs from the current quorum. + +Requirements: + +- `new_quorum` must be non-zero. +- `new_quorum` must be less than or equal to the total number of signers. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + +#### Events [!toc] [#MultisigComponent-Events] + + +Emitted when a new `signer` is added. + + + +Emitted when a `signer` is removed. + + + +Emitted when the `quorum` value is updated. + + + +Emitted when a new transaction is submitted by a `signer`. + + + +Emitted when a transaction is confirmed by a `signer`. + + + +Emitted when a `signer` revokes his confirmation. + + + +Emitted when a transaction is executed. + + + +Emitted when a new transaction is submitted with non-zero salt. + + +## [](#timelock)Timelock + +In a governance system, `TimelockControllerComponent` is in charge of introducing a delay between a proposal and its execution. + +### `TimelockControllerComponent` [toc] [#TimelockControllerComponent] + + + +```rust +use openzeppelin_governance::timelock::TimelockControllerComponent; +``` + +Component that implements [ITimelock](#ITimelock) and enables the implementing contract to act as a timelock controller. + +[Embeddable Mixin Implementations](../components#mixins) + +#### TimelockMixinImpl [!toc] [#TimelockControllerComponent-TimelockMixinImpl] + +- [`TimelockImpl`](#TimelockControllerComponent-Embeddable-Impls-TimelockImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) +- [`AccessControlImpl`](./access#AccessControlComponent-Embeddable-Impls) +- [`AccessControlCamelImpl`](./access#AccessControlComponent-Embeddable-Impls) + +Embeddable Implementations + +#### TimelockImpl [!toc] [#TimelockControllerComponent-TimelockImpl] + +- [`is_operation(self, id)`](#TimelockControllerComponent-is_operation) +- [`is_operation_pending(self, id)`](#TimelockControllerComponent-is_operation_pending) +- [`is_operation_ready(self, id)`](#TimelockControllerComponent-is_operation_ready) +- [`is_operation_done(self, id)`](#TimelockControllerComponent-is_operation_done) +- [`get_timestamp(self, id)`](#TimelockControllerComponent-get_timestamp) +- [`get_operation_state(self, id)`](#TimelockControllerComponent-get_operation_state) +- [`get_min_delay(self)`](#TimelockControllerComponent-get_min_delay) +- [`hash_operation(self, call, predecessor, salt)`](#TimelockControllerComponent-hash_operation) +- [`hash_operation_batch(self, calls, predecessor, salt)`](#TimelockControllerComponent-hash_operation_batch) +- [`schedule(self, call, predecessor, salt, delay)`](#TimelockControllerComponent-schedule) +- [`schedule_batch(self, calls, predecessor, salt, delay)`](#TimelockControllerComponent-schedule_batch) +- [`cancel(self, id)`](#TimelockControllerComponent-cancel) +- [`execute(self, call, predecessor, salt)`](#TimelockControllerComponent-execute) +- [`execute_batch(self, calls, predecessor, salt)`](#TimelockControllerComponent-execute_batch) +- [`update_delay(self, new_delay)`](#TimelockControllerComponent-update_delay) + +#### SRC5Impl [!toc] [#TimelockControllerComponent-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +#### AccessControlImpl [!toc] [#TimelockControllerComponent-AccessControlImpl] + +- [`has_role(self, role, account)`](./access#IAccessControl-has_role) +- [`get_role_admin(self, role)`](./access#IAccessControl-get_role_admin) +- [`grant_role(self, role, account)`](./access#IAccessControl-grant_role) +- [`revoke_role(self, role, account)`](./access#IAccessControl-revoke_role) +- [`renounce_role(self, role, account)`](./access#IAccessControl-renounce_role) + +#### AccessControlCamelImpl [!toc] [#TimelockControllerComponent-AccessControlCamelImpl] + +- [`hasRole(self, role, account)`](./access#IAccessControl-hasRole) +- [`getRoleAdmin(self, role)`](./access#IAccessControl-getRoleAdmin) +- [`grantRole(self, role, account)`](./access#IAccessControl-grantRole) +- [`revokeRole(self, role, account)`](./access#IAccessControl-revokeRole) +- [`renounceRole(self, role, account)`](./access#IAccessControl-renounceRole) + +Internal Implementations + +#### InternalImpl [!toc] [#TimelockControllerComponent-InternalImpl] + +- [`initializer(self, min_delay, proposers, executors, admin)`](#TimelockControllerComponent-initializer) +- [`assert_only_role(self, role)`](#TimelockControllerComponent-assert_only_role) +- [`assert_only_role_or_open_role(self, role)`](#TimelockControllerComponent-assert_only_role_or_open_role) +- [`assert_only_self(self)`](#TimelockControllerComponent-assert_only_self) +- [`_before_call(self, id, predecessor)`](#TimelockControllerComponent-_before_call) +- [`_after_call(self, id)`](#TimelockControllerComponent-_after_call) +- [`_schedule(self, id, delay)`](#TimelockControllerComponent-_schedule) +- [`_execute(self, call)`](#TimelockControllerComponent-_execute) + +Events + +- [`CallScheduled(id, index, call, predecessor, delay)`](#TimelockControllerComponent-CallScheduled) +- [`CallExecuted(id, index, call)`](#TimelockControllerComponent-CallExecuted) +- [`CallSalt(id, salt)`](#TimelockControllerComponent-CallSalt) +- [`CallCancelled(id)`](#TimelockControllerComponent-CallCancelled) +- [`MinDelayChanged(old_duration, new_duration)`](#TimelockControllerComponent-MinDelayChanged) + +#### Embeddable functions [!toc] [#TimelockControllerComponent-EmbeddableFunctions] + + +Returns whether `id` corresponds to a registered operation. This includes the OperationStates: `Waiting`, `Ready`, and `Done`. + + + +Returns whether the `id` OperationState is pending or not. Note that a pending operation may be either `Waiting` or `Ready`. + + + +Returns whether the `id` OperationState is `Ready` or not. + + + +Returns whether the `id` OperationState is `Done` or not. + + + +Returns the timestamp at which `id` becomes `Ready`. + +`0` means the OperationState is `Unset` and `1` means the OperationState is `Done`. + + + +Returns the current state of the operation with the given `id`. + +The possible states are: + +- `Unset`: the operation has not been scheduled or has been canceled. +- `Waiting`: the operation has been scheduled and is pending the scheduled delay. +- `Ready`: the timer has expired, and the operation is eligible for execution. +- `Done`: the operation has been executed. + + + +Returns the minimum delay in seconds for an operation to become valid. This value can be changed by executing an operation that calls `update_delay`. + + + +Returns the identifier of an operation containing a single transaction. + + + +Returns the identifier of an operation containing a batch of transactions. + + + +Schedule an operation containing a single transaction. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. +- The proposal must not already exist. +- `delay` must be greater than or equal to the min delay. + +Emits [CallScheduled](#TimelockControllerComponent-CallScheduled) event. Emits [CallSalt](#TimelockControllerComponent-CallSalt) event if `salt` is not zero. + + + +Schedule an operation containing a batch of transactions. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. +- The proposal must not already exist. +- `delay` must be greater than or equal to the min delay. + +Emits one [CallScheduled](#TimelockControllerComponent-CallScheduled) event for each transaction in the batch. Emits [CallSalt](#TimelockControllerComponent-CallSalt) event if `salt` is not zero. + + + +Cancels an operation. A canceled operation returns to `Unset` OperationState. + +Requirements: + +- The caller must have the `CANCELLER_ROLE` role. +- `id` must be a pending operation. + +Emits a [CallCancelled](#TimelockControllerComponent-CallCancelled) event. + + + +Execute a (Ready) operation containing a single Call. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#TimelockControllerComponent-CallExecuted) event. + +This function can reenter, but it doesn't pose a risk because [`_after_call(self: @ContractState, id: felt252)` internal](#TimelockControllerComponent-_after_call) checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Execute a (Ready) operation containing a batch of Calls. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#TimelockControllerComponent-CallExecuted) event for each Call. + +This function can reenter, but it doesn't pose a risk because `_after_call` checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Changes the minimum timelock duration for future operations. + +Requirements: + +- The caller must be the timelock itself. This can only be achieved by scheduling and later executing an operation where the timelock is the target and the data is the serialized call to this function. + +Emits a [MinDelayChanged](#TimelockControllerComponent-MinDelayChanged) event. + + +#### Internal functions [!toc] [#TimelockControllerComponent-InternalFunctions] + + +Initializes the contract by registering support for SRC5 and AccessControl. + +This function also configures the contract with the following parameters: + +- `min_delay`: initial minimum delay in seconds for operations. +- `proposers`: accounts to be granted proposer and canceller roles. +- `executors`: accounts to be granted executor role. +- `admin`: optional account to be granted admin role; disable with zero address. + +The optional admin can aid with initial configuration of roles after deployment without being subject to delay, but this role should be subsequently renounced in favor of administration through timelocked proposals. + +Emits two [IAccessControl::RoleGranted](./access#IAccessControl-RoleGranted) events for each account in `proposers` with `PROPOSER_ROLE` and `CANCELLER_ROLE` roles. + +Emits a [IAccessControl::RoleGranted](./access#IAccessControl-RoleGranted) event for each account in `executors` with `EXECUTOR_ROLE` role. + +May emit a [IAccessControl::RoleGranted](./access#IAccessControl-RoleGranted) event for `admin` with `DEFAULT_ADMIN_ROLE` role (if `admin` is not zero). + +Emits [MinDelayChanged](#TimelockControllerComponent-MinDelayChanged) event. + + + +Validates that the caller has the given `role`. Otherwise it panics. + + + +Validates that the caller has the given `role`. If `role` is granted to the zero address, then this is considered an open role which allows anyone to be the caller. + + + +Validates that the caller is the timelock contract itself. Otherwise it panics. + + + +Private function that checks before execution of an operation's calls. + +Requirements: + +- `id` must be in the `Ready` OperationState. +- `predecessor` must either be zero or be in the `Done` OperationState. + + + +Private function that checks after execution of an operation's calls and sets the OperationState of `id` to `Done`. + +Requirements: + +- `id` must be in the Ready OperationState. + + + +Private function that schedules an operation that is to become valid after a given `delay`. + + + +Private function that executes an operation's calls. + + +#### Events [!toc] [#TimelockControllerComponent-Events] + + +Emitted when `call` is scheduled as part of operation `id`. + + + +Emitted when `call` is performed as part of operation `id`. + + + +Emitted when a new proposal is scheduled with non-zero salt. + + + +Emitted when operation `id` is cancelled. + + + +Emitted when the minimum delay for future operations is modified. + + +## [](#votes)Votes + +The `VotesComponent` provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. + +### `VotesComponent` [toc] [#VotesComponent] + + + +```rust +use openzeppelin_governance::votes::VotesComponent; +``` + +Component that implements the [IVotes](#IVotes) interface and provides a flexible system for tracking and delegating voting power. + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + +When using this module, your contract must implement the [VotingUnitsTrait](#VotingUnitsTrait). For convenience, this is done automatically for `ERC20` and `ERC721` tokens. + +Voting Units Trait Implementations + +#### ERC20VotesImpl [!toc] [#VotesComponent-ERC20VotesImpl] + +- [`get_voting_units(self, account)`](#VotesComponent-ERC20VotesImpl-get_voting_units) + +#### ERC721VotesImpl [!toc] [#VotesComponent-ERC721VotesImpl] + +- [`get_voting_units(self, account)`](#VotesComponent-ERC721VotesImpl-get_voting_units) + +Embeddable Implementations + +#### VotesImpl [!toc] [#VotesComponent-VotesImpl] + +- [`get_votes(self, account)`](#VotesComponent-get_votes) +- [`get_past_votes(self, account, timepoint)`](#VotesComponent-get_past_votes) +- [`get_past_total_supply(self, timepoint)`](#VotesComponent-get_past_total_supply) +- [`delegates(self, account)`](#VotesComponent-delegates) +- [`delegate(self, delegatee)`](#VotesComponent-delegate) +- [`delegate_by_sig(self, delegator, delegatee, nonce, expiry, signature)`](#VotesComponent-delegate_by_sig) +- [`clock(self)`](#VotesComponent-clock) +- [`CLOCK_MODE(self)`](#VotesComponent-CLOCK_MODE) + +Internal implementations + +#### InternalImpl [!toc] [#VotesComponent-InternalImpl] + +- [`get_total_supply(self)`](#VotesComponent-get_total_supply) +- [`move_delegate_votes(self, from, to, amount)`](#VotesComponent-move_delegate_votes) +- [`transfer_voting_units(self, from, to, amount)`](#VotesComponent-transfer_voting_units) +- [`num_checkpoints(self, account)`](#VotesComponent-num_checkpoints) +- [`checkpoints(self, account, pos)`](#VotesComponent-checkpoints) +- [`_delegate(self, account, delegatee)`](#VotesComponent-_delegate) + +Events + +- [`DelegateChanged(delegator, from_delegate, to_delegate)`](#VotesComponent-DelegateChanged) +- [`DelegateVotesChanged(delegate, previous_votes, new_votes)`](#VotesComponent-DelegateVotesChanged) + + +Returns the number of voting units for a given account. + +This implementation is specific to ERC20 tokens, where the balance of tokens directly represents the number of voting units. + +This implementation will work out of the box if the ERC20 component is implemented in the final contract. + +This implementation assumes tokens map to voting units 1:1. Any deviation from this formula when transferring voting units (e.g. by using hooks) may compromise the internal vote accounting. + + + +Returns the number of voting units for a given account. + +This implementation is specific to ERC721 tokens, where each token represents one voting unit. The function returns the balance of ERC721 tokens for the specified account. + +This implementation will work out of the box if the ERC721 component is implemented in the final contract. + +This implementation assumes tokens map to voting units 1:1. Any deviation from this formula when transferring voting units (e.g. by using hooks) may compromise the internal vote accounting. + + +#### Embeddable functions [!toc] [#VotesComponent-EmbeddableFunctions] + + +Returns the current amount of votes that `account` has. + + + +Returns the amount of votes that `account` had at a specific moment in the past. + +Requirements: + +- `timepoint` must be in the past. + + + +Returns the total supply of votes available at a specific moment in the past. + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. Votes that have not been delegated are still part of total supply, even though they would not participate in a vote. + +Requirements: + +- `timepoint` must be in the past. + + + +Returns the delegate that `account` has chosen. + + + +Delegates votes from the sender to `delegatee`. + +Emits a [DelegateChanged](#VotesComponent-DelegateChanged) event. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Delegates votes from `delegator` to `delegatee` through a [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message signature validation. + +Requirements: + +- `expiry` must not be in the past. +- `nonce` must match the account's current nonce. +- `delegator` must implement `SRC6::is_valid_signature`. +- `signature` should be valid for the message hash. + +Emits a [DelegateChanged](#VotesComponent-DelegateChanged) event. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Returns the current timepoint determined by the contract's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the contract is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + +#### Internal functions [!toc] [#VotesComponent-InternalFunctions] + + +Returns the current total supply of votes. + + + +Moves delegated votes from one delegate to another. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Transfers, mints, or burns voting units. + +To register a mint, `from` should be zero. To register a burn, `to` should be zero. Total supply of voting units will be adjusted with mints and burns. + +If voting units are based on an underlying transferable asset (like a token), you must call this function every time the asset is transferred to keep the internal voting power accounting in sync. For ERC20 and ERC721 tokens, this is typically handled using hooks. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Returns the number of checkpoints for `account`. + + + +Returns the `pos`-th checkpoint for `account`. + + + +Delegates all of `account`'s voting units to `delegatee`. + +Emits a [DelegateChanged](#VotesComponent-DelegateChanged) event. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + +#### Events [!toc] [#VotesComponent-Events] + + +Emitted when an account changes their delegate. + + + +Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. + + +### `VotingUnitsTrait` [toc] [#VotingUnitsTrait] + + + +```rust +pub trait VotingUnitsTrait { + fn get_voting_units(self: @TState, account: ContractAddress) -> u256; +} +``` + +A trait that must be implemented when integrating [VotesComponent](#VotesComponent) into a contract. It offers a mechanism to retrieve the number of voting units for a given account at the current time. + +Functions + +- [`get_voting_units(self, account)`](#VotingUnitsTrait-get_voting_units) + +#### Functions [!toc] [#VotingUnitsTrait-Functions] + + +Returns the number of voting units for a given account. For ERC20, this is typically the token balance. For ERC721, this is typically the number of tokens owned. + +While any formula can be used as a measure of voting units, the internal vote accounting of the contract may be compromised if voting units are transferred in any external flow by following a different formula. +For example, when implementing the hook for ERC20, the number of voting units transferred should match the formula given by the `get_voting_units` implementation. + diff --git a/docs/content/contracts-cairo/2.x/api/introspection.mdx b/docs/content/contracts-cairo/2.x/api/introspection.mdx new file mode 100644 index 00000000..a0bb6158 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/introspection.mdx @@ -0,0 +1,100 @@ +--- +title: Introspection +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate handles [type introspection](https://en.wikipedia.org/wiki/Type_introspection) of contracts. In other words, it examines which functions can be called on a given contract. This is referred to as the contract's interface. + +## Interfaces + +### `ISRC5` [toc] [#ISRC5] + + + +```rust +use openzeppelin_introspection::interface::ISRC5; +``` + +Interface of the SRC5 Introspection Standard as defined in [SNIP-5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md). + +[SRC5 ID](#ISRC5) + +```text +0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055 +``` + +Functions + +- [`supports_interface(interface_id)`](#ISRC5-supports_interface) + +#### Functions [!toc] [#ISRC5-Functions] + + +Checks whether the contract implements the given interface. + +Check [Computing the Interface ID](../introspection#computing-the-interface-id) for more information on how to compute this ID. + + +## [](#core)Core + +### `SRC5Component` [toc] [#SRC5Component] + + + +```rust +use openzeppelin_introspection::src5::SRC5Component; +``` + +SRC5 component extending [`ISRC5`](#ISRC5). + +Embeddable Implementations + +#### SRC5Impl [!toc] [#SRC5Component-SRC5Impl] + +- [`supports_interface(self, interface_id)`](#SRC5Component-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#SRC5Component-InternalImpl] + +- [`register_interface(self, interface_id)`](#SRC5Component-register_interface) +- [`deregister_interface(self, interface_id)`](#SRC5Component-deregister_interface) + +#### Embeddable functions [!toc] [#SRC5Component-EmbeddableFunctions] + + +See [`ISRC5::supports_interface`](#ISRC5-supports_interface). + + +#### Internal functions [!toc] [#SRC5Component-InternalFunctions] + + +Registers support for the given `interface_id`. + + + +Deregisters support for the given `interface_id`. + diff --git a/docs/content/contracts-cairo/2.x/api/merkle-tree.mdx b/docs/content/contracts-cairo/2.x/api/merkle-tree.mdx new file mode 100644 index 00000000..5772e073 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/merkle-tree.mdx @@ -0,0 +1,185 @@ +--- +title: Merkle Tree +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides a set of utilities for verifying Merkle Tree proofs on-chain. The tree and the proofs can be generated using this [JavaScript library](https://github.com/ericnordelo/strk-merkle-tree). + +This module provides: + +- `verify` - can prove that some value is part of a Merkle tree. +- `verify_multi_proof` - can prove multiple values are part of a Merkle tree. + +`openzeppelin_merkle_tree` doesn’t have dependencies outside of `corelib`, and can be used in projects that are not Starknet-related. + +To use it as a standalone package, you can add it in your `Scarb.toml` as follows: + +`openzeppelin_merkle_tree = "3.0.0-alpha.1"` + +## [](#modules)Modules + +### [](#merkle_proof)`merkle_proof` [toc] [#merkle_proof] + + + +```rust +use openzeppelin_merkle_tree::merkle_proof; +``` + +These functions deal with verification of Merkle Tree proofs. + +The tree and the proofs can be generated using this [JavaScript library](https://github.com/ericnordelo/strk-merkle-tree). You will find a quickstart guide in the readme. + +You should avoid using leaf values that are two felt252 values long prior to hashing, or use a hash function other than the one used to hash internal nodes for hashing leaves. This is because the concatenation of a sorted pair of internal nodes in the Merkle tree could be reinterpreted as a leaf value. The JavaScript library generates Merkle trees that are safe against this attack out of the box. + +Functions + +- [`verify(proof, root, leaf)`](#merkle_proof-verify) +- [`verify_pedersen(proof, root, leaf)`](#merkle_proof-verify_pedersen) +- [`verify_poseidon(proof, root, leaf)`](#merkle_proof-verify_poseidon) +- [`process_proof(proof, leaf)`](#merkle_proof-process_proof) +- [`verify_multi_proof(proof, proof_flags, root, leaves)`](#merkle_proof-verify_multi_proof) +- [`process_multi_proof(proof, proof_flags, leaf)`](#merkle_proof-process_multi_proof) + +#### [](#merkle_proof-Functions)Functions [!toc] + + +Returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`. + +For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the root of the tree. + +Each pair of leaves and each pair of pre-images are assumed to be sorted. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + +`verify_pedersen` and `verify_poseidon` already include the corresponding `Hasher` implementations. + + + +Version of `verify` using Pedersen as the hashing function. + + + +Version of `verify` using Poseidon as the hashing function. + + + +Returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`. + +A `proof` is valid if and only if the rebuilt hash matches the root of the tree. + +When processing the proof, the pairs of leaves & pre-images are assumed to be sorted. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + + + +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by `root`, according to `proof` and `proof_flags` as described in `process_multi_proof`. + +The `leaves` must be validated independently. + +Not all Merkle trees admit multiproofs. See `process_multi_proof` for details. + +Consider the case where `root == proof.at(0) && leaves.len() == 0` as it will return `true`. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + + + +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. + +The reconstruction proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another leaf/inner node or a proof sibling node, depending on whether each `proof_flags` item is true or false respectively. + +Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: + +1. The tree is complete (but not necessarily perfect). +2. The leaves to be proven are in the opposite order than they are in the tree. (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + +The *empty set* (i.e. the case where `proof.len() == 1 && leaves.len() == 0`) is considered a no-op, and therefore a valid multiproof (i.e. it returns `proof.at(0)`). Consider disallowing this case if you're not validating the leaves elsewhere. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + + +### [](#hashes)`hashes` [toc] [#hashes] + + + +```rust +use openzeppelin_merkle_tree::hashes; +``` + +Module providing the trait and default implementations for the commutative hash functions used in [`merkle_proof`](#merkle_proof). + +The `PedersenCHasher` implementation matches the default node hashing function used in the [JavaScript library](https://github.com/ericnordelo/strk-merkle-tree). + +Traits + +- [`CommutativeHasher`](#hashes-CommutativeHasher) + +Impls + +- [`PedersenCHasher`](#hashes-PedersenCHasher) +- [`PoseidonCHasher`](#hashes-PoseidonCHasher) + +#### [](#hashes-Traits)Traits [!toc] + + +Declares a commutative hash function with the following signature: + +`commutative_hash(a: felt252, b: felt252) → felt252;` + +which computes a commutative hash of a sorted pair of felt252 values. + +This is usually implemented as an extension of a non-commutative hash function, like Pedersen or Poseidon, returning the hash of the concatenation of the two values by first sorting them. + +Frequently used when working with merkle proofs. + +The `commutative_hash` function MUST follow the invariant that `commutative_hash(a, b) == commutative_hash(b, a)`. + + +#### [](#hashes-Impls)Impls [!toc] + + +Implementation of the `CommutativeHasher` trait which computes the Pedersen hash of chaining the two input values with the len (2), sorting the pair first. + + + +Implementation of the `CommutativeHasher` trait which computes the Poseidon hash of the concatenation of two values, sorting the pair first. + diff --git a/docs/content/contracts-cairo/2.x/api/security.mdx b/docs/content/contracts-cairo/2.x/api/security.mdx new file mode 100644 index 00000000..12e3a01f --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/security.mdx @@ -0,0 +1,202 @@ +--- +title: Security +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides components to handle common security-related tasks. + +## [](#initializable)Initializable + +### [](#InitializableComponent)`InitializableComponent` [toc] [#InitializableComponent] + + + +```rust +use openzeppelin_security::InitializableComponent; +``` + +Component enabling one-time initialization for contracts. + +Embeddable Implementations + +InitializableImpl + +- [`is_initialized(self)`](#InitializableComponent-is_initialized) + +Internal Implementations + +InternalImpl + +- [`initialize(self)`](#InitializableComponent-initialize) + +#### [](#InitializableComponent-Embeddable-Functions)Embeddable functions [!toc] + + +Returns whether the contract has been initialized. + + +#### [](#InitializableComponent-Internal-Functions)Internal functions [!toc] + + +Initializes the contract. Can only be called once. + +Requirements: + +- the contract must not have been initialized before. + + +## [](#pausable)Pausable + +### [](#PausableComponent)`PausableComponent` [toc] [#PausableComponent] + + + +```rust +use openzeppelin_security::PausableComponent; +``` + +Component to implement an emergency stop mechanism. + +Embeddable Implementations + +PausableImpl + +- [`is_paused(self)`](#PausableComponent-is_paused) + +Internal Implementations + +InternalImpl + +- [`assert_not_paused(self)`](#PausableComponent-assert_not_paused) +- [`assert_paused(self)`](#PausableComponent-assert_paused) +- [`pause(self)`](#PausableComponent-pause) +- [`unpause(self)`](#PausableComponent-unpause) + +Events + +- [`Paused(account)`](#PausableComponent-Paused) +- [`Unpaused(account)`](#PausableComponent-Unpaused) + +#### [](#PausableComponent-Embeddable-Functions)Embeddable functions [!toc] + + +Returns whether the contract is currently paused. + + +#### [](#PausableComponent-Internal-Functions)Internal functions [!toc] + + +Panics if the contract is paused. + + + +Panics if the contract is not paused. + + + +Pauses the contract. + +Requirements: + +- the contract must not be paused. + +Emits a [Paused](#PausableComponent-Paused) event. + + + +Unpauses the contract. + +Requirements: + +- the contract must be paused. + +Emits an [Unpaused](#PausableComponent-Unpaused) event. + + +#### [](#PausableComponent-Events)Events [!toc] + + +Emitted when the contract is paused by `account`. + + + +Emitted when the contract is unpaused by `account`. + + +## [](#reentrancyguard)ReentrancyGuard + +### [](#ReentrancyGuardComponent)`ReentrancyGuardComponent` [toc] [#ReentrancyGuardComponent] + + + +```rust +use openzeppelin_security::ReentrancyGuardComponent; +``` + +Component to help prevent reentrant calls. + +Internal Implementations + +InternalImpl + +- [`start(self)`](#ReentrancyGuardComponent-start) +- [`end(self)`](#ReentrancyGuardComponent-end) + +#### [](#ReentrancyGuardComponent-Internal-Functions)Internal functions [!toc] + + +Prevents a contract's function from calling itself or another protected function, directly or indirectly. + +Requirements: + +- the guard must not be currently enabled. + + + +Removes the reentrant guard. + diff --git a/docs/content/contracts-cairo/2.x/api/testing.mdx b/docs/content/contracts-cairo/2.x/api/testing.mdx new file mode 100644 index 00000000..7a9b3cc6 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/testing.mdx @@ -0,0 +1,10 @@ +--- +title: Testing +--- + + +The `openzeppelin_testing` package version is now decoupled from the `cairo-contracts` version. + + +You can find the documentation for the `openzeppelin_testing` package in the README for the corresponding version in the +[scarb registry](https://scarbs.xyz/packages/openzeppelin_testing). diff --git a/docs/content/contracts-cairo/2.x/api/token_common.mdx b/docs/content/contracts-cairo/2.x/api/token_common.mdx new file mode 100644 index 00000000..3be2e45f --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/token_common.mdx @@ -0,0 +1,469 @@ +--- +title: Common (Token) +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This module provides extensions and utilities that are common to multiple token standards. + +## Interfaces + +### [](#IERC2981)`IERC2981` [toc] [#IERC2981] + + + +```rust +use openzeppelin_token::common::erc2981::interface::IERC2981; +``` + +[SRC5 ID](./introspection#ISRC5) + +```text +0x2d3414e45a8700c29f119a54b9f11dca0e29e06ddcb214018fc37340e165ed6 +``` + +Interface of the ERC2981 standard as defined in [EIP-2981](https://eips.ethereum.org/EIPS/eip-2981). + +Functions + +- [`royalty_info(token_id, sale_price)`](#IERC2981-royalty_info) + +#### [](#IERC2981-Functions)Functions [!toc] + + +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of exchange. The royalty amount is denominated and must be paid in that same unit of exchange. + + +### [](#IERC2981Info)`IERC2981Info` [toc] [#IERC2981Info] + + + +```rust +use openzeppelin_token::common::erc2981::interface::IERC2981Info; +``` + +Interface providing external read functions for discovering the state of ERC2981 component. + +Functions + +- [`default_royalty()`](#IERC2981Info-default_royalty) +- [`token_royalty(token_id)`](#IERC2981Info-token_royalty) + +#### [](#IERC2981Info-Functions)Functions [!toc] + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Returns the royalty information specific to a token. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + +### [](#IERC2981Admin)`IERC2981Admin` [toc] [#IERC2981Admin] + + + +```rust +use openzeppelin_token::common::erc2981::interface::IERC2981Admin; +``` + +Interface providing external admin functions for managing the settings of ERC2981 component. + +Functions + +- [`set_default_royalty(receiver, fee_numerator)`](#IERC2981Admin-set_default_royalty) +- [`delete_default_royalty()`](#IERC2981Admin-delete_default_royalty) +- [`set_token_royalty(token_id, receiver, fee_numerator)`](#IERC2981Admin-set_token_royalty) +- [`reset_token_royalty(token_id)`](#IERC2981Admin-reset_token_royalty) + +#### [](#IERC2981Admin-Functions)Functions [!toc] + + +Sets the royalty information that all ids in this contract will default to. + + + +Sets the default royalty percentage and receiver to zero. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + + + +Resets royalty information for the token id back to unset. + + +## [](#erc2981)ERC2981 + +### [](#ERC2981Component)`ERC2981Component` [toc] [#ERC2981Component] + + + +```rust +use openzeppelin_token::common::erc2981::ERC2981Component; +``` + +ERC2981 component extending [IERC2981](#IERC2981). + +[Immutable Component Config](../components#immutable-config) + +constants + +- [`FEE_DENOMINATOR`](#ERC2981Component-IC-FEE_DENOMINATOR) + +functions + +- [`validate()`](#ERC2981Component-IC-validate) + +Embeddable Implementations + +ERC2981Impl + +- [`royalty_info(self, token_id, sale_price)`](#ERC2981Component-royalty_info) + +ERC2981InfoImpl + +- [`default_royalty(self)`](#ERC2981InfoImpl-default_royalty) +- [`token_royalty(self, token_id)`](#ERC2981InfoImpl-token_royalty) + +ERC2981AdminOwnableImpl + +- [`set_default_royalty(self, receiver, fee_numerator)`](#ERC2981AdminOwnableImpl-set_default_royalty) +- [`delete_default_royalty(self)`](#ERC2981AdminOwnableImpl-delete_default_royalty) +- [`set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981AdminOwnableImpl-set_token_royalty) +- [`reset_token_royalty(self, token_id)`](#ERC2981AdminOwnableImpl-reset_token_royalty) + +ERC2981AdminAccessControlImpl + +- [`set_default_royalty(self, receiver, fee_numerator)`](#ERC2981AdminAccessControlImpl-set_default_royalty) +- [`delete_default_royalty(self)`](#ERC2981AdminAccessControlImpl-delete_default_royalty) +- [`set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981AdminAccessControlImpl-set_token_royalty) +- [`reset_token_royalty(self, token_id)`](#ERC2981AdminAccessControlImpl-reset_token_royalty) + +Internal implementations + +InternalImpl + +- [`initializer(self, default_receiver, default_royalty_fraction)`](#ERC2981Component-initializer) +- [`_default_royalty(self)`](#ERC2981Component-_default_royalty) +- [`_set_default_royalty(self, receiver, fee_numerator)`](#ERC2981Component-_set_default_royalty) +- [`_delete_default_royalty(self)`](#ERC2981Component-_delete_default_royalty) +- [`_token_royalty(self, token_id)`](#ERC2981Component-_token_royalty) +- [`_set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981Component-_set_token_royalty) +- [`_reset_token_royalty(self, token_id)`](#ERC2981Component-_reset_token_royalty) + +#### [](#ERC2981Component-Immutable-Config)Immutable Config constants [!toc] + + +The denominator with which to interpret the fee set in `_set_token_royalty` and `_set_default_royalty` as a fraction of the sale price. + + + +Validates the given implementation of the contract's configuration. + +Requirements: + +- `FEE_DENOMINATOR` must be greater than 0. + +This function is called by the contract's initializer. + + +#### [](#ERC2981Component-Embeddable-functions)Embeddable functions [!toc] + + +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The amount of royalty payment. + + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Returns the royalty information specific to a token. If no specific royalty information is set for the token, the default is returned. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + +#### [](#ERC2981Component-ERC2981AdminOwnableImpl)ERC2981AdminOwnableImpl [!toc] + +Provides admin functions for managing royalty settings that are restricted to be called only by the contract's owner. Requires the contract to implement [OwnableComponent](./access#OwnableComponent). + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- The caller is the contract owner. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + +Requirements: + +- The caller is the contract owner. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- The caller is the contract owner. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + +Requirements: + +- The caller is the contract owner. + + +#### [](#ERC2981Component-ERC2981AdminAccessControlImpl)ERC2981AdminAccessControlImpl [!toc] + +Provides admin functions for managing royalty settings that require `ROYALTY_ADMIN_ROLE` to be granted to the caller. Requires the contract to implement [AccessControlComponent](./access#AccessControlComponent). + + +Role for the admin responsible for managing royalty settings. + + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. + + +#### [](#ERC2981Component-Internal-functions)Internal functions [!toc] + + +Initializes the contract by setting the default royalty and registering the supported interface. + +Requirements: + +- `default_receiver` cannot be the zero address. +- `default_royalty_fraction` cannot be greater than the fee denominator. +- The fee denominator must be greater than 0. + +The fee denominator is set by the contract using the [Immutable Component Config](../components#immutable-config). + + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + diff --git a/docs/content/contracts-cairo/2.x/api/udc.mdx b/docs/content/contracts-cairo/2.x/api/udc.mdx new file mode 100644 index 00000000..28bc000a --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/udc.mdx @@ -0,0 +1,85 @@ +--- +title: Universal Deployer +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +Reference of the Universal Deployer Contract (UDC) interface and preset. + +## Interfaces + +### [](#IUniversalDeployer)`IUniversalDeployer` [toc] [#IUniversalDeployer] + + + +```rust +use openzeppelin_utils::interfaces::IUniversalDeployer; +``` + +Functions + +- [`deploy_contract(class_hash, salt, not_from_zero, calldata)`](#IUniversalDeployer-deploy_contract) + +Events + +- [`ContractDeployed(address, deployer, not_from_zero, class_hash, calldata, salt)`](#IUniversalDeployer-ContractDeployed) + +#### [](#IUniversalDeployer-Functions)Functions [!toc] + + +Deploys a contract through the Universal Deployer Contract. + + +#### [](#IUniversalDeployer-Events)Events [!toc] + + +Emitted when `deployer` deploys a contract through the Universal Deployer Contract. + + +## [](#presets)Presets + +### [](#UniversalDeployer)`UniversalDeployer` [toc] [#UniversalDeployer] + + + +```rust +use openzeppelin_presets::UniversalDeployer; +``` + +The standard Universal Deployer Contract. + +[Sierra class hash](../presets) + +```text +{{UniversalDeployerClassHash}} +``` + +Embedded Implementations + +UniversalDeployerImpl + +- [`deploy_contract(self, address, deployer, not_from_zero, class_hash, calldata, salt)`](#UniversalDeployer-deploy_contract) + +#### [](#UniversalDeployer-External-functions)External functions [!toc] + + +Deploys a contract through the Universal Deployer Contract. + +When `not_from_zero` is `true`, `salt` is hashed with the caller address and the modified salt is passed to the inner `deploy_syscall`. This type of deployment is [origin-dependent](../udc#origin-dependent). + +When `not_from_zero` is `false`, the deployment type is [origin-independent](../udc#origin-independent). + +Emits an [ContractDeployed](#IUniversalDeployer-ContractDeployed) event. + diff --git a/docs/content/contracts-cairo/2.x/api/upgrades.mdx b/docs/content/contracts-cairo/2.x/api/upgrades.mdx new file mode 100644 index 00000000..493a1408 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/upgrades.mdx @@ -0,0 +1,128 @@ +--- +title: Upgrades +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides interfaces and utilities related to upgradeability. + +## Interfaces + +### [](#IUpgradeable)`IUpgradeable` [toc] [#IUpgradeable] + + + +```rust +use openzeppelin_upgrades::interface::IUpgradeable; +``` + +Interface of an upgradeable contract. + +Functions + +- [`upgrade(new_class_hash)`](#IUpgradeable-upgrade) + +#### [](#IUpgradeable-Functions)Functions [!toc] + + +Upgrades the contract code by updating its [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/). + +This function is usually protected by an [Access Control](../access) mechanism. + + +### [](#IUpgradeAndCall)`IUpgradeAndCall` [toc] [#IUpgradeAndCall] + + + +```rust +use openzeppelin_upgrades::interface::IUpgradeAndCall; +``` + +Interface for an upgradeable contract that couples an upgrade with a function call in the upgraded context. + +Functions + +- [`upgrade_and_call(new_class_hash, selector, calldata)`](#IUpgradeAndCall-upgrade_and_call) + +#### [](#IUpgradeAndCall-Functions)Functions [!toc] + + +Upgrades the contract code by updating its [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/) and calls `selector` with the upgraded context. + +This function is usually protected by an [Access Control](../access) mechanism. + + +## [](#core)Core + +### [](#UpgradeableComponent)`UpgradeableComponent` [toc] [#UpgradeableComponent] + + + +```rust +use openzeppelin_upgrades::upgradeable::UpgradeableComponent; +``` + +Upgradeable component. + +Internal Implementations + +InternalImpl + +- [`upgrade(self, new_class_hash)`](#UpgradeableComponent-upgrade) +- [`upgrade_and_call(self, new_class_hash, selector, calldata)`](#UpgradeableComponent-upgrade_and_call) + +Events + +- [`Upgraded(class_hash)`](#UpgradeableComponent-Upgraded) + +#### [](#UpgradeableComponent-Internal-Functions)Internal Functions [!toc] + + +Upgrades the contract by updating the contract [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/). + +Requirements: + +- `new_class_hash` must be different from zero. + +Emits an [Upgraded](#UpgradeableComponent-Upgraded) event. + + + +Replaces the contract's class hash with `new_class_hash` and then calls `selector` from the upgraded context. This function returns the unwrapped `call_contract_syscall` return value(s), if available, of the `selector` call. + +Requirements: + +- `new_class_hash` must be different from zero. + +The function call comes from the upgraded contract itself and not the account. + +A similar behavior to `upgrade_and_call` can also be achieved with a list of calls from an account since the [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) account standard supports multicall. An account can execute a list of calls with [upgrade](#IUpgradeable-upgrade) being the first element in the list and the extra function call as the second. With this approach, the calls will execute from the account's context and can't be front-ran. + +Emits an [Upgraded](#UpgradeableComponent-Upgraded) event. + + +#### [](#UpgradeableComponent-Events)Events [!toc] + + +Emitted when the [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/) is upgraded. + diff --git a/docs/content/contracts-cairo/2.x/api/utilities.mdx b/docs/content/contracts-cairo/2.x/api/utilities.mdx new file mode 100644 index 00000000..7aef39e7 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/api/utilities.mdx @@ -0,0 +1,381 @@ +--- +title: Utilities +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides miscellaneous components and libraries containing utility functions to handle common tasks. + +## Core + +### `utils` [toc] [#utils] + + + +```rust +use openzeppelin_utils; +``` + +Module containing core utilities of the library. + +Members + +Inner modules + +- [`cryptography`](#utils-cryptography) +- [`deployments`](#utils-deployments) +- [`math`](#utils-math) +- [`contract_clock`](#utils-contract_clock) +- [`serde`](#utils-serde) + +#### Inner modules [!toc] [#utils-Inner-Modules] + + +See [`openzeppelin_utils::cryptography`](#cryptography). + + + +See [`openzeppelin_utils::deployments`](#deployments). + + + +See [`openzeppelin_utils::math`](#math). + + + +See [`openzeppelin_utils::contract_clock`](#contract_clock). + + + +See [`openzeppelin_utils::serde`](#serde). + + +### `cryptography` [toc] [#cryptography] + + + +```rust +use openzeppelin_utils::cryptography; +``` + +Module containing utilities related to cryptography. + +Members + +Inner modules + +- [`nonces`](#cryptography-nonces) +- [`snip12`](#cryptography-snip12) + +#### Inner modules [!toc] [#cryptography-Inner-Modules] + + +See [`openzeppelin_utils::cryptography::nonces::NoncesComponent`](#NoncesComponent). + + + +See [`openzeppelin_utils::cryptography::snip12`](#snip12). + + +### `deployments` [toc] [#deployments] + + + +```rust +use openzeppelin_utils::deployments; +``` + +Module containing utility functions for calculating contract addresses through [deploy\_syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy) and the [Universal Deployer Contract](../udc) (UDC). + +Members + +Structs + +- [`DeployerInfo(caller_address, udc_address)`](#deployments-DeployerInfo) + +Functions + +- [`calculate_contract_address_from_deploy_syscall(salt, class_hash, constructor_calldata, deployer_address)`](#deployments-calculate_contract_address_from_deploy_syscall) +- [`compute_hash_on_elements(data)`](#deployments-compute_hash_on_elements) +- [`calculate_contract_address_from_udc(salt, class_hash, constructor_calldata, deployer_info)`](#deployments-calculate_contract_address_from_udc) + +#### Structs [!toc] [#deployments-Structs] + + +Struct containing arguments necessary in [utils::calculate\_contract\_address\_from\_udc](#deployments-calculate_contract_address_from_udc) for origin-dependent deployment calculations. + + +#### Functions [!toc] [#deployments-Functions] + + +Returns the contract address when passing the given arguments to [deploy\_syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy). + + + +Creates a Pedersen hash chain with the elements of `data` and returns the finalized hash. + + + +Returns the calculated contract address for UDC deployments. + +Origin-independent deployments (deployed from zero) should pass `Option::None` as `deployer_info`. + +Origin-dependent deployments hash `salt` with `caller_address` (member of [DeployerInfo](#deployments-DeployerInfo)) and pass the hashed salt to the inner [deploy\_syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy) as the `contract_address_salt` argument. + + +### `math` [toc] [#math] + + + +```rust +use openzeppelin_utils::math; +``` + +Module containing math utilities. + +Members + +Functions + +- [`average(a, b)`](#math-average) + +#### Functions [!toc] [#math-Functions] + + +Returns the average of two unsigned integers. The result is rounded down. + +`T` is a generic value matching different numeric implementations. + + +### `contract_clock` [toc] [#contract_clock] + + +```rust +use openzeppelin_utils::contract_clock; +``` + +Module providing a trait for the [EIP-6372](https://eips.ethereum.org/EIPS/eip-6372) standard along with default clock implementations based on either block number or block timestamp. + +Traits + +- [`ERC6372Clock`](#ERC6372Clock) + +Implementations + +- [`ERC6372BlockNumberClock`](#contract_clock-ERC6372BlockNumberClock) +- [`ERC6372TimestampClock`](#contract_clock-ERC6372TimestampClock) + +#### `ERC6372Clock` [toc] [#ERC6372Clock] + + +```rust +use openzeppelin_utils::contract_clock::ERC6372Clock; +``` + +A trait for the [EIP-6372](https://eips.ethereum.org/EIPS/eip-6372) standard that allows flexible internal clock implementation — based on block timestamp, block number, or a custom logic. + +Functions + +- [`clock()`](#ERC6372Clock-clock) +- [`CLOCK_MODE()`](#ERC6372Clock-CLOCK_MODE) + +#### Functions [!toc] [#ERC6372Clock-Functions] + + +Returns the current timepoint determined by the contract's operational mode, intended for use in time-sensitive logic. + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the contract is operating in. + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + +#### Implementations [!toc] [#contract_clock-Impls] + + +Implementation of the `ERC6372Clock` trait that uses the block number as its clock reference. + + + +Implementation of the `ERC6372Clock` trait that uses the block timestamp as its clock reference. + + +### `serde` [toc] [#serde] + + + +```rust +use openzeppelin_utils::serde; +``` + +Module containing utilities related to serialization and deserialization of Cairo data structures. + +Members + +Traits + +- [`SerializedAppend`](#serde-SerializedAppend) + +#### Traits [!toc] [#serde-Traits] + + +Importing this trait allows the ability to append a serialized representation of a Cairo data structure already implementing the `Serde` trait to a `felt252` buffer. + +Usage example: + +```rust +use openzeppelin_utils::serde::SerializedAppend; +use starknet::ContractAddress; + +fn to_calldata(recipient: ContractAddress, amount: u256) -> Array { + let mut calldata = array![]; + calldata.append_serde(recipient); + calldata.append_serde(amount); + calldata +} +``` + +Note that the `append_serde` method is automatically available for arrays of felts, and it accepts any data structure that implements the `Serde` trait. + + +## Cryptography [#cryptography-toc] + +### `NoncesComponent` [toc] [#NoncesComponent] + + + +```rust +use openzeppelin_utils::cryptography::nonces::NoncesComponent; +``` + +This component provides a simple mechanism for handling incremental nonces for a set of addresses. It is commonly used to prevent replay attacks when contracts accept signatures as input. + +Embeddable Implementations + +NoncesImpl + +- [`nonces(self, owner)`](#NoncesComponent-nonces) + +Internal Implementations + +InternalImpl + +- [`use_nonce(self, owner)`](#NoncesComponent-use_nonce) +- [`use_checked_nonce(self, owner, nonce)`](#NoncesComponent-use_checked_nonce) + +#### Embeddable functions [!toc] [#NoncesComponent-Embeddable-Functions] + + +Returns the next unused nonce for an `owner`. + + +#### Internal functions [!toc] [#NoncesComponent-Internal-Functions] + + +Consumes a nonce, returns the current value, and increments nonce. + +For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be decremented or reset. This guarantees that the nonce never overflows. + + + +Same as `use_nonce` but checking that `nonce` is the next valid one for `owner`. + + +### `snip12` [toc] [#snip12] + + + +```rust +use openzeppelin_utils::snip12; +``` + +Supports on-chain generation of message hashes compliant with [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md). + +For a full walkthrough on how to use this module, see the [SNIP12 and Typed Messages](../guides/snip12) guide. diff --git a/docs/content/contracts-cairo/2.x/backwards-compatibility.mdx b/docs/content/contracts-cairo/2.x/backwards-compatibility.mdx new file mode 100644 index 00000000..3c63bfcd --- /dev/null +++ b/docs/content/contracts-cairo/2.x/backwards-compatibility.mdx @@ -0,0 +1,35 @@ +--- +title: Backwards Compatibility +--- + +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. + +Bear in mind that while releasing versions, we treat minors as majors and patches as minors, in accordance with semantic versioning. This means that `v2.1.0` could be adding features to `v2.0.0`, while `v3.0.0` would be considered a breaking release. + +## 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. + +### 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. + +In the case of breaking "upgrade compatibility", an entry to the changelog will be added listing those breaking changes. + +## Storage layout + +Patch updates will always preserve storage layout compatibility, and after `1.0.0` minors will too. 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. + +## Cairo version + +The minimum Cairo version required to compile the contracts will remain unchanged for patch updates, but it may change for minors. diff --git a/docs/content/contracts-cairo/2.x/components.mdx b/docs/content/contracts-cairo/2.x/components.mdx new file mode 100644 index 00000000..5266792a --- /dev/null +++ b/docs/content/contracts-cairo/2.x/components.mdx @@ -0,0 +1,667 @@ +--- +title: Components +--- + +The following documentation provides reasoning and examples on how to use Contracts for Cairo components. + +Starknet components are separate modules that contain storage, events, and implementations that can be integrated into a contract. +Components themselves cannot be declared or deployed. +Another way to think of components is that they are abstract modules that must be instantiated. + +[shamans_post]: https://community.starknet.io/t/cairo-components/101136#components-1 +[cairo_book]: https://book.cairo-lang.org/ch103-02-00-composability-and-components.html + + +For more information on the construction and design of Starknet components, see the [Starknet Shamans post][shamans_post] and the [Cairo book][cairo_book]. + + +## Building a contract + +### Setup + +The contract should first import the component and declare it with the `component!` macro: + +```rust +#[starknet::contract] +mod MyContract { + // Import the component + use openzeppelin_security::InitializableComponent; + + // Declare the component + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); +} +``` + +The `path` argument should be the imported component itself (in this case, [InitializableComponent](./security#initializable)). +The `storage` and `event` arguments are the variable names that will be set in the `Storage` struct and `Event` enum, respectively. +Note that even if the component doesn’t define any events, the compiler will still create an empty event enum inside the component module. + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + initializable: InitializableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + InitializableEvent: InitializableComponent::Event + } +} +``` + +The `#[substorage(v0)]` attribute must be included for each component in the `Storage` trait. +This allows the contract to have indirect access to the component’s storage. +See [Accessing component storage](#accessing-component-storage) for more on this. + +The `#[flat]` attribute for events in the `Event` enum, however, is not required. +For component events, the first key in the event log is the component ID. +Flattening the component event removes it, leaving the event ID as the first key. + +### Implementations + +Components come with granular implementations of different interfaces. +This allows contracts to integrate only the implementations that they’ll use and avoid unnecessary bloat. +Integrating an implementation looks like this: + +```rust +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + (...) + + // Gives the contract access to the implementation methods + impl InitializableImpl = + InitializableComponent::InitializableImpl; +} +``` + +Defining an `impl` gives the contract access to the methods within the implementation from the component. +For example, `is_initialized` is defined in the `InitializableImpl`. +A function on the contract level can expose it like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + (...) + + impl InitializableImpl = + InitializableComponent::InitializableImpl; + + #[external(v0)] + fn is_initialized(ref self: ContractState) -> bool { + self.initializable.is_initialized() + } +} +``` + +While there’s nothing wrong with manually exposing methods like in the previous example, this process can be tedious for implementations with many methods. +Fortunately, a contract can embed implementations which will expose all of the methods of the implementation. +To embed an implementation, add the `#[abi(embed_v0)]` attribute above the `impl`: + +```rust +#[starknet::contract] +mod MyContract { + (...) + + // This attribute exposes the methods of the `impl` + #[abi(embed_v0)] + impl InitializableImpl = + InitializableComponent::InitializableImpl; +} +``` + +`InitializableImpl` defines the `is_initialized` method in the component. +By adding the embed attribute, `is_initialized` becomes a contract entrypoint for `MyContract`. + + +Embeddable implementations, when available in this library’s components, are segregated from the internal component implementation which makes it easier to safely expose. +Components also separate granular implementations from [mixin](#mixins) implementations. +The API documentation design reflects these groupings. +See [ERC20Component](/contracts-cairo/2.x/api/erc20#erc20component) as an example which includes: + +* **Embeddable Mixin Implementation** +* **Embeddable Implementations** +* **Internal Implementations** +* **Events** + + + +### Mixins + +Mixins are impls made of a combination of smaller, more specific impls. +While separating components into granular implementations offers flexibility, +integrating components with many implementations can appear crowded especially if the contract uses all of them. +Mixins simplify this by allowing contracts to embed groups of implementations with a single directive. + +Compare the following code blocks to see the benefit of using a mixin when creating an account contract. + +#### Account without mixin + +```rust +component!(path: AccountComponent, storage: account, event: AccountEvent); +component!(path: SRC5Component, storage: src5, event: SRC5Event); + +#[abi(embed_v0)] +impl SRC6Impl = AccountComponent::SRC6Impl; +#[abi(embed_v0)] +impl DeclarerImpl = AccountComponent::DeclarerImpl; +#[abi(embed_v0)] +impl DeployableImpl = AccountComponent::DeployableImpl; +#[abi(embed_v0)] +impl PublicKeyImpl = AccountComponent::PublicKeyImpl; +#[abi(embed_v0)] +impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; +#[abi(embed_v0)] +impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; +impl AccountInternalImpl = AccountComponent::InternalImpl; + +#[abi(embed_v0)] +impl SRC5Impl = SRC5Component::SRC5Impl; +``` + +#### Account with mixin + +```rust +component!(path: AccountComponent, storage: account, event: AccountEvent); +component!(path: SRC5Component, storage: src5, event: SRC5Event); + +#[abi(embed_v0)] +impl AccountMixinImpl = AccountComponent::AccountMixinImpl; +impl AccountInternalImpl = AccountComponent::InternalImpl; +``` + +The rest of the setup for the contract, however, does not change. +This means that component dependencies must still be included in the `Storage` struct and `Event` enum. +Here’s a full example of an account contract that embeds the `AccountMixinImpl`: + +```rust +#[starknet::contract] +mod Account { + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // This embeds all of the methods from the many AccountComponent implementations + // and also includes `supports_interface` from `SRC5Impl` + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +``` + +### Initializers + + +Failing to use a component’s `initializer` can result in irreparable contract deployments. + +Always read the API Reference documentation for each integrated component. + + + +Some components require some sort of setup upon construction. +Usually, this would be a job for a constructor; however, components themselves cannot implement constructors. +Components instead offer ``initializer``s within their `InternalImpl` to call from the contract’s constructor. +Let’s look at how a contract would integrate [OwnableComponent](/contracts-cairo/2.x/api/access#OwnableComponent): + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_access::ownable::OwnableComponent; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + // Instantiate `InternalImpl` to give the contract access to the `initializer` + impl InternalImpl = OwnableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + // Invoke ownable's `initializer` + self.ownable.initializer(owner); + } +} +``` + +### Immutable Config + +While initializers help set up the component’s initial state, some require configuration that may be defined +as constants, saving gas by avoiding the necessity of reading from storage each time the variable needs to be used. The +Immutable Component Config pattern helps with this matter by allowing the implementing contract to define a set of +constants declared in the component, customizing its functionality. + + +The Immutable Component Config standard is defined in the SRC-107. + + +Here’s an example of how to use the Immutable Component Config pattern with the [ERC2981Component](/contracts-cairo/2.x/api/token_common#erc2981component): + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::common::erc2981::ERC2981Component; + use starknet::contract_address_const; + + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // Instantiate `InternalImpl` to give the contract access to the `initializer` + impl InternalImpl = ERC2981Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc2981: ERC2981Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC2981Event: ERC2981Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + // Define the immutable config + pub impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig { + const FEE_DENOMINATOR: u128 = 10_000; + } + + #[constructor] + fn constructor(ref self: ContractState) { + let default_receiver = contract_address_const::<'RECEIVER'>(); + let default_royalty_fraction = 1000; + // Invoke erc2981's `initializer` + self.erc2981.initializer(default_receiver, default_royalty_fraction); + } +} +``` + +#### Default config + +Sometimes, components implementing the Immutable Component Config pattern provide a default configuration that can be +directly used without implementing the `ImmutableConfig` trait locally. When provided, this implementation will be named +`DefaultConfig` and will be available in the same module containing the component, as a sibling. + +In the following example, the `DefaultConfig` trait is used to define the `FEE_DENOMINATOR` config constant. + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_introspection::src5::SRC5Component; + // Bring the DefaultConfig trait into scope + use openzeppelin_token::common::erc2981::{ERC2981Component, DefaultConfig}; + use starknet::contract_address_const; + + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // Instantiate `InternalImpl` to give the contract access to the `initializer` + impl InternalImpl = ERC2981Component::InternalImpl; + + #[storage] + struct Storage { + (...) + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + (...) + } + + #[constructor] + fn constructor(ref self: ContractState) { + let default_receiver = contract_address_const::<'RECEIVER'>(); + let default_royalty_fraction = 1000; + // Invoke erc2981's `initializer` + self.erc2981.initializer(default_receiver, default_royalty_fraction); + } +} +``` + +#### `validate` function + +The `ImmutableConfig` trait may also include a `validate` function with a default implementation, which +asserts that the configuration is correct, and must not be overridden by the implementing contract. For more information +on how to use this function, refer to the [validate section of the SRC-107](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-107.md#validate-function). + +### Dependencies + +Some components include dependencies of other components. +Contracts that integrate components with dependencies must also include the component dependency. +For instance, [AccessControlComponent](/contracts-cairo/2.x/api/access#accesscontrolcomponent) depends on [SRC5Component](/contracts-cairo/2.x/api/introspection#src5component). +Creating a contract with `AccessControlComponent` should look like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + #[abi(embed_v0)] + impl AccessControlCamelImpl = + AccessControlComponent::AccessControlCamelImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + (...) +} +``` + +## Customization + + +Customizing implementations and accessing component storage can potentially corrupt the state, bypass security checks, and undermine the component logic. + +**Exercise extreme caution**. See [Security](#security). + + + +### Hooks + +Hooks are entrypoints to the business logic of a token component that are accessible at the contract level. +This allows contracts to insert additional behaviors before and/or after token transfers (including mints and burns). +Prior to hooks, extending functionality required contracts to create [custom implementations](#custom-implementations). + +All token components include a generic hooks trait that include empty default functions. +When creating a token contract, the using contract must create an implementation of the hooks trait. +Suppose an ERC20 contract wanted to include Pausable functionality on token transfers. +The following snippet leverages the `before_update` hook to include this behavior. + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_security::pausable::PausableComponent::InternalTrait; + use openzeppelin_security::pausable::PausableComponent; + use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + // Create the hooks implementation + impl ERC20HooksImpl of ERC20Component::ERC20HooksTrait { + // Occurs before token transfers + fn before_update( + ref self: ERC20Component::ComponentState, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + // Access local state from component state + let contract_state = self.get_contract(); + // Call function from integrated component + contract_state.pausable.assert_not_paused(); + } + + // Omitting the `after_update` hook because the default behavior + // is already implemented in the trait + } + + (...) +} +``` + +Notice that the `self` parameter expects a component state type. +Instead of passing the component state, the using contract’s state can be passed which simplifies the syntax. +The hook then moves the scope up with the Cairo-generated `get_contract` through the `HasComponent` trait (as illustrated with ERC20Component in this example). +From here, the hook can access the using contract’s integrated components, storage, and implementations. + +Be advised that even if a token contract does not require hooks, the hooks trait must still be implemented. +The using contract may instantiate an empty impl of the trait; +however, the Contracts for Cairo library already provides the instantiated impl to abstract this away from contracts. +The using contract just needs to bring the implementation into scope like this: + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + + (...) +} +``` + + +For a more in-depth guide on hooks, see [Extending Cairo Contracts with Hooks](https://fleming-andrew.medium.com/extending-cairo-contracts-with-hooks-c3ca21d1d6b8). + + +### Custom implementations + +There are instances where a contract requires different or amended behaviors from a component implementation. +In these scenarios, a contract must create a custom implementation of the interface. +Let’s break down a pausable ERC20 contract to see what that looks like. +Here’s the setup: + +```rust +#[starknet::contract] +mod ERC20Pausable { + use openzeppelin_security::pausable::PausableComponent; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + // Import the ERC20 interfaces to create custom implementations + use openzeppelin_token::erc20::interface::{IERC20, IERC20CamelOnly}; + use starknet::ContractAddress; + + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + // `ERC20MetadataImpl` can keep the embed directive because the implementation + // will not change + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + // Do not add the embed directive to these implementations because + // these will be customized + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) +} +``` + +The first thing to notice is that the contract imports the interfaces of the implementations that will be customized. +These will be used in the next code example. + +Next, the contract includes the [ERC20Component](/contracts-cairo/2.x/api/erc20#erc20component) implementations; however, `ERC20Impl` and `ERC20CamelOnlyImpl` are **not** embedded. +Instead, we want to expose our custom implementation of an interface. +The following example shows the pausable logic integrated into the ERC20 implementations: + +```rust +#[starknet::contract] +mod ERC20Pausable { + (...) + + // Custom ERC20 implementation + #[abi(embed_v0)] + impl CustomERC20Impl of IERC20 { + fn transfer( + ref self: ContractState, recipient: ContractAddress, amount: u256 + ) -> bool { + // Add the custom logic + self.pausable.assert_not_paused(); + // Add the original implementation method from `IERC20Impl` + self.erc20.transfer(recipient, amount) + } + + fn total_supply(self: @ContractState) -> u256 { + // This method's behavior does not change from the component + // implementation, but this method must still be defined. + // Simply add the original implementation method from `IERC20Impl` + self.erc20.total_supply() + } + + (...) + } + + // Custom ERC20CamelOnly implementation + #[abi(embed_v0)] + impl CustomERC20CamelOnlyImpl of IERC20CamelOnly { + fn totalSupply(self: @ContractState) -> u256 { + self.erc20.total_supply() + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + self.erc20.balance_of(account) + } + + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + self.pausable.assert_not_paused(); + self.erc20.transfer_from(sender, recipient, amount) + } + } +} +``` + +Notice that in the `CustomERC20Impl`, the `transfer` method integrates `pausable.assert_not_paused` as well as `erc20.transfer` from `PausableImpl` and `ERC20Impl` respectively. +This is why the contract defined the `ERC20Impl` from the component in the previous example. + +Creating a custom implementation of an interface must define **all** methods from that interface. +This is true even if the behavior of a method does not change from the component implementation (as `total_supply` exemplifies in this example). + +### Accessing component storage + +There may be cases where the contract must read or write to an integrated component’s storage. +To do so, use the same syntax as calling an implementation method except replace the name of the method with the storage variable like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + initializable: InitializableComponent::Storage + } + + (...) + + fn write_to_comp_storage(ref self: ContractState) { + self.initializable.Initializable_initialized.write(true); + } + + fn read_from_comp_storage(self: @ContractState) -> bool { + self.initializable.Initializable_initialized.read() + } +} +``` + +## Security + +The maintainers of OpenZeppelin Contracts for Cairo are mainly concerned with the correctness and security of the code as published in the library. + +Customizing implementations and manipulating the component state may break some important assumptions and introduce vulnerabilities. +While we try to ensure the components remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. +Any and all customizations to the component logic should be carefully reviewed and checked against the source code of the component they are customizing so as to fully understand their impact and guarantee their security. diff --git a/docs/content/contracts-cairo/2.x/erc1155.mdx b/docs/content/contracts-cairo/2.x/erc1155.mdx new file mode 100644 index 00000000..d3e05494 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/erc1155.mdx @@ -0,0 +1,239 @@ +--- +title: ERC1155 +--- + +The ERC1155 multi-token standard is a specification for [fungibility-agnostic](https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens) token contracts. +The ERC1155 library implements an approximation of [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) in Cairo for StarkNet. + +## Multi Token Standard + +The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This +is why its [balance_of](/contracts-cairo/2.x/api/erc1155#IERC1155-balance_of) function differs from ERC20’s and ERC777’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 ERC721 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 ERC721 [balance_of](/contracts-cairo/2.x/api/erc721#IERC721-balance_of) function refers to how many different tokens an account +has, not how many of each. On the other hand, in ERC1155 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 ERC1155 token contract can hold the entire system state, reducing deployment +costs and complexity. + +## Usage + +Using Contracts for Cairo, constructing an ERC1155 contract requires integrating both `ERC1155Component` and `SRC5Component`. +The contract should also set up the constructor to initialize the token’s URI and interface support. +Here’s an example of a basic contract: + +```rust +#[starknet::contract] +mod MyERC1155 { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 Mixin + #[abi(embed_v0)] + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + token_uri: ByteArray, + recipient: ContractAddress, + token_ids: Span, + values: Span + ) { + self.erc1155.initializer(token_uri); + self + .erc1155 + .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); + } +} +``` + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC1155Component](/contracts-cairo/2.x/api/erc1155#ERC1155Component). +The interface includes the [IERC1155](/contracts-cairo/2.x/api/erc1155#IERC1155) standard interface and the optional [IERC1155MetadataURI](/contracts-cairo/2.x/api/erc1155#IERC1155MetadataURI) interface together with [ISRC5](/contracts-cairo/2.x/api/introspection#ISRC5). + +To support older token deployments, as mentioned in [Dual interfaces](./guides/interfaces-and-dispatchers#dual-interfaces), the component also includes implementations of the interface written in camelCase. + +```rust +#[starknet::interface] +pub trait ERC1155ABI { + // IERC1155 + fn balance_of(account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(operator: ContractAddress, approved: bool); + + // IERC1155MetadataURI + fn uri(token_id: u256) -> ByteArray; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IERC1155Camel + fn balanceOf(account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch( + accounts: Span, tokenIds: Span + ) -> Span; + fn safeTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ); + fn safeBatchTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(operator: ContractAddress, approved: bool); +} +``` + +## ERC1155 Compatibility + +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard but some differences can still be found, such as: + +* The optional `data` argument in both `safe_transfer_from` and `safe_batch_transfer_from` is implemented as `Span`. +* `IERC1155Receiver` compliant contracts must implement SRC5 and register the `IERC1155Receiver` interface ID. +* `IERC1155Receiver::on_erc1155_received` must return that interface ID on success. + +## 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, [balance_of_batch](/contracts-cairo/2.x/api/erc1155#IERC1155-balance_of_batch) and [safe_batch_transfer_from](/contracts-cairo/2.x/api/erc1155#IERC1155-safe_batch_transfer_from), that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. We also have [safe_transfer_from](/contracts-cairo/2.x/api/erc1155#IERC1155-safe_transfer_from) for non-batch operations. + +In the spirit of the standard, we’ve also included batch operations in the non-standard functions, such as +[batch_mint_with_acceptance_check](/contracts-cairo/2.x/api/erc1155#ERC1155Component-batch_mint_with_acceptance_check). + + +While [safe_transfer_from](/contracts-cairo/2.x/api/erc1155#IERC1155-safe_transfer_from) and [safe_batch_transfer_from](/contracts-cairo/2.x/api/erc1155#IERC1155-safe_batch_transfer_from) prevent loss by checking the receiver can handle the +tokens, this yields execution to the receiver which can result in a [reentrant call](./security#reentrancy-guard). + + +## Receiving tokens + +In order to be sure a non-account contract can safely accept ERC1155 tokens, the contract must implement the `IERC1155Receiver` interface. +The recipient contract must also implement the [SRC5](./introspection#src5) interface which supports interface introspection. + +### IERC1155Receiver + +```rust +#[starknet::interface] +pub trait IERC1155Receiver { + fn on_erc1155_received( + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252; + fn on_erc1155_batch_received( + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252; +} +``` + +Implementing the `IERC1155Receiver` interface exposes the [on_erc1155_received](/contracts-cairo/2.x/api/erc1155#IERC1155Receiver-on_erc1155_received) and [on_erc1155_batch_received](/contracts-cairo/2.x/api/erc1155#IERC1155Receiver-on_erc1155_batch_received) methods. +When [safe_transfer_from](/contracts-cairo/2.x/api/erc1155#IERC1155-safe_transfer_from) and [safe_batch_transfer_from](/contracts-cairo/2.x/api/erc1155#IERC1155-safe_batch_transfer_from) are called, they invoke the recipient contract’s `on_erc1155_received` or `on_erc1155_batch_received` methods respectively which **must** return the [IERC1155Receiver interface ID](/contracts-cairo/2.x/api/erc1155#IERC1155Receiver). +Otherwise, the transaction will fail. + + +For information on how to calculate interface IDs, see [Computing the interface ID](./introspection#computing-the-interface-id). + + +### Creating a token receiver contract + +The Contracts for Cairo ERC1155ReceiverComponent already returns the correct interface ID for safe token transfers. +To integrate the `IERC1155Receiver` interface into a contract, simply include the ABI embed directive to the implementations and add the `initializer` in the contract’s constructor. +Here’s an example of a simple token receiver contract: + +```rust +#[starknet::contract] +mod MyTokenReceiver { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc1155::ERC1155ReceiverComponent; + use starknet::ContractAddress; + + component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155Receiver Mixin + #[abi(embed_v0)] + impl ERC1155ReceiverMixinImpl = ERC1155ReceiverComponent::ERC1155ReceiverMixinImpl; + impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155_receiver: ERC1155ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc1155_receiver.initializer(); + } +} +``` diff --git a/docs/content/contracts-cairo/2.x/erc20.mdx b/docs/content/contracts-cairo/2.x/erc20.mdx new file mode 100644 index 00000000..601d4902 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/erc20.mdx @@ -0,0 +1,252 @@ +--- +title: ERC20 +--- + +The ERC20 token standard is a specification for [fungible tokens](https://docs.openzeppelin.com/contracts/4.x/tokens#different-kinds-of-tokens), a type of token where all the units are exactly equal to each other. +`token::erc20::ERC20Component` provides an approximation of [EIP-20](https://eips.ethereum.org/EIPS/eip-20) in Cairo for Starknet. + + +Prior to [Contracts v0.7.0](https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v0.7.0), ERC20 contracts store and read `decimals` from storage; however, this implementation returns a static `18`. +If upgrading an older ERC20 contract that has a decimals value other than `18`, the upgraded contract **must** use a custom `decimals` implementation. +See the [Customizing decimals](#customizing-decimals) guide. + + +## Usage + +Using Contracts for Cairo, constructing an ERC20 contract requires setting up the constructor and instantiating the token implementation. +Here’s what that looks like: + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + initial_supply: u256, + recipient: ContractAddress + ) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + } +} +``` + +`MyToken` integrates both the `ERC20Impl` and `ERC20MetadataImpl` with the embed directive which marks the implementations as external in the contract. +While the `ERC20MetadataImpl` is optional, it’s generally recommended to include it because the vast majority of ERC20 tokens provide the metadata methods. +The above example also includes the `ERC20InternalImpl` instance. +This allows the contract’s constructor to initialize the contract and create an initial supply of tokens. + + +For a more complete guide on ERC20 token mechanisms, see [Creating ERC20 Supply](./guides/erc20-supply). + + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC20Component](/contracts-cairo/2.x/api/erc20#ERC20Component). +The interface includes the [IERC20](/contracts-cairo/2.x/api/erc20#IERC20) standard interface as well as the optional [IERC20Metadata](/contracts-cairo/2.x/api/erc20#IERC20Metadata). + +To support older token deployments, as mentioned in [Dual interfaces](./guides/interfaces-and-dispatchers#dual-interfaces), the component also includes an implementation of the interface written in camelCase. + +```rust +#[starknet::interface] +pub trait ERC20ABI { + // IERC20 + fn total_supply() -> u256; + fn balance_of(account: ContractAddress) -> u256; + fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn decimals() -> u8; + + // IERC20Camel + fn totalSupply() -> u256; + fn balanceOf(account: ContractAddress) -> u256; + fn transferFrom( + sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} +``` + +## ERC20 compatibility + +Although Starknet is not EVM compatible, this component aims to be as close as possible to the ERC20 token standard. +Some notable differences, however, can still be found, such as: + +* The `ByteArray` type is used to represent strings in Cairo. +* The component offers a [dual interface](./guides/interfaces-and-dispatchers#dual-interfaces) which supports both snake_case and camelCase methods, as opposed to just camelCase in Solidity. +* `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error. +* Function selectors are calculated differently between [Cairo](https://github.com/starkware-libs/cairo/blob/7dd34f6c57b7baf5cd5a30c15e00af39cb26f7e1/crates/cairo-lang-starknet/src/contract.rs#L39-L48) and [Solidity](https://solidity-by-example.org/function-selector/). + +## Customizing decimals + +Cairo, like Solidity, does not support [floating-point numbers](https://en.wikipedia.org//wiki/Floating-point_arithmetic). +To get around this limitation, ERC20 token contracts may offer a `decimals` field which communicates to outside interfaces (wallets, exchanges, etc.) how the token should be displayed. +For instance, suppose a token had a `decimals` value of `3` and the total token supply was `1234`. +An outside interface would display the token supply as `1.234`. +In the actual contract, however, the supply would still be the integer `1234`. +In other words, **the decimals field in no way changes the actual arithmetic** because all operations are still performed on integers. + +Most contracts use `18` decimals and this was even proposed to be compulsory (see the [EIP discussion](https://github.com/ethereum/EIPs/issues/724)). + +### The static approach (SRC-107) + +The Contracts for Cairo `ERC20` component leverages SRC-107 to allow for a static and configurable number of decimals. +To use the default `18` decimals, you can use the `DefaultConfig` implementation by just importing it: + +```rust +#[starknet::contract] +mod MyToken { + // Importing the DefaultConfig implementation would make decimals 18 by default. + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) +} +``` + +To customize this value, you can implement the ImmutableConfig trait locally in the contract. +The following example shows how to set the decimals to `6`: + +```rust +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) + + // Custom implementation of the ERC20Component ImmutableConfig. + impl ERC20ImmutableConfig of ERC20Component::ImmutableConfig { + const DECIMALS: u8 = 6; + } +} +``` + +### The storage approach + +For more complex scenarios, such as a factory deploying multiple tokens with differing values for decimals, a flexible solution might be appropriate. + + +Note that we are not using the MixinImpl or the DefaultConfig in this case, since we need to customize the IERC20Metadata implementation. + + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::interface as interface; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + // The decimals value is stored locally + decimals: u8, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, decimals: u8, initial_supply: u256, recipient: ContractAddress, + ) { + // Call the internal function that writes decimals to storage + self._set_decimals(decimals); + + // Initialize ERC20 + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + } + + #[abi(embed_v0)] + impl ERC20CustomMetadataImpl of interface::IERC20Metadata { + fn name(self: @ContractState) -> ByteArray { + self.erc20.ERC20_name.read() + } + + fn symbol(self: @ContractState) -> ByteArray { + self.erc20.ERC20_symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + self.decimals.read() + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _set_decimals(ref self: ContractState, decimals: u8) { + self.decimals.write(decimals); + } + } +} +``` + +This contract expects a `decimals` argument in the constructor and uses an internal function to write the decimals to storage. +Note that the `decimals` state variable must be defined in the contract’s storage because this variable does not exist in the component offered by OpenZeppelin Contracts for Cairo. +It’s important to include a custom ERC20 metadata implementation and NOT use the Contracts for Cairo `ERC20MetadataImpl` in this specific case since the `decimals` method will always return `18`. diff --git a/docs/content/contracts-cairo/2.x/erc4626.mdx b/docs/content/contracts-cairo/2.x/erc4626.mdx new file mode 100644 index 00000000..e22f162e --- /dev/null +++ b/docs/content/contracts-cairo/2.x/erc4626.mdx @@ -0,0 +1,433 @@ +--- +title: ERC4626 +--- + +[ERC4626](https://eips.ethereum.org/EIPS/eip-4626) is an extension of [ERC20](./erc20) 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 component of ERC4626 which is designed to allow developers to easily re-configure the vault’s behavior, using traits and hooks, while +staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault. + +## Security concern: Inflation attack + +### Visualizing the vault + +In exchange for the assets deposited into an ERC4626 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. + +![Exchange rates in linear scale](/erc4626-rate-linear.png) + +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. + +![Exchange rates in logarithmic scale](/erc4626-rate-loglog.png) + +In such a representation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. + +![More exchange rates in logarithmic scale](/erc4626-rate-loglogext.png) + +### 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 less than 1 share worth of tokens, you will receive 0 shares, effectively making 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. + +![Depositing assets](/erc4626-deposit.png) + +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%. + +![Minting shares](/erc4626-mint.png) + +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. + +![Inflation attack without protection](/erc4626-attack.png) + +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: + +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | Assets | Shares | Rate | +| --- | --- | --- | --- | +| initial | $0$ | $0$ | - | +| after attacker's deposit | $a_0$ | $a_0$ | $1$ | +| after attacker's donation | $a_0 + a_1$ | $a_0$ | $\frac{a_0}{a_1 + a_0}$ | + +This means a deposit of $u$ will give this number of shares: + +```math +\frac{u \times a_0}{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 + +```math +\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0} +``` + +Using $a_0 = 1$ and $a_1 = u$ is enough. So the attacker only needs $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 $\frac{u}{n}$, the user needs to suffer rounding of a similar fraction, which means the user must receive at most $n$ shares. This results in: + +```math +\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 $n$ times less powerful (in how much it is stealing) and costs $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 [YieldBox](https://github.com/boringcrypto/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 to perform an attack. + +Following the previous math definitions, we have: + +* $\delta$ the vault offset +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | Assets | Shares | Rate | +| --- | --- | --- | --- | +| initial | $1$ | $10^\delta$ | $10^\delta$ | +| after attacker's deposit | $1+a_0$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | +| after attacker's donation | $1+a_0+a_1$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | + +One important thing to note is that the attacker only owns a fraction $\frac{a_0}{1 + a_0}$ of the shares, so when doing the donation, he will only be able +to recover that fraction $\frac{a_1 * a_0}{1 + a_0}$ of the donation. The remaining $\frac{a_1}{1+a_0}$ are captured by the vault. + +```math +\mathit{loss} = \frac{a_1}{1+a_0} +``` + +When the user deposits $u$, he receives + +```math +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 + +```math +10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1 +``` + +```math +\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0} +``` + +```math +\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0} +``` + +```math +\iff 10^\delta \times u \le \mathit{loss} +``` + +* If the offset is 0, the attacker 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. + +![Inflation attack without offset=3](/erc4626-attack-3a.png) +$\delta = 3$, $a_0 = 1$, $a_1 = 10^5$ + +![Inflation attack without offset=3 and an attacker deposit that limits its losses](/erc4626-attack-3b.png) +$\delta = 3$, $a_0 = 100$, $a_1 = 10^5$ + +![Inflation attack without offset=6](/erc4626-attack-6.png) +$\delta = 6$, $a_0 = 1$, $a_1 = 10^5$ + +## Usage + +### Custom behavior: Adding fees to the vault + +In ERC4626 vaults, fees can be captured during deposit/mint and/or withdraw/redeem operations. It is essential to remain +compliant with the ERC4626 requirements regarding the preview functions. Fees are calculated through the [FeeConfigTrait](/contracts-cairo/2.x/api/erc20#ERC4626Component-FeeConfigTrait) +implementation. By default, the ERC4626 component charges no fees. If this is the desired behavior, you can use the default +[ERC4626DefaultNoFees](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20/extensions/erc4626/erc4626.cairo#L899) implementation. + + +Starting from v3.0.0, fees can be charged in either assets or shares. Prior versions only supported fees taken in assets. +See the updated [FeeConfigTrait](/contracts-cairo/2.x/api/erc20#ERC4626Component-FeeConfigTrait) and implementation examples in [ERC4626 mocks](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo). + + +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 `preview_deposit(100)`. +Similarly, `preview_mint` 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 the user receives. +Any fees should be added to the quote (in shares) performed by `preview_withdraw`. + +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 taken in assets on deposits/withdrawals and in shares on mints/redemptions +proportional to the deposited/withdrawn amount can be implemented: + +```rust +#[starknet::contract] +#[with_components(./erc20, ERC4626)] +pub mod ERC4626Fees { + use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::{Fee, FeeConfigTrait}; + use openzeppelin_token::erc20::extensions::erc4626::{ + DefaultConfig, ERC4626DefaultNoLimits, ERC4626SelfAssetsManagement, + }; + use openzeppelin_token::erc20::{DefaultConfig as ERC20DefaultConfig, ERC20HooksEmptyImpl}; + use openzeppelin_utils::math; + use openzeppelin_utils::math::Rounding; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + const _BASIS_POINT_SCALE: u256 = 10_000; + const FEE_TRANSFER_FAILED: felt252 = 0x1; + + // ERC4626 + #[abi(embed_v0)] + impl ERC4626ComponentImpl = ERC4626Component::ERC4626Impl; + // ERC4626MetadataImpl is a custom impl of IERC20Metadata + #[abi(embed_v0)] + impl ERC4626MetadataImpl = ERC4626Component::ERC4626MetadataImpl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + + #[storage] + pub struct Storage { + pub entry_fee_basis_point_value: u256, + pub entry_fee_recipient: ContractAddress, + pub exit_fee_basis_point_value: u256, + pub exit_fee_recipient: ContractAddress, + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + underlying_asset: ContractAddress, + initial_supply: u256, + recipient: ContractAddress, + entry_fee: u256, + entry_treasury: ContractAddress, + exit_fee: u256, + exit_treasury: ContractAddress, + ) { + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + self.erc4626.initializer(underlying_asset); + + self.entry_fee_basis_point_value.write(entry_fee); + self.entry_fee_recipient.write(entry_treasury); + self.exit_fee_basis_point_value.write(exit_fee); + self.exit_fee_recipient.write(exit_treasury); + } + + /// Hooks + impl ERC4626HooksImpl of ERC4626Component::ERC4626HooksTrait { + fn after_deposit( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + if let Option::Some(fee) = fee { + let mut contract_state = self.get_contract_mut(); + let fee_recipient = contract_state.entry_fee_recipient.read(); + match fee { + Fee::Assets(fee) => { + let asset_address = contract_state.asset(); + let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; + assert( + asset_dispatcher.transfer(fee_recipient, fee), FEE_TRANSFER_FAILED, + ); + }, + Fee::Shares(fee) => contract_state.erc20.mint(fee_recipient, fee), + }; + } + } + + fn before_withdraw( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + if let Option::Some(fee) = fee { + let mut contract_state = self.get_contract_mut(); + let fee_recipient = contract_state.exit_fee_recipient.read(); + match fee { + Fee::Assets(fee) => { + let asset_address = contract_state.asset(); + let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; + assert( + asset_dispatcher.transfer(fee_recipient, fee), FEE_TRANSFER_FAILED, + ); + }, + Fee::Shares(fee) => { + if caller != owner { + contract_state.erc20._spend_allowance(owner, caller, fee); + } + contract_state.erc20._transfer(owner, fee_recipient, fee); + }, + }; + } + } + } + + /// Calculate fees + impl FeeConfigImpl of FeeConfigTrait { + fn calculate_deposit_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_total(assets, contract_state.entry_fee_basis_point_value.read()); + Option::Some(Fee::Assets(fee)) + } + + fn calculate_mint_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_raw(shares, contract_state.entry_fee_basis_point_value.read()); + Option::Some(Fee::Shares(fee)) + } + + fn calculate_withdraw_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_raw(assets, contract_state.exit_fee_basis_point_value.read()); + Option::Some(Fee::Assets(fee)) + } + + fn calculate_redeem_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_total(shares, contract_state.exit_fee_basis_point_value.read()); + Option::Some(Fee::Shares(fee)) + } + } + + /// Calculates the fees that should be added to an amount `assets` that does not already + /// include fees. + /// Used in IERC4626::mint and IERC4626::withdraw operations. + fn fee_on_raw( + assets: u256, + fee_basis_points: u256, + ) -> u256 { + math::u256_mul_div(assets, fee_basis_points, _BASIS_POINT_SCALE, Rounding::Ceil) + } + + /// Calculates the fee part of an amount `assets` that already includes fees. + /// Used in IERC4626::deposit and IERC4626::redeem operations. + fn fee_on_total( + assets: u256, + fee_basis_points: u256, + ) -> u256 { + math::u256_mul_div( + assets, fee_basis_points, fee_basis_points + _BASIS_POINT_SCALE, Rounding::Ceil, + ) + } +} +``` + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC4626Component](/contracts-cairo/2.x/api/erc20#ERC4626Component). +The full interface includes the [IERC4626](/contracts-cairo/2.x/api/erc20#IERC4626), [IERC20](/contracts-cairo/2.x/api/erc20#IERC20), and [IERC20Metadata](/contracts-cairo/2.x/api/erc20#IERC20Metadata) interfaces. +Note that implementing the IERC20Metadata interface is a requirement of IERC4626. + +```rust +#[starknet::interface] +pub trait ERC4626ABI { + // IERC4626 + fn asset() -> ContractAddress; + fn total_assets() -> u256; + fn convert_to_shares(assets: u256) -> u256; + fn convert_to_assets(shares: u256) -> u256; + fn max_deposit(receiver: ContractAddress) -> u256; + fn preview_deposit(assets: u256) -> u256; + fn deposit(assets: u256, receiver: ContractAddress) -> u256; + fn max_mint(receiver: ContractAddress) -> u256; + fn preview_mint(shares: u256) -> u256; + fn mint(shares: u256, receiver: ContractAddress) -> u256; + fn max_withdraw(owner: ContractAddress) -> u256; + fn preview_withdraw(assets: u256) -> u256; + fn withdraw( + assets: u256, receiver: ContractAddress, owner: ContractAddress, + ) -> u256; + fn max_redeem(owner: ContractAddress) -> u256; + fn preview_redeem(shares: u256) -> u256; + fn redeem( + shares: u256, receiver: ContractAddress, owner: ContractAddress, + ) -> u256; + + // IERC20 + fn total_supply() -> u256; + fn balance_of(account: ContractAddress) -> u256; + fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + fn approve(spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn decimals() -> u8; + + // IERC20CamelOnly + fn totalSupply() -> u256; + fn balanceOf(account: ContractAddress) -> u256; + fn transferFrom( + sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; +} +``` diff --git a/docs/content/contracts-cairo/2.x/erc721.mdx b/docs/content/contracts-cairo/2.x/erc721.mdx new file mode 100644 index 00000000..cee0e614 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/erc721.mdx @@ -0,0 +1,213 @@ +--- +title: ERC721 +--- + +The ERC721 token standard is a specification for [non-fungible tokens](https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens), or more colloquially: NFTs. +`token::erc721::ERC721Component` provides an approximation of [EIP-721](https://eips.ethereum.org/EIPS/eip-721) in Cairo for Starknet. + +## Usage + +Using Contracts for Cairo, constructing an ERC721 contract requires integrating both `ERC721Component` and `SRC5Component`. +The contract should also set up the constructor to initialize the token’s name, symbol, and interface support. +Here’s an example of a basic contract: + +```rust +#[starknet::contract] +mod MyNFT { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 Mixin + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + recipient: ContractAddress + ) { + let name = "MyNFT"; + let symbol = "NFT"; + let base_uri = "https://api.example.com/v1/"; + let token_id = 1; + + self.erc721.initializer(name, symbol, base_uri); + self.erc721.mint(recipient, token_id); + } +} +``` + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC721Component](/contracts-cairo/2.x/api/erc721#ERC721Component). +The interface includes the [IERC721](/contracts-cairo/2.x/api/erc721#IERC721) standard interface and the optional [IERC721Metadata](/contracts-cairo/2.x/api/erc721#IERC721Metadata) interface. + +To support older token deployments, as mentioned in [Dual interfaces](./guides/interfaces-and-dispatchers#dual-interfaces), the component also includes implementations of the interface written in camelCase. + +```rust +#[starknet::interface] +pub trait ERC721ABI { + // IERC721 + fn balance_of(account: ContractAddress) -> u256; + fn owner_of(token_id: u256) -> ContractAddress; + fn safe_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn transfer_from(from: ContractAddress, to: ContractAddress, token_id: u256); + fn approve(to: ContractAddress, token_id: u256); + fn set_approval_for_all(operator: ContractAddress, approved: bool); + fn get_approved(token_id: u256) -> ContractAddress; + fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721Metadata + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn token_uri(token_id: u256) -> ByteArray; + + // IERC721CamelOnly + fn balanceOf(account: ContractAddress) -> u256; + fn ownerOf(tokenId: u256) -> ContractAddress; + fn safeTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn transferFrom(from: ContractAddress, to: ContractAddress, tokenId: u256); + fn setApprovalForAll(operator: ContractAddress, approved: bool); + fn getApproved(tokenId: u256) -> ContractAddress; + fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721MetadataCamelOnly + fn tokenURI(tokenId: u256) -> ByteArray; +} +``` + +## ERC721 compatibility + +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard. +This implementation does, however, include a few notable differences such as: + +* ``interface_id``s are hardcoded and initialized by the constructor. +The hardcoded values derive from Starknet’s selector calculations. +See the [Introspection](./introspection) docs. +* `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. +The difference between both functions consists of accepting `data` as an argument. +`safe_transfer_from` by default accepts the `data` argument which is interpreted as `Span`. +If `data` is not used, simply pass an empty array. +* ERC721 utilizes [SRC5](./introspection#src5) to declare and query interface support on Starknet as opposed to Ethereum’s [EIP165](https://eips.ethereum.org/EIPS/eip-165). +The design for `SRC5` is similar to OpenZeppelin’s [ERC165Storage](https://docs.openzeppelin.com/contracts/4.xapi/utils#ERC165Storage). +* `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). + +## Token transfers + +This library includes [transfer_from](/contracts-cairo/2.x/api/erc721#IERC721-transfer_from) and [safe_transfer_from](/contracts-cairo/2.x/api/erc721#IERC721-safe_transfer_from) to transfer NFTs. +If using `transfer_from`, **the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.** +The `safe_transfer_from` method mitigates this risk by querying the recipient contract’s interface support. + + +Usage of `safe_transfer_from` prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. + + +## Receiving tokens + +In order to be sure a non-account contract can safely accept ERC721 tokens, said contract must implement the `IERC721Receiver` interface. +The recipient contract must also implement the [SRC5](./introspection#src5) interface which, as described earlier, supports interface introspection. + +### IERC721Receiver + +```rust +#[starknet::interface] +pub trait IERC721Receiver { + fn on_erc721_received( + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252; +} +``` + +Implementing the `IERC721Receiver` interface exposes the [on_erc721_received](/contracts-cairo/2.x/api/erc721#IERC721Receiver-on_erc721_received) method. +When safe methods such as [safe_transfer_from](/contracts-cairo/2.x/api/erc721#IERC721-safe_transfer_from) and [safe_mint](/contracts-cairo/2.x/api/erc721#ERC721-safe_mint) are called, they invoke the recipient contract’s `on_erc721_received` method which **must** return the [IERC721Receiver interface ID](/contracts-cairo/2.x/api/erc721#IERC721Receiver). +Otherwise, the transaction will fail. + + +For information on how to calculate interface IDs, see [Computing the interface ID](./introspection#computing-the-interface-id). + + +### Creating a token receiver contract + +The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. +To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract’s constructor. +Here’s an example of a simple token receiver contract: + +```rust +#[starknet::contract] +mod MyTokenReceiver { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::ERC721ReceiverComponent; + use starknet::ContractAddress; + + component!(path: ERC721ReceiverComponent, storage: erc721_receiver, event: ERC721ReceiverEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721Receiver Mixin + #[abi(embed_v0)] + impl ERC721ReceiverMixinImpl = ERC721ReceiverComponent::ERC721ReceiverMixinImpl; + impl ERC721ReceiverInternalImpl = ERC721ReceiverComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721_receiver: ERC721ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721ReceiverEvent: ERC721ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721_receiver.initializer(); + } +} +``` + +## Storing ERC721 URIs + +Token URIs were previously stored as single field elements prior to Cairo v0.2.5. +ERC721Component now stores only the base URI as a `ByteArray` and the full token URI is returned as the `ByteArray` concatenation of the base URI and the token ID through the [token_uri](/contracts-cairo/2.x/api/erc721#IERC721Metadata-token_uri) method. +This design mirrors OpenZeppelin’s default [Solidity implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/932fddf69a699a9a80fd2396fd1a2ab91cdda123/contracts/token/ERC721/ERC721.sol#L85-L93) for ERC721. diff --git a/docs/content/contracts-cairo/2.x/finance.mdx b/docs/content/contracts-cairo/2.x/finance.mdx new file mode 100644 index 00000000..50e91a4e --- /dev/null +++ b/docs/content/contracts-cairo/2.x/finance.mdx @@ -0,0 +1,216 @@ +--- +title: Finance +--- + +This module includes primitives for financial systems. + +## Vesting component + +The [VestingComponent](/contracts-cairo/2.x/api/finance#VestingComponent) manages the gradual release of ERC-20 tokens to a designated beneficiary based on a predefined vesting schedule. +The implementing contract must implement the [OwnableComponent](/contracts-cairo/2.x/api/access#OwnableComponent), where the contract owner is regarded as the vesting beneficiary. +This structure allows ownership rights of both the contract and the vested tokens to be assigned and transferred. + + +Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning of the vesting period. +As a result, if the vesting has already started, a portion of the newly transferred tokens may become immediately releasable. + + + +By setting the duration to 0, it’s possible to configure this contract to behave like an asset timelock that holds tokens +for a beneficiary until a specified date. + + +### Vesting schedule + +The [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) trait defines the logic for calculating the vested amount based on a given timestamp. This +logic is not part of the [VestingComponent](/contracts-cairo/2.x/api/finance#VestingComponent), so any contract implementing the [VestingComponent](/contracts-cairo/2.x/api/finance#VestingComponent) must provide its own +implementation of the [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) trait. + + +There’s a ready-made implementation of the [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) trait available named [LinearVestingSchedule](/contracts-cairo/2.x/api/finance#LinearVestingSchedule). +It incorporates a cliff period by returning 0 vested amount until the cliff ends. After the cliff, the vested amount +is calculated as directly proportional to the time elapsed since the beginning of the vesting schedule. + + +### Usage + +The contract must integrate [VestingComponent](/contracts-cairo/2.x/api/finance#VestingComponent) and [OwnableComponent](/contracts-cairo/2.x/api/access#OwnableComponent) as dependencies. The contract’s constructor +should initialize both components. Core vesting parameters, such as `beneficiary`, `start`, `duration` +and `cliff_duration`, are passed as arguments to the constructor and set at the time of deployment. + +The implementing contract must provide an implementation of the [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) trait. This can be achieved either by importing +a ready-made [LinearVestingSchedule](/contracts-cairo/2.x/api/finance#LinearVestingSchedule) implementation or by defining a custom one. + +Here’s an example of a simple vesting wallet contract with a [LinearVestingSchedule](/contracts-cairo/2.x/api/finance#LinearVestingSchedule), where the vested amount +is calculated as being directly proportional to the time elapsed since the start of the vesting period. + +```rust +#[starknet::contract] +mod LinearVestingWallet { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_finance::vesting::{VestingComponent, LinearVestingSchedule}; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl VestingImpl = VestingComponent::VestingImpl; + impl VestingInternalImpl = VestingComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + vesting: VestingComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + VestingEvent: VestingComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64 + ) { + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff_duration); + } +} +``` + +A vesting schedule will often follow a custom formula. In such cases, the [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) trait is useful. +To support a custom vesting schedule, the contract must provide an implementation of the +[calculate_vested_amount](/contracts-cairo/2.x/api/finance#VestingComponent-calculate_vested_amount) function based on the desired formula. + + +When using a custom [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) implementation, the [LinearVestingSchedule](/contracts-cairo/2.x/api/finance#LinearVestingSchedule) must be excluded from the imports. + + + +If there are additional parameters required for calculations, which are stored in the contract’s storage, you can access them using `self.get_contract()`. + + +Here’s an example of a vesting wallet contract with a custom [VestingSchedule](/contracts-cairo/2.x/api/finance#VestingComponent-Vesting-Schedule) implementation, where tokens +are vested in a number of steps. + +```rust +#[starknet::contract] +mod StepsVestingWallet { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_finance::vesting::VestingComponent::VestingScheduleTrait; + use openzeppelin_finance::vesting::VestingComponent; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl VestingImpl = VestingComponent::VestingImpl; + impl VestingInternalImpl = VestingComponent::InternalImpl; + + #[storage] + struct Storage { + total_steps: u64, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + vesting: VestingComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + VestingEvent: VestingComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + total_steps: u64, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff: u64, + ) { + self.total_steps.write(total_steps); + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff); + } + + impl VestingSchedule of VestingScheduleTrait { + fn calculate_vested_amount( + self: @VestingComponent::ComponentState, + token: ContractAddress, + total_allocation: u256, + timestamp: u64, + start: u64, + duration: u64, + cliff: u64, + ) -> u256 { + if timestamp < cliff { + 0 + } else if timestamp >= start + duration { + total_allocation + } else { + let total_steps = self.get_contract().total_steps.read(); + let vested_per_step = total_allocation / total_steps.into(); + let step_duration = duration / total_steps; + let current_step = (timestamp - start) / step_duration; + let vested_amount = vested_per_step * current_step.into(); + vested_amount + } + } + } +} +``` + +### Interface + +Here is the full interface of a standard contract implementing the vesting functionality: + +```rust +#[starknet::interface] +pub trait VestingABI { + // IVesting + fn start(self: @TState) -> u64; + fn cliff(self: @TState) -> u64; + fn duration(self: @TState) -> u64; + fn end(self: @TState) -> u64; + fn released(self: @TState, token: ContractAddress) -> u256; + fn releasable(self: @TState, token: ContractAddress) -> u256; + fn vested_amount(self: @TState, token: ContractAddress, timestamp: u64) -> u256; + fn release(ref self: TState, token: ContractAddress) -> u256; + + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); +} +``` diff --git a/docs/content/contracts-cairo/2.x/governance/governor.mdx b/docs/content/contracts-cairo/2.x/governance/governor.mdx new file mode 100644 index 00000000..cfad3946 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/governance/governor.mdx @@ -0,0 +1,443 @@ +--- +title: Governor +--- + +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”. In +OpenZeppelin Contracts for Cairo, we set out to build a modular system of Governor components where different +requirements can be accommodated by implementing specific traits. You will find the most common requirements out of the box, +but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. + +## Usage and setup + +### Token + +The voting power of each account in our governance setup will be determined by an ERC20 or an ERC721 token. The token has +to implement the [VotesComponent](../api/governance#VotesComponent) 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. + +If your project already has a live token that does not include Votes and is not upgradeable, you can wrap it in a +governance token by using a wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. + + +The library currently does not include a wrapper for tokens, but it will be added in a future release. + + + +Currently, the clock mode is fixed to block timestamps, since the Votes component uses the block timestamp to track +checkpoints. We plan to add support for more flexible clock modes in Votes in a future release, allowing to use, for example, +block numbers instead. + + +### Governor + +We will initially build a Governor without a timelock. The core logic is given by the [GovernorComponent](../api/governance#GovernorComponent), 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) the execution mechanism that should be used. + +Each of these aspects is customizable by writing your own extensions, +or more easily choosing one from the library. + +***For 1)*** we will use the GovernorVotes extension, which hooks to an [IVotes](../api/governance#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 the address of the token to be passed as an argument to the initializer. + +***For 2)*** we will use GovernorVotesQuorumFraction. This works together with the [IVotes](../api/governance#IVotes) instance to define the quorum as a +percentage of the total supply at the block when a proposal’s voting power is retrieved. This requires an initializer +parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%. Since the quorum denominator +is 1000 for precision, we initialize the module with a numerator of 40, resulting in a 4% quorum (40/1000 = 0.04 or 4%). + +***For 3)*** we will use GovernorCountingSimple, an extension that offers 3 options to voters: For, Against, and Abstain, +and where only For and Abstain votes are counted towards quorum. + +***For 4)*** we will use GovernorCoreExecution, an extension that allows proposal execution directly through the governor. + + +Another option is GovernorTimelockExecution. An example can be found in the next section. + + +Besides these, we also need an implementation for the GovernorSettingsTrait defining the voting delay, voting period, +and proposal threshold. While we can use the GovernorSettings extension which allows to set these parameters by the +governor itself, we will implement the trait locally in the contract and set the voting delay, voting period, +and proposal threshold as constant values. + +__voting_delay__: How long after a proposal is created should voting power be fixed. A large voting delay gives +users time to unstake tokens if necessary. + +__voting_period__: How long does a proposal remain open to votes. + + +These parameters are specified in the unit defined in the token’s clock, which is for now always timestamps. + + +__proposal_threshold__: This restricts proposal creation to accounts who have enough voting power. + +An implementation of `GovernorComponent::ImmutableConfig` is also required. For the example below, we have used +the `DefaultConfig`. Check the immutable-config guide for more details. + +The last missing step is to add an `SNIP12Metadata` implementation used to retrieve the name and version of the governor. + +```rust +#[starknet::contract] +mod MyGovernor { + use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent::InternalTrait; + use openzeppelin_governance::governor::extensions::{ + GovernorVotesQuorumFractionComponent, GovernorCountingSimpleComponent, + GovernorCoreExecutionComponent, + }; + use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + pub const VOTING_DELAY: u64 = 86400; // 1 day + pub const VOTING_PERIOD: u64 = 604800; // 1 week + pub const PROPOSAL_THRESHOLD: u256 = 10; + pub const QUORUM_NUMERATOR: u256 = 40; // 4% + + component!(path: GovernorComponent, storage: governor, event: GovernorEvent); + component!( + path: GovernorVotesQuorumFractionComponent, + storage: governor_votes, + event: GovernorVotesEvent + ); + component!( + path: GovernorCountingSimpleComponent, + storage: governor_counting_simple, + event: GovernorCountingSimpleEvent + ); + component!( + path: GovernorCoreExecutionComponent, + storage: governor_core_execution, + event: GovernorCoreExecutionEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Governor + #[abi(embed_v0)] + impl GovernorImpl = GovernorComponent::GovernorImpl; + + // Extensions external + #[abi(embed_v0)] + impl QuorumFractionImpl = + GovernorVotesQuorumFractionComponent::QuorumFractionImpl; + + // Extensions internal + impl GovernorQuorumImpl = GovernorVotesQuorumFractionComponent::GovernorQuorum; + impl GovernorVotesImpl = GovernorVotesQuorumFractionComponent::GovernorVotes; + impl GovernorCountingSimpleImpl = + GovernorCountingSimpleComponent::GovernorCounting; + impl GovernorCoreExecutionImpl = + GovernorCoreExecutionComponent::GovernorExecution; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub governor: GovernorComponent::Storage, + #[substorage(v0)] + pub governor_votes: GovernorVotesQuorumFractionComponent::Storage, + #[substorage(v0)] + pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, + #[substorage(v0)] + pub governor_core_execution: GovernorCoreExecutionComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + GovernorEvent: GovernorComponent::Event, + #[flat] + GovernorVotesEvent: GovernorVotesQuorumFractionComponent::Event, + #[flat] + GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, + #[flat] + GovernorCoreExecutionEvent: GovernorCoreExecutionComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, votes_token: ContractAddress) { + self.governor.initializer(); + self.governor_votes.initializer(votes_token, QUORUM_NUMERATOR); + } + + // + // SNIP12 Metadata + // + + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // + // Locally implemented extensions + // + + pub impl GovernorSettings of GovernorComponent::GovernorSettingsTrait { + /// See `GovernorComponent::GovernorSettingsTrait::voting_delay`. + fn voting_delay(self: @GovernorComponent::ComponentState) -> u64 { + VOTING_DELAY + } + + /// See `GovernorComponent::GovernorSettingsTrait::voting_period`. + fn voting_period(self: @GovernorComponent::ComponentState) -> u64 { + VOTING_PERIOD + } + + /// See `GovernorComponent::GovernorSettingsTrait::proposal_threshold`. + fn proposal_threshold(self: @GovernorComponent::ComponentState) -> u256 { + PROPOSAL_THRESHOLD + } + } +} +``` + +### 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](#timelock) in combination with the +GovernorTimelockExecution extension. + + +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. + + +TimelockController uses an [AccessControl](../access#role-based-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 must be granted, +and it MUST be the only proposer (and canceller) 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 is in charge of canceling operations: the Governor instance must be granted this role, +and it MUST be the only canceller in the system. + +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. + +The following example uses the GovernorTimelockExecution extension, together with GovernorSettings, and uses a +fixed quorum value instead of a percentage: + +```rust +#[starknet::contract] +pub mod MyTimelockedGovernor { + use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorSettingsComponent::InternalTrait as GovernorSettingsInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent::InternalTrait as GovernorTimelockExecutionInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorVotesComponent::InternalTrait as GovernorVotesInternalTrait; + use openzeppelin_governance::governor::extensions::{ + GovernorVotesComponent, GovernorSettingsComponent, GovernorCountingSimpleComponent, + GovernorTimelockExecutionComponent + }; + use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + pub const VOTING_DELAY: u64 = 86400; // 1 day + pub const VOTING_PERIOD: u64 = 604800; // 1 week + pub const PROPOSAL_THRESHOLD: u256 = 10; + pub const QUORUM: u256 = 100_000_000; + + component!(path: GovernorComponent, storage: governor, event: GovernorEvent); + component!(path: GovernorVotesComponent, storage: governor_votes, event: GovernorVotesEvent); + component!( + path: GovernorSettingsComponent, storage: governor_settings, event: GovernorSettingsEvent + ); + component!( + path: GovernorCountingSimpleComponent, + storage: governor_counting_simple, + event: GovernorCountingSimpleEvent + ); + component!( + path: GovernorTimelockExecutionComponent, + storage: governor_timelock_execution, + event: GovernorTimelockExecutionEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Governor + #[abi(embed_v0)] + impl GovernorImpl = GovernorComponent::GovernorImpl; + + // Extensions external + #[abi(embed_v0)] + impl VotesTokenImpl = GovernorVotesComponent::VotesTokenImpl; + #[abi(embed_v0)] + impl GovernorSettingsAdminImpl = + GovernorSettingsComponent::GovernorSettingsAdminImpl; + #[abi(embed_v0)] + impl TimelockedImpl = + GovernorTimelockExecutionComponent::TimelockedImpl; + + // Extensions internal + impl GovernorVotesImpl = GovernorVotesComponent::GovernorVotes; + impl GovernorSettingsImpl = GovernorSettingsComponent::GovernorSettings; + impl GovernorCountingSimpleImpl = + GovernorCountingSimpleComponent::GovernorCounting; + impl GovernorTimelockExecutionImpl = + GovernorTimelockExecutionComponent::GovernorExecution; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub governor: GovernorComponent::Storage, + #[substorage(v0)] + pub governor_votes: GovernorVotesComponent::Storage, + #[substorage(v0)] + pub governor_settings: GovernorSettingsComponent::Storage, + #[substorage(v0)] + pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, + #[substorage(v0)] + pub governor_timelock_execution: GovernorTimelockExecutionComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + GovernorEvent: GovernorComponent::Event, + #[flat] + GovernorVotesEvent: GovernorVotesComponent::Event, + #[flat] + GovernorSettingsEvent: GovernorSettingsComponent::Event, + #[flat] + GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, + #[flat] + GovernorTimelockExecutionEvent: GovernorTimelockExecutionComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, votes_token: ContractAddress, timelock_controller: ContractAddress + ) { + self.governor.initializer(); + self.governor_votes.initializer(votes_token); + self.governor_settings.initializer(VOTING_DELAY, VOTING_PERIOD, PROPOSAL_THRESHOLD); + self.governor_timelock_execution.initializer(timelock_controller); + } + + // + // SNIP12 Metadata + // + + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // + // Locally implemented extensions + // + + impl GovernorQuorum of GovernorComponent::GovernorQuorumTrait { + /// See `GovernorComponent::GovernorQuorumTrait::quorum`. + fn quorum(self: @GovernorComponent::ComponentState, timepoint: u64) -> u256 { + QUORUM + } + } +} +``` + +## Interface + +This is the full interface of the `Governor` implementation: +```rust +#[starknet::interface] +pub trait IGovernor { + fn name(self: @TState) -> felt252; + fn version(self: @TState) -> felt252; + fn COUNTING_MODE(self: @TState) -> ByteArray; + fn hash_proposal(self: @TState, calls: Span, description_hash: felt252) -> felt252; + fn state(self: @TState, proposal_id: felt252) -> ProposalState; + fn proposal_threshold(self: @TState) -> u256; + fn proposal_snapshot(self: @TState, proposal_id: felt252) -> u64; + fn proposal_deadline(self: @TState, proposal_id: felt252) -> u64; + fn proposal_proposer(self: @TState, proposal_id: felt252) -> ContractAddress; + fn proposal_eta(self: @TState, proposal_id: felt252) -> u64; + fn proposal_needs_queuing(self: @TState, proposal_id: felt252) -> bool; + fn voting_delay(self: @TState) -> u64; + fn voting_period(self: @TState) -> u64; + fn quorum(self: @TState, timepoint: u64) -> u256; + fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; + fn get_votes_with_params( + self: @TState, account: ContractAddress, timepoint: u64, params: Span + ) -> u256; + fn has_voted(self: @TState, proposal_id: felt252, account: ContractAddress) -> bool; + fn propose(ref self: TState, calls: Span, description: ByteArray) -> felt252; + fn queue(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn execute(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn cancel(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn cast_vote(ref self: TState, proposal_id: felt252, support: u8) -> u256; + fn cast_vote_with_reason( + ref self: TState, proposal_id: felt252, support: u8, reason: ByteArray + ) -> u256; + fn cast_vote_with_reason_and_params( + ref self: TState, + proposal_id: felt252, + support: u8, + reason: ByteArray, + params: Span + ) -> u256; + fn cast_vote_by_sig( + ref self: TState, + proposal_id: felt252, + support: u8, + voter: ContractAddress, + signature: Span + ) -> u256; + fn cast_vote_with_reason_and_params_by_sig( + ref self: TState, + proposal_id: felt252, + support: u8, + voter: ContractAddress, + reason: ByteArray, + params: Span, + signature: Span + ) -> u256; + fn nonces(self: @TState, voter: ContractAddress) -> felt252; + fn relay(ref self: TState, call: Call); +} +``` diff --git a/docs/content/contracts-cairo/2.x/governance/multisig.mdx b/docs/content/contracts-cairo/2.x/governance/multisig.mdx new file mode 100644 index 00000000..c98fdf9d --- /dev/null +++ b/docs/content/contracts-cairo/2.x/governance/multisig.mdx @@ -0,0 +1,150 @@ +--- +title: Multisig +--- + +The Multisig component implements a multi-signature mechanism to enhance the security and +governance of smart contract transactions. It ensures that no single signer can unilaterally +execute critical actions, requiring multiple registered signers to approve and collectively +execute transactions. + +This component is designed to secure operations such as fund management or protocol governance, +where collective decision-making is essential. The Multisig Component is self-administered, +meaning that changes to signers or quorum must be approved through the multisig process itself. + +## Key features + +* **Multi-Signature Security**: transactions must be approved by multiple signers, ensuring +distributed governance. +* **Quorum Enforcement**: defines the minimum number of approvals required for transaction execution. +* **Self-Administration**: all modifications to the component (e.g., adding or removing signers) +must pass through the multisig process. +* **Event Logging**: provides comprehensive event logging for transparency and auditability. + +## Signer management + +The Multisig component introduces the concept of signers and quorum: + +* **Signers**: only registered signers can submit, confirm, revoke, or execute transactions. The Multisig +Component supports adding, removing, or replacing signers. +* **Quorum**: the quorum defines the minimum number of confirmations required to approve a transaction. + + +To prevent unauthorized modifications, only the contract itself can add, remove, or replace signers or change the quorum. +This ensures that all modifications pass through the multisig approval process. + + +## Transaction lifecycle + +The state of a transaction is represented by the `TransactionState` enum and can be retrieved +by calling the `get_transaction_state` function with the transaction’s identifier. + +The identifier of a multisig transaction is a `felt252` value, computed as the Pedersen hash +of the transaction’s calls and salt. It can be computed by invoking the implementing contract’s +`hash_transaction` method for single-call transactions or `hash_transaction_batch` for multi-call +transactions. Submitting a transaction with identical calls and the same salt value a second time +will fail, as transaction identifiers must be unique. To resolve this, use a different salt value +to generate a unique identifier. + +A transaction in the Multisig component follows a specific lifecycle: + +`NotFound` → `Pending` → `Confirmed` → `Executed` + +* **NotFound**: the transaction does not exist. +* **Pending**: the transaction exists but has not reached the required confirmations. +* **Confirmed**: the transaction has reached the quorum but has not yet been executed. +* **Executed**: the transaction has been successfully executed. + +## Usage + +Integrating the Multisig functionality into a contract requires implementing [MultisigComponent](../api/governance#MultisigComponent). +The contract’s constructor should initialize the component with a quorum value and a list of initial signers. + +Here’s an example of a simple wallet contract featuring the Multisig functionality: + +```rust +#[starknet::contract] +mod MultisigWallet { + use openzeppelin_governance::multisig::MultisigComponent; + use starknet::ContractAddress; + + component!(path: MultisigComponent, storage: multisig, event: MultisigEvent); + + #[abi(embed_v0)] + impl MultisigImpl = MultisigComponent::MultisigImpl; + impl MultisigInternalImpl = MultisigComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + multisig: MultisigComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + MultisigEvent: MultisigComponent::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, quorum: u32, signers: Span) { + self.multisig.initializer(quorum, signers); + } +} +``` + +## Interface + +This is the interface of a contract implementing the [MultisigComponent](../api/governance#MultisigComponent): + +```rust +#[starknet::interface] +pub trait MultisigABI { + // Read functions + fn get_quorum(self: @TState) -> u32; + fn is_signer(self: @TState, signer: ContractAddress) -> bool; + fn get_signers(self: @TState) -> Span; + fn is_confirmed(self: @TState, id: TransactionID) -> bool; + fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool; + fn is_executed(self: @TState, id: TransactionID) -> bool; + fn get_submitted_block(self: @TState, id: TransactionID) -> u64; + fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState; + fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32; + fn hash_transaction( + self: @TState, + to: ContractAddress, + selector: felt252, + calldata: Span, + salt: felt252, + ) -> TransactionID; + fn hash_transaction_batch(self: @TState, calls: Span, salt: felt252) -> TransactionID; + + // Write functions + fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span); + fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span); + fn replace_signer( + ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress, + ); + fn change_quorum(ref self: TState, new_quorum: u32); + fn submit_transaction( + ref self: TState, + to: ContractAddress, + selector: felt252, + calldata: Span, + salt: felt252, + ) -> TransactionID; + fn submit_transaction_batch( + ref self: TState, calls: Span, salt: felt252, + ) -> TransactionID; + fn confirm_transaction(ref self: TState, id: TransactionID); + fn revoke_confirmation(ref self: TState, id: TransactionID); + fn execute_transaction( + ref self: TState, + to: ContractAddress, + selector: felt252, + calldata: Span, + salt: felt252, + ); + fn execute_transaction_batch(ref self: TState, calls: Span, salt: felt252); +} +``` diff --git a/docs/content/contracts-cairo/2.x/governance/timelock.mdx b/docs/content/contracts-cairo/2.x/governance/timelock.mdx new file mode 100644 index 00000000..d3404511 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/governance/timelock.mdx @@ -0,0 +1,198 @@ +--- +title: Timelock Controller +--- + +The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. + + +The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. + + +## Operation lifecycle + +The state of an operation is represented by the `OperationState` enum and can be retrieved +by calling the `get_operation_state` function with the operation’s identifier. + +The identifier of an operation is a `felt252` value, computed as the Pedersen hash of the +operation’s call or calls, its predecessor, and salt. It can be computed by invoking the +implementing contract’s `hash_operation` function for single-call operations or +`hash_operation_batch` for multi-call operations. Submitting an operation with identical calls, +predecessor, and the same salt value a second time will fail, as operation identifiers must be +unique. To resolve this, use a different salt value to generate a unique identifier. + +Timelocked operations follow a specific lifecycle: + +`Unset` → `Waiting` → `Ready` → `Done` + +* `Unset`: the operation has not been scheduled or has been canceled. +* `Waiting`: the operation has been scheduled and is pending the scheduled delay. +* `Ready`: the timer has expired, and the operation is eligible for execution. +* `Done`: the operation has been executed. + +## Timelock flow + +### Schedule + +When a proposer calls [schedule](../api/governance#ITimelock-schedule), the `OperationState` moves from `Unset` to `Waiting`. +This starts a timer that must be greater than or equal to the minimum delay. +The timer expires at a timestamp accessible through [get_timestamp](../api/governance#ITimelock-get_timestamp). +Once the timer expires, the `OperationState` automatically moves to the `Ready` state. +At this point, it can be executed. + +### Execute + +By calling [execute](../api/governance#ITimelock-execute), an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor’s operation must be in the `Done` state for this transaction to succeed. + +### Cancel + +The [cancel](../api/governance#ITimelock-cancel) function allows cancellers to cancel any pending operations. +This resets the operation to the `Unset` state. +It is therefore possible for a proposer to re-schedule an operation that has been cancelled. +In this case, the timer restarts when the operation is re-scheduled. + +## Roles + +[TimelockControllerComponent](../api/governance#TimelockControllerComponent) leverages an [AccessControlComponent](../api/access#AccessControlComponent) setup that we need to understand in order to set up roles. + +* `PROPOSER_ROLE` - in charge of queueing operations. +* `CANCELLER_ROLE` - may cancel scheduled operations. +During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. +Therefore, the initial proposers may also cancel operations after they are scheduled. +* `EXECUTOR_ROLE` - in charge of executing already available operations. +* `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. + + +The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. +The latter case may be required to ease a contract’s initial configuration; however, this role should promptly be renounced. + + +Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. +This allows anyone to execute an operation once it’s in the `Ready` OperationState. +To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. + + +Be very careful with enabling open roles as _anyone_ can call the function. + + +## Minimum delay + +The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. +The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. + +After initialization, the only way to change the timelock’s minimum delay is to schedule it and execute it with the same flow as any other operation. + +The minimum delay of a contract is accessible through [get_min_delay](../api/governance#ITimelock-get_min_delay). + +## Usage + +Integrating the timelock into a contract requires integrating [TimelockControllerComponent](../api/governance#TimelockControllerComponent) as well as [SRC5Component](../api/introspection#SRC5Component) and [AccessControlComponent](../api/access#AccessControlComponent) as dependencies. +The contract’s constructor should initialize the timelock which consists of setting the: + +* Proposers and executors. +* Minimum delay between scheduling and executing an operation. +* Optional admin if additional configuration is required. + + +The optional admin should renounce their role once configuration is complete. + + +Here’s an example of a simple timelock contract: + +```rust +#[starknet::contract] +mod TimelockControllerContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_governance::timelock::TimelockControllerComponent; + use openzeppelin_introspection::src5::SRC5Component; + use starknet::ContractAddress; + + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Timelock Mixin + #[abi(embed_v0)] + impl TimelockMixinImpl = + TimelockControllerComponent::TimelockMixinImpl; + impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + access_control: AccessControlComponent::Storage, + #[substorage(v0)] + timelock: TimelockControllerComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + TimelockEvent: TimelockControllerComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + min_delay: u64, + proposers: Span, + executors: Span, + admin: ContractAddress + ) { + self.timelock.initializer(min_delay, proposers, executors, admin); + } +} +``` + +## Interface + +This is the full interface of the TimelockMixinImpl implementation: + +```rust +#[starknet::interface] +pub trait TimelockABI { + // ITimelock + fn is_operation(self: @TState, id: felt252) -> bool; + fn is_operation_pending(self: @TState, id: felt252) -> bool; + fn is_operation_ready(self: @TState, id: felt252) -> bool; + fn is_operation_done(self: @TState, id: felt252) -> bool; + fn get_timestamp(self: @TState, id: felt252) -> u64; + fn get_operation_state(self: @TState, id: felt252) -> OperationState; + fn get_min_delay(self: @TState) -> u64; + fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; + fn hash_operation_batch( + self: @TState, calls: Span, predecessor: felt252, salt: felt252 + ) -> felt252; + fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); + fn schedule_batch( + ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 + ); + fn cancel(ref self: TState, id: felt252); + fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); + fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); + fn update_delay(ref self: TState, new_delay: u64); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IAccessControl + fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(self: @TState, role: felt252) -> felt252; + fn grant_role(ref self: TState, role: felt252, account: ContractAddress); + fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); + fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(self: @TState, role: felt252) -> felt252; + fn grantRole(ref self: TState, role: felt252, account: ContractAddress); + fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); + fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); +} +``` diff --git a/docs/content/contracts-cairo/2.x/governance/votes.mdx b/docs/content/contracts-cairo/2.x/governance/votes.mdx new file mode 100644 index 00000000..44d9e731 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/governance/votes.mdx @@ -0,0 +1,222 @@ +--- +title: Votes +--- + +The [VotesComponent](../api/governance#VotesComponent) provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. + + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + + + +The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the [usage](#usage) section for examples of how to implement this. + + +## Key features + +1. **Delegation**: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the [delegate](../api/governance#VotesComponent-delegate) function directly, or by providing a signature to be used with [delegate_by_sig](../api/governance#VotesComponent-delegate_by_sig). +2. **Historical lookups**: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp.\ +This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. + +## Usage + +When integrating the `VotesComponent`, the [VotingUnitsTrait](../api/governance#VotingUnitsTrait) must be implemented to get the voting units for a given account as a function of the implementing contract.\ +For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated.\ +Additionally, you must implement the [NoncesComponent](../api/utilities#NoncesComponent) and the [SNIP12Metadata](../api/utilities#snip12) trait to enable delegation by signatures. + +Here’s an example of how to structure a simple ERC20Votes contract: + +```rust +#[starknet::contract] +mod ERC20VotesContract { + use openzeppelin_governance::votes::VotesComponent; + use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + // Votes + #[abi(embed_v0)] + impl VotesImpl = VotesComponent::VotesImpl; + impl VotesInternalImpl = VotesComponent::InternalImpl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // Nonces + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc20_votes: VotesComponent::Storage, + #[substorage(v0)] + pub erc20: ERC20Component::Storage, + #[substorage(v0)] + pub nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20VotesEvent: VotesComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + // Required for hash computation. + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // We need to call the `transfer_voting_units` function after + // every mint, burn and transfer. + // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. + impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { + fn after_update( + ref self: ERC20Component::ComponentState, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); + } + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer("MyToken", "MTK"); + } +} +``` + +And here’s an example of how to structure a simple ERC721Votes contract: + +```rust +#[starknet::contract] +pub mod ERC721VotesContract { + use openzeppelin_governance::votes::VotesComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::ERC721Component; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + // Votes + #[abi(embed_v0)] + impl VotesImpl = VotesComponent::VotesImpl; + impl VotesInternalImpl = VotesComponent::InternalImpl; + + // ERC721 + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // Nonces + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc721_votes: VotesComponent::Storage, + #[substorage(v0)] + pub erc721: ERC721Component::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + #[substorage(v0)] + pub nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721VotesEvent: VotesComponent::Event, + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + /// Required for hash computation. + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // We need to call the `transfer_voting_units` function after + // every mint, burn and transfer. + // For this, we use the `before_update` hook of the + //`ERC721Component::ERC721HooksTrait`. + // This hook is called before the transfer is executed. + // This gives us access to the previous owner. + impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut contract_state = self.get_contract_mut(); + + // We use the internal function here since it does not check if the token + // id exists which is necessary for mints + let previous_owner = self._owner_of(token_id); + contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); + } + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721.initializer("MyToken", "MTK", ""); + } +} +``` + +## Interface + +This is the full interface of the `VotesImpl` implementation: + +```rust +#[starknet::interface] +pub trait VotesABI { + // IVotes + fn get_votes(self: @TState, account: ContractAddress) -> u256; + fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; + fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; + fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; + fn delegate(ref self: TState, delegatee: ContractAddress); + fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); + + // INonces + fn nonces(self: @TState, owner: ContractAddress) -> felt252; +} +``` diff --git a/docs/content/contracts-cairo/2.x/guides/deploy-udc.mdx b/docs/content/contracts-cairo/2.x/guides/deploy-udc.mdx new file mode 100644 index 00000000..5e20ac1e --- /dev/null +++ b/docs/content/contracts-cairo/2.x/guides/deploy-udc.mdx @@ -0,0 +1,273 @@ +--- +title: UDC Appchain Deployment +--- + +While the Universal Deployer Contract (UDC) is deployed on Starknet public networks, appchains may need to deploy +their own instance of the UDC for their own use. This guide will walk you through this process while keeping the +same final address on all networks. + +## Prerequisites + +This guide assumes you have: + +* Familiarity with [Scarb](https://docs.swmansion.com/scarb/docs.html) and Starknet development environment. +* A functional account available on the network you’re deploying to. +* Familiarity with the process of declaring contracts through the [declare transaction](https://docs.starknet.io/resources/transactions-reference/#declare_transaction). + + +For declaring contracts on Starknet, you can use the [sncast](https://foundry-rs.github.io/starknet-foundry/starknet/declare.html) tool from the [starknet-foundry](https://foundry-rs.github.io/starknet-foundry/index.html) project. + + +## Note on the UDC final address + +It is important that the Universal Deployer Contract (UDC) in Starknet maintains the same address across all +networks because essential developer tools like **starkli** and **sncast** rely on this address by default when deploying contracts. +These tools are widely used in the Starknet ecosystem to streamline and standardize contract deployment workflows. + +If the UDC address is consistent, developers can write deployment scripts, CI/CD pipelines, and integrations that work seamlessly +across testnets, mainnet, and appchains without needing to update configuration files or handle special cases for each +environment. + +In the following sections, we’ll walk you through the process of deploying the UDC on appchains while keeping the same address, +under one important assumption: **the declared UDC class hash MUST be the same across all networks**. +Different compiler versions may produce different class hashes for the same contract, so you need to make +sure you are using the same compiler version to build the UDC class (and the release profile). + +The latest version of the UDC available in the `openzeppelin_presets` package was compiled with **Cairo v2.11.4** (release profile) and the resulting class hash is `0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8`. + + +If you are using a different compiler version, you need to make sure the class hash is the same as the one above in order to keep the same address across all networks. + + + +To avoid potential issues by using a different compiler version, you can directly import the contract class deployed on Starknet mainnet and declare it on your appchain. At +the time of writing, this is not easily achievable with the `sncast` tool, but you can leverage `[starkli](https://book.starkli.rs/declaring-classes)` to do it. + +Quick reference: + +```bash +starkli class-by-hash --parse \ + 0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8 \ + --network mainnet \ + > udc.json +``` + +This will output a `udc.json` file that you can use to declare the UDC on your appchain. + +```bash +starkli declare udc.json --rpc +``` + + + +## Madara Appchains + +[Madara](https://github.com/madara-alliance/madara/blob/main/README.md) is a popular Starknet node implementation that has a friendly and robust interface for building appchains. If +you are using it for this purpose, you are probably familiar with the [Madara Bootstrapper](https://github.com/madara-alliance/madara/tree/main/bootstrapper#readme), which already declares and +deploys a few contracts for you when you create a new appchain, including accounts and the UDC. + +However, since the UDC was migrated to a new version in June 2025, it’s possible that the appchain was created before +this change, meaning the UDC on the appchain is an older version. If that’s the case, you can follow the steps below to +deploy the new UDC. + +### 1. Declare and deploy the Bootstrapper + +In the Starknet ecosystem, contracts need to be declared before they can be deployed, and deployments can only happen +either via the `deploy_syscall`, or using a `deploy_account` transaction. The latter would require adding account +functionality to the UDC, which is not optimal, so we’ll use the `deploy_syscall`, which requires having an account +with this functionality enabled. + + +Madara declares an account with this functionality enabled as part of the bootstrapping process. You may be able to +use that implementation directly to skip this step. + + +#### Bootstrapper Contract + +The bootstrapper contract is a simple contract that declares the UDC and allows for its deployment via the `deploy_syscall`. +You can find a reference implementation below: + + +This reference implementation targets Cairo v2.11.4. If you are using a different version of Cairo, you may need to update the code to match your compiler version. + + +```rust +#[starknet::contract(account)] +mod UniversalDeployerBootstrapper { + use core::num::traits::Zero; + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::deployments::calculate_contract_address_from_deploy_syscall; + use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // + // Account features (deployable, declarer, and invoker) + // + + #[abi(embed_v0)] + pub(crate) impl DeployableImpl = + AccountComponent::DeployableImpl; + #[abi(embed_v0)] + impl DeclarerImpl = AccountComponent::DeclarerImpl; + #[abi(embed_v0)] + impl SRC6Impl = AccountComponent::SRC6Impl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub account: AccountComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub(crate) enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + pub fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } + + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn deploy_udc(ref self: ContractState, udc_class_hash: ClassHash) { + self.account.assert_only_self(); + starknet::syscalls::deploy_syscall(udc_class_hash, 0, array![].span(), true) + .unwrap_syscall(); + } + + #[external(v0)] + fn get_udc_address(ref self: ContractState, udc_class_hash: ClassHash) -> ContractAddress { + calculate_contract_address_from_deploy_syscall( + 0, udc_class_hash, array![].span(), Zero::zero(), + ) + } + +} +``` + +#### Deploying the Bootstrapper + +This guide assumes you have a functional account available on the network you’re deploying to, and familiarity +with the process of declaring contracts through the `declare` transaction. To recap, the reason we are deploying +this bootstrapper account contract is to be able to deploy the UDC via the `deploy_syscall`. + + +sncast v0.45.0 was used in the examples below. + + +As a quick example, if your account is configured for **sncast**, you can declare the bootstrapper contract with the following command: + +```bash +sncast -p declare \ + --contract-name UniversalDeployerBootstrapper +``` + +The bootstrapper implements the `IDeployable` trait, meaning it can be counterfactually deployed. Check out the +[Counterfactual Deployments](./deployment) guide. Continuing with the **sncast** examples, you can create and deploy the bootstrapper with the following commands: + +##### Create the account + +```bash +sncast account create --name bootstrapper \ + --network \ + --class-hash \ + --type oz +``` + +##### Deploy it to the network + + +You need to prefund the account with enough funds before you can deploy it. + + +```bash +sncast account deploy \ + --network \ + --name bootstrapper +``` + +### 2. Declare and deploy the UDC + +Once the bootstrapper is deployed, you can declare and deploy the UDC through it. + +#### Declaring the UDC + +The UDC source code is available in the `openzeppelin_presets` package. You can copy it to your project and declare it with the following command: + +```bash +sncast -p declare \ + --contract-name UniversalDeployer +``` + + +If you followed the [Note on the UDC final address](#note-on-the-udc-final-address) section, your declared class hash should be +`0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8`. + + +#### Previewing the UDC address + +You can preview the UDC address with the following command: + +```bash +sncast call \ + --network \ + --contract-address \ + --function "get_udc_address" \ + --arguments '' +``` + +If the UDC class hash is the same as the one in the [Note on the UDC final address](#note-on-the-udc-final-address) section, +the output should be `0x2ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125`. + +#### Deploying the UDC + +Now everything is set up to deploy the UDC. You can use the following command to deploy it: + + +Note that the bootstrapper contract MUST call itself to successfully deploy the UDC, since the `deploy_udc` function is protected. + + +```bash +sncast \ + --account bootstrapper \ + invoke \ + --network \ + --contract-address \ + --function "deploy_udc" \ + --arguments '' +``` + +## Other Appchain providers + +If you are using an appchain provider different from Madara, you can follow the same steps to deploy the UDC +as long as you have access to an account that can declare contracts. + +Summarizing, the steps to follow are: + +1. Declare the Bootstrapper +2. Counterfactually deploy the Bootstrapper +3. Declare the UDC +4. Preview the UDC address +5. Deploy the UDC from the Bootstrapper + +## Conclusion + +By following this guide, you have successfully deployed the Universal Deployer Contract on your appchain while ensuring consistency with +Starknet’s public networks. Maintaining the same UDC address and class hash across all environments is crucial for seamless contract deployment +and tooling compatibility, allowing developers to leverage tools like **sncast** and **starkli** without additional configuration. This process not only +improves the reliability of your deployment workflows but also ensures that your appchain remains compatible with the broader Starknet ecosystem. +With the UDC correctly deployed, you are now ready to take full advantage of streamlined contract +deployments and robust developer tooling on your appchain. diff --git a/docs/content/contracts-cairo/2.x/guides/deployment.mdx b/docs/content/contracts-cairo/2.x/guides/deployment.mdx new file mode 100644 index 00000000..42a924db --- /dev/null +++ b/docs/content/contracts-cairo/2.x/guides/deployment.mdx @@ -0,0 +1,40 @@ +--- +title: Counterfactual deployments +--- + +A counterfactual contract is a contract we can interact with even before actually deploying it on-chain. +For example, we can send funds or assign privileges to a contract that doesn’t yet exist. +Why? Because deployments in Starknet are deterministic, allowing us to predict the address where our contract will be deployed. +We can leverage this property to make a contract pay for its own deployment by simply sending funds in advance. We call this a counterfactual deployment. + +This process can be described with the following steps: + + +For testing this flow you can check the [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/starknet/account.html) or the [Starkli](https://book.starkli.rs/accounts#account-deployment) guides for deploying accounts. + + +1. Deterministically precompute the `contract_address` given a `class_hash`, `salt`, and constructor `calldata`. +Note that the `class_hash` must be previously declared for the deployment to succeed. +2. Send funds to the `contract_address`. Usually you will estimate the fee of the transaction first. Existing +tools usually do this for you. +3. Send a `DeployAccount` type transaction to the network. +4. The protocol will then validate the transaction with the `__validate_deploy__` entrypoint of the contract to be deployed. +5. If the validation succeeds, the protocol will charge the fee and then register the contract as deployed. + + +Although this method is very popular to deploy accounts, this works for any kind of contract. + + +## Deployment validation + +To be counterfactually deployed, the deploying contract must implement the `__validate_deploy__` entrypoint, +called by the protocol when a `DeployAccount` transaction is sent to the network. + +```rust +trait IDeployable { + /// Must return 'VALID' when the validation is successful. + fn __validate_deploy__( + class_hash: felt252, contract_address_salt: felt252, public_key: felt252 + ) -> felt252; +} +``` diff --git a/docs/content/contracts-cairo/2.x/guides/erc20-permit.mdx b/docs/content/contracts-cairo/2.x/guides/erc20-permit.mdx new file mode 100644 index 00000000..28259458 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/guides/erc20-permit.mdx @@ -0,0 +1,63 @@ +--- +title: ERC20Permit +--- + +The [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) standard, commonly referred to as ERC20Permit, is designed to support gasless token approvals. This is achieved with an off-chain +signature following the [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) standard, rather than with an on-chain transaction. The [permit](../api/erc20#ERC20Component-permit) function verifies the signature and sets +the spender’s allowance if the signature is valid. This approach improves user experience and reduces gas costs. + +## Differences from Solidity + +Although this extension is mostly similar to the [Solidity implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol) of [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612), there are some notable differences in the parameters of the [permit](../api/erc20#ERC20Component-permit) function: + +* The `deadline` parameter is represented by `u64` rather than `u256`. +* The `signature` parameter is represented by a span of felts rather than `v`, `r`, and `s` values. + + +Unlike Solidity, there is no enforced format for signatures on Starknet. A signature is represented by an array or span of felts, +and there is no universal method for validating signatures of unknown formats. Consequently, a signature provided to the [permit](../api/erc20#ERC20Component-permit) function +is validated through an external `is_valid_signature` call to the contract at the `owner` address. + + + +## Usage + +The functionality is provided as an embeddable [ERC20Permit](../api/erc20#ERC20Component-Embeddable-Impls-ERC20PermitImpl) trait of the [ERC20Component](../api/erc20#ERC20Component). + +```rust +#[abi(embed_v0)] +impl ERC20PermitImpl = ERC20Component::ERC20PermitImpl; +``` + +A contract must meet the following requirements to be able to use the [ERC20Permit](../api/erc20#ERC20Component-Embeddable-Impls-ERC20PermitImpl) trait: + +* Implement [ERC20Component](../api/erc20#ERC20Component). +* Implement [NoncesComponent](../api/utilities#NoncesComponent). +* Implement [SNIP12Metadata](../api/utilities#snip12) trait (used in signature generation). + +## Typed message + +To safeguard against replay attacks and ensure the uniqueness of each approval via [permit](../api/erc20#ERC20Component-permit), the data signed includes: + +* The address of the `owner`. +* The parameters specified in the [approve](../api/erc20#ERC20Component-approve) function (`spender` and `amount`) +* The address of the `token` contract itself. +* A `nonce`, which must be unique for each operation. +* The `chain_id`, which protects against cross-chain replay attacks. + +The format of the `Permit` structure in a signed permit message is as follows: +```rust +struct Permit { + token: ContractAddress, + spender: ContractAddress, + amount: u256, + nonce: felt252, + deadline: u64, +} +``` + + +The owner of the tokens is also part of the signed message. It is used as the `signer` parameter in the `get_message_hash` call. + + +Further details on preparing and signing a typed message can be found in the [SNIP12 guide](./snip12). diff --git a/docs/content/contracts-cairo/2.x/guides/erc20-supply.mdx b/docs/content/contracts-cairo/2.x/guides/erc20-supply.mdx new file mode 100644 index 00000000..51465637 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/guides/erc20-supply.mdx @@ -0,0 +1,148 @@ +--- +title: Creating ERC20 Supply +--- + +The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20. +[EIP20](https://eips.ethereum.org/EIPS/eip-20), from which ERC20 contracts are derived, does not specify how tokens are created. +This guide will go over strategies for creating both a fixed and dynamic token supply. + +## Fixed Supply + +Let’s say we want to create a token named `MyToken` with a fixed token supply. +We can achieve this by setting the token supply in the constructor which will execute upon deployment. + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + fixed_supply: u256, + recipient: ContractAddress + ) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, fixed_supply); + } +} +``` + +In the constructor, we’re first calling the ERC20 initializer to set the token name and symbol. +Next, we’re calling the internal `mint` function which creates `fixed_supply` of tokens and allocates them to `recipient`. +Since the internal `mint` is not exposed in our contract, it will not be possible to create any more tokens. +In other words, we’ve implemented a fixed token supply! + +## Dynamic Supply + +ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens. +Let’s make a few changes to the almighty `MyToken` contract and create a minting mechanism. + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + } + + #[external(v0)] + fn mint( + ref self: ContractState, + recipient: ContractAddress, + amount: u256 + ) { + // This function is NOT protected which means + // ANYONE can mint tokens + self.erc20.mint(recipient, amount); + } +} +``` + +The exposed `mint` above will create `amount` tokens and allocate them to `recipient`. +We now have our minting mechanism! + +There is, however, a big problem. +`mint` does not include any restrictions on who can call this function. +For the sake of good practices, let’s implement a simple permissioning mechanism with `Ownable`. + +```rust +#[starknet::contract] +mod MyToken { + + (...) + + // Integrate Ownable + + #[external(v0)] + fn mint( + ref self: ContractState, + recipient: ContractAddress, + amount: u256 + ) { + // Set permissions with Ownable + self.ownable.assert_only_owner(); + + // Mint tokens if called by the contract owner + self.erc20.mint(recipient, amount); + } +} +``` + +In the constructor, we pass the owner address to set the owner of the `MyToken` contract. +The `mint` function includes `assert_only_owner` which will ensure that only the contract owner can call this function. +Now, we have a protected ERC20 minting mechanism to create a dynamic token supply. + + +For a more thorough explanation of permission mechanisms, see [Access Control](../access). + diff --git a/docs/content/contracts-cairo/2.x/guides/interfaces-and-dispatchers.mdx b/docs/content/contracts-cairo/2.x/guides/interfaces-and-dispatchers.mdx new file mode 100644 index 00000000..022c2c47 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/guides/interfaces-and-dispatchers.mdx @@ -0,0 +1,163 @@ +--- +title: Interfaces and Dispatchers +--- + +This section describes the interfaces OpenZeppelin Contracts for Cairo offer, and explains the design choices behind them. + +Interfaces can be found in the module tree under the `interface` submodule, such as `token::erc20::interface`. + +For example: + +```rust +use openzeppelin_token::erc20::interface::IERC20; +``` + +or + +```rust +use openzeppelin_token::erc20::interface::ERC20ABI; +``` + + +For simplicity, we’ll use ERC20 as example but the same concepts apply to other modules. + + +## Interface traits + +The library offers three types of traits to implement or interact with contracts: + +### Standard traits + +These are associated with a predefined interface such as a standard. +This includes only the functions defined in the interface, and is the standard way to interact with a compliant contract. + +```rust +#[starknet::interface] +pub trait IERC20 { + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +### ABI traits + +They describe a contract’s complete interface. This is useful to interface with a preset contract offered by this library, such as +the ERC20 preset that includes functions from different standards such as `IERC20` and `IERC20Camel`. + + +The library offers an ABI trait for most components, providing all external function signatures +even when most of the time all of them don’t need to be implemented at the same time. This can be helpful when interacting with +a contract implementing the component, instead of defining a new dispatcher. + + +```rust +#[starknet::interface] +pub trait ERC20ABI { + // IERC20 + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + + // IERC20CamelOnly + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} +``` + +### Dispatcher traits + +Traits annotated with `#[starknet::interface]` automatically generate a dispatcher that can be used to interact with contracts that implement the given interface. They can be imported by appending the `Dispatcher` and `DispatcherTrait` suffixes to the trait name, like this: + +```rust +use openzeppelin_token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +``` + +Other types of dispatchers are also auto-generated from the annotated trait. See the +[Interacting with another contract](https://book.cairo-lang.org/ch15-02-interacting-with-another-contract.html) section of the Cairo book for more information. + + +In the example, the `IERC20Dispatcher` is the one used to interact with contracts, but the +`IERC20DispatcherTrait` needs to be in scope for the functions to be available. + + +## Dual interfaces + + +The `camelCase` functions are deprecated and maintained only for backwards compatibility. +It’s recommended to only use `snake_case` interfaces with contracts and components. The `camelCase` functions will be removed in +future versions. + + +Following the [Great Interface Migration](https://community.starknet.io/t/the-great-interface-migration/92107) plan, we added `snake_case` functions to all of our preexisting `camelCase` contracts with the goal of eventually dropping support for the latter. + +In short, the library offers two types of interfaces and utilities to handle them: + +1. `camelCase` interfaces, which are the ones we’ve been using so far. +2. `snake_case` interfaces, which are the ones we’re migrating to. + +This means that currently most of our contracts implement _dual interfaces_. For example, the ERC20 preset contract exposes `transferFrom`, `transfer_from`, `balanceOf`, `balance_of`, etc. + + +Dual interfaces are available for all external functions present in previous versions of OpenZeppelin Contracts for Cairo ([v0.6.1](https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v0.6.1) and below). + + +### `IERC20` + +The default version of the ERC20 interface trait exposes `snake_case` functions: + +```rust +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +### `IERC20Camel` + +On top of that, the library also offers a `camelCase` version of the same interface: + +```rust +#[starknet::interface] +pub trait IERC20Camel { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` diff --git a/docs/content/contracts-cairo/2.x/guides/snip12.mdx b/docs/content/contracts-cairo/2.x/guides/snip12.mdx new file mode 100644 index 00000000..98ac1f4b --- /dev/null +++ b/docs/content/contracts-cairo/2.x/guides/snip12.mdx @@ -0,0 +1,344 @@ +--- +title: SNIP12 and Typed Messages +--- + +Similar to [EIP712](https://eips.ethereum.org/EIPS/eip-712), [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) is a standard for secure off-chain signature verification on Starknet. +It provides a way to hash and sign generic typed structs rather than just strings. When building decentralized +applications, usually you might need to sign a message with complex data. The purpose of signature verification +is then to ensure that the received message was indeed signed by the expected signer, and it hasn’t been tampered with. + +OpenZeppelin Contracts for Cairo provides a set of utilities to make the implementation of this standard +as easy as possible, and in this guide we will walk you through the process of generating the hashes of typed messages +using these utilities for on-chain signature verification. For that, let’s build an example with a custom [ERC20](/contracts-cairo/2.x/api/erc20#ERC20) contract +adding an extra `transfer_with_signature` method. + + +This is an educational example, and it is not intended to be used in production environments. + + +## CustomERC20 + +Let’s start with a basic ERC20 contract leveraging the [ERC20Component](/contracts-cairo/2.x/api/erc20#ERC20Component), and let’s add the new function. +Note that some declarations are omitted for brevity. The full example will be available at the end of the guide. + +```rust +#[starknet::contract] +mod CustomERC20 { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) + + #[constructor] + fn constructor( + ref self: ContractState, + initial_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer("MyToken", "MTK"); + self.erc20.mint(recipient, initial_supply); + } + + #[external(v0)] + fn transfer_with_signature( + ref self: ContractState, + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64, + signature: Array + ) { + (...) + } +} +``` + +The `transfer_with_signature` function will allow a user to transfer tokens to another account by providing a signature. +The signature will be generated off-chain, and it will be used to verify the message on-chain. Note that the message +we need to verify is a struct with the following fields: + +* `recipient`: The address of the recipient. +* `amount`: The amount of tokens to transfer. +* `nonce`: A unique number to prevent replay attacks. +* `expiry`: The timestamp when the signature expires. + +Note that generating the hash of this message on-chain is a requirement to verify the signature, because if we accept +the message as a parameter, it could be easily tampered with. + +## Generating the Typed Message Hash + +To generate the hash of the message, we need to follow these steps: + +### 1. Define the message struct. + +In this particular example, the message struct looks like this: + +```rust +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} +``` + +### 2. Get the message type hash. + +This is the `starknet_keccak(encode_type(message))` as defined in the [SNIP](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md#how-to-work-with-each-type). + +In this case it can be computed as follows: + +```rust +// Since there's no u64 type in SNIP-12, we use u128 for `expiry` in the type hash generation. +let message_type_hash = selector!( + "\"Message\"(\"recipient\":\"ContractAddress\",\"amount\":\"u256\",\"nonce\":\"felt\",\"expiry\":\"u128\")\"u256\"(\"low\":\"u128\",\"high\":\"u128\")" +); +``` + +which is the same as: + +```rust +let message_type_hash = 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; +``` + + +In practice it’s better to compute the type hash off-chain and hardcode it in the contract, since it is a constant value. + + +### 3. Implement the `StructHash` trait for the struct. + +You can import the trait from: `openzeppelin_utils::snip12::StructHash`. And this implementation +is nothing more than the encoding of the message as defined in the [SNIP](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md#how-to-work-with-each-type). + +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; +use openzeppelin_utils::snip12::StructHash; +use starknet::ContractAddress; + +const MESSAGE_TYPE_HASH: felt252 = + 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; + +#[derive(Copy, Drop, Hash)] +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} + +impl StructHashImpl of StructHash { + fn hash_struct(self: @Message) -> felt252 { + let hash_state = PoseidonTrait::new(); + hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() + } +} +``` + +### 4. Implement the `SNIP12Metadata` trait. + +This implementation determines the values of the domain separator. Only the `name` and `version` fields are required +because the `chain_id` is obtained on-chain, and the `revision` is hardcoded to `1`. + +```rust +use openzeppelin_utils::snip12::SNIP12Metadata; + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { 'DAPP_NAME' } + fn version() -> felt252 { 'v1' } +} +``` + +In the above example, no storage reads are required which avoids unnecessary extra gas costs, but in +some cases we may need to read from storage to get the domain separator values. This can be accomplished even when +the trait is not bounded to the ContractState, like this: + +```rust +use openzeppelin_utils::snip12::SNIP12Metadata; + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + let state = unsafe_new_contract_state(); + + // Some logic to get the name from storage + state.erc20.name().at(0).unwrap().into() + } + + fn version() -> felt252 { 'v1' } +} +``` + +### 5. Generate the hash. + +The final step is to use the `OffchainMessageHashImpl` implementation to generate the hash of the message +using the `get_message_hash` function. The implementation is already available as a utility. + +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; +use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; +use starknet::ContractAddress; + +const MESSAGE_TYPE_HASH: felt252 = + 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; + +#[derive(Copy, Drop, Hash)] +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} + +impl StructHashImpl of StructHash { + fn hash_struct(self: @Message) -> felt252 { + let hash_state = PoseidonTrait::new(); + hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() + } +} + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'v1' + } +} + +fn get_hash( + account: ContractAddress, recipient: ContractAddress, amount: u256, nonce: felt252, expiry: u64 +) -> felt252 { + let message = Message { recipient, amount, nonce, expiry }; + message.get_message_hash(account) +} +``` + + +The expected parameter for the `get_message_hash` function is the address of account that signed the message. + + +## Full Implementation + +Finally, the full implementation of the `CustomERC20` contract looks like this: + + +We are using the [`ISRC6Dispatcher`](/contracts-cairo/2.x/api/account#ISRC6) to verify the signature, +and the [`NoncesComponent`](/contracts-cairo/2.x/api/utilities#NoncesComponent) to handle nonces to prevent replay attacks. + + +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; +use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; +use starknet::ContractAddress; + +const MESSAGE_TYPE_HASH: felt252 = + 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; + +#[derive(Copy, Drop, Hash)] +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} + +impl StructHashImpl of StructHash { + fn hash_struct(self: @Message) -> felt252 { + let hash_state = PoseidonTrait::new(); + hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() + } +} + +#[starknet::contract] +mod CustomERC20 { + use openzeppelin_account::interface::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use starknet::ContractAddress; + + use super::{Message, OffchainMessageHash, SNIP12Metadata}; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + impl NoncesInternalImpl = NoncesComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { + self.erc20.initializer("MyToken", "MTK"); + self.erc20.mint(recipient, initial_supply); + } + + /// Required for hash computation. + impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'CustomERC20' + } + fn version() -> felt252 { + 'v1' + } + } + + #[external(v0)] + fn transfer_with_signature( + ref self: ContractState, + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64, + signature: Array + ) { + assert(starknet::get_block_timestamp() <= expiry, 'Expired signature'); + let owner = starknet::get_caller_address(); + + // Check and increase nonce + self.nonces.use_checked_nonce(owner, nonce); + + // Build hash for calling `is_valid_signature` + let message = Message { recipient, amount, nonce, expiry }; + let hash = message.get_message_hash(owner); + + let is_valid_signature_felt = ISRC6Dispatcher { contract_address: owner } + .is_valid_signature(hash, signature); + + // Check either 'VALID' or true for backwards compatibility + let is_valid_signature = is_valid_signature_felt == starknet::VALIDATED + || is_valid_signature_felt == 1; + assert(is_valid_signature, 'Invalid signature'); + + // Transfer tokens + self.erc20._transfer(owner, recipient, amount); + } +} +``` diff --git a/docs/content/contracts-cairo/2.x/index.mdx b/docs/content/contracts-cairo/2.x/index.mdx new file mode 100644 index 00000000..d78d99d5 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/index.mdx @@ -0,0 +1,122 @@ +--- +title: Contracts for Cairo +--- + +[starknet]:https://starkware.co/product/starknet/ +[scarb]:https://docs.swmansion.com/scarb/ +[scarb-install]:https://docs.swmansion.com/scarb/download.html + + +**A library for secure smart contract development** written in Cairo for [Starknet][starknet]. This library consists of a set of +[reusable components](/contracts-cairo/2.x/components) to build custom smart contracts, as well as ready-to-deploy [presets](/contracts-cairo/2.x/presets). You can also +find other [utilities](/contracts-cairo/2.x/api/utilities) including [interfaces and dispatchers](/contracts-cairo/alpha/interfaces) and [test utilities](/contracts-cairo/2.x/api/testing) +that facilitate testing with Starknet Foundry. + + +You can track our roadmap and future milestones in our [Github Project](https://github.com/orgs/OpenZeppelin/projects/29/). + + +## Installation + +The library is available as a [Scarb][scarb] package. Follow [this guide][scarb-install] for installing Cairo and Scarb on your machine +before proceeding, and run the following command to check that the installation was successful: + +```bash +$ scarb --version + +scarb 2.12.0 (639d0a65e 2025-08-04) +cairo: 2.12.0 (https://crates.io/crates/cairo-lang-compiler/2.12.0) +sierra: 1.7.0 +``` + +### Set up your project + +Create an empty directory, and `cd` into it: + +```bash +mkdir my_project/ && cd my_project/ +``` + +Initialize a new Scarb project: + +```bash +scarb init +``` + +The contents of `my_project/` should now look like this: + +```bash +$ ls + +Scarb.toml src +``` + +### Install the library + +Install the library by declaring it as a dependency in the project’s `Scarb.toml` file: + +```javascript +[dependencies] +openzeppelin = "{{umbrella_version}}" +``` + +The previous example would import the entire library. We can also add each package as a separate dependency to +improve the building time by not including modules that won’t be used: + +```toml +[dependencies] +openzeppelin_access = "{{umbrella_version}}" +``` + +## Basic usage + +This is how it looks to build an ERC20 contract using the [ERC20 component](/contracts-cairo/2.x/erc20). +Copy the code into `src/lib.cairo`. + +```rust +#[starknet::contract] +mod MyERC20Token { + // NOTE: If you added the entire library as a dependency, + // use `openzeppelin::token` instead. + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + fixed_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, fixed_supply); + } +} +``` + +You can now compile it: + +```bash +scarb build +``` diff --git a/docs/content/contracts-cairo/2.x/introspection.mdx b/docs/content/contracts-cairo/2.x/introspection.mdx new file mode 100644 index 00000000..da51ffd1 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/introspection.mdx @@ -0,0 +1,137 @@ +--- +title: Introspection +--- + +To smooth interoperability, often standards require smart contracts to implement [introspection mechanisms](https://en.wikipedia.org/wiki/Type_introspection). + +In Ethereum, the [EIP165](https://eips.ethereum.org/EIPS/eip-165) standard defines how contracts should declare +their support for a given interface, and how other contracts may query this support. + +Starknet offers a similar mechanism for interface introspection defined by the [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) standard. + +## SRC5 + +Similar to its Ethereum counterpart, the [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) standard requires contracts to implement the `supports_interface` function, +which can be used by others to query if a given interface is supported. + +### Usage + +To expose this functionality, the contract must implement the [SRC5Component](/contracts-cairo/2.x/api/introspection#SRC5Component), which defines the `supports_interface` function. +Here is an example contract: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.src5.register_interface(selector!("some_interface")); + } +} +``` + +### Interface + +```rust +#[starknet::interface] +pub trait ISRC5 { + /// Query if a contract implements an interface. + /// Receives the interface identifier as specified in SRC-5. + /// Returns `true` if the contract implements `interface_id`, `false` otherwise. + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +## Computing the interface ID + +The interface ID, as specified in the standard, is the [XOR](https://en.wikipedia.org/wiki/Exclusive_or) of all the +[Extended Function Selectors](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md#extended-function-selector) +of the interface. We strongly advise reading the SNIP to understand the specifics of computing these +extended function selectors. There are tools such as [src5-rs](https://github.com/ericnordelo/src5-rs) that can help with this process. + +## Registering interfaces + +For a contract to declare its support for a given interface, we recommend using the SRC5 component to register support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_account::interface as interface; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl InternalImpl = SRC5Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + // Register the contract's support for the ISRC6 interface + self.src5.register_interface(interface::ISRC6_ID); + } + + (...) +} +``` + +## Querying interfaces + +Use the `supports_interface` function to query a contract’s support for a given interface. + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_account::interface as interface; + use openzeppelin_introspection::interface::ISRC5DispatcherTrait; + use openzeppelin_introspection::interface::ISRC5Dispatcher; + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[external(v0)] + fn query_is_account(self: @ContractState, target: ContractAddress) -> bool { + let dispatcher = ISRC5Dispatcher { contract_address: target }; + dispatcher.supports_interface(interface::ISRC6_ID) + } +} +``` + + +If you are unsure whether a contract implements SRC5 or not, you can follow the process described in +[here](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md#how-to-detect-if-a-contract-implements-src-5). + diff --git a/docs/content/contracts-cairo/2.x/macros.mdx b/docs/content/contracts-cairo/2.x/macros.mdx new file mode 100644 index 00000000..13482da4 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/macros.mdx @@ -0,0 +1,15 @@ +--- +title: Macros +--- + +This crate provides a collection of macros that streamline and simplify development with the library. +To use them, you need to add the `openzeppelin_macros` crate as a dependency in your `Scarb.toml` file: + +```toml +openzeppelin_macros = "{{umbrella_version}}" +``` + +## Attribute macros + +* [with_components](./macros/with_components) +* [type_hash](./macros/type_hash) diff --git a/docs/content/contracts-cairo/2.x/macros/type_hash.mdx b/docs/content/contracts-cairo/2.x/macros/type_hash.mdx new file mode 100644 index 00000000..279090a2 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/macros/type_hash.mdx @@ -0,0 +1,193 @@ +--- +title: type_hash +--- + +This macro generates a SNIP-12-compatible type hash for a given struct or enum. + + +This macro is fully compatible with the SNIP-12 standard revision 1. + + +## Usage + +```rust +#[type_hash(name: "My Struct", debug: true)] +struct MyStruct { + #[snip12(name: "My Field")] + my_field: felt252, +} +``` + +This will generate a type hash for the struct. + +```rust +pub const MY_STRUCT_TYPE_HASH: felt252 = 0x1735aa9819941b96c651b740b792a96c854565eaff089b7e293d996828b88a8; +``` + +And because of the `debug` argument, it will generate the following code: + +```rust +pub fn __MY_STRUCT_encoded_type() { + println!("\"My Struct\"(\"My Field\":\"felt\")"); +} +``` + +## Basic types + +The list of supported basic types as defined in the SNIP-12 standard is: + +* felt252 +* shortstring +* ClassHash +* ContractAddress +* timestamp +* selector +* merkletree +* u128 +* i128 + +### Examples + +Struct with basic types and custom names and kinds: + +```rust +#[type_hash(name: "My Struct", debug: true)] +pub struct MyStruct { + #[snip12(name: "Simple Felt")] // Optional custom name + pub simple_felt: felt252, + #[snip12(name: "Class Hash")] + pub class_hash: ClassHash, + #[snip12(name: "Target Token")] + pub target: ContractAddress, + #[snip12(name: "Timestamp", kind: "timestamp")] + pub timestamp: u128, + #[snip12(name: "Selector", kind: "selector")] + pub selector: felt252, +} + +pub const MY_STRUCT_TYPE_HASH: felt252 + = 0x522e0c3dc5e13b0978f4645760a436b1e119fd335842523fee8fbae6057b8c; + +``` + +Enum with basic types and custom names and kinds: + +```rust +#[type_hash(name: "My Enum", debug: true)] +pub enum MyEnum { + #[snip12(name: "Simple Felt")] + SimpleFelt: felt252, + #[snip12(name: "Class Hash")] + ClassHash: ClassHash, + #[snip12(name: "Target Token")] + ContractAddress: ContractAddress, + #[snip12(name: "Timestamp", kind: "timestamp")] + Timestamp: u128, + #[snip12(name: "Selector", kind: "selector")] + Selector: felt252, +} + +pub const MY_ENUM_TYPE_HASH: felt252 + = 0x3f30aaa6cda9f699d4131940b10602b78b986feb88f28a19f3b48567cb4b566; +``` + +## Collection types + +The list of supported collection types as defined in the SNIP-12 standard is: + +* Array +* Tuple ***(Only supported for enums)*** +* Span ***(Treated as an array)*** + + +While Span is not directly supported by the SNIP-12 standard, it is treated as an array for the purposes of this macro, since +it is sometimes helpful to use `Span` instead of `Array` in order to save on gas. + + +### Examples + +Struct with collection types: + +```rust +#[type_hash(name: "My Struct", debug: true)] +pub struct MyStruct { + #[snip12(name: "Member 1")] + pub member1: Array, + #[snip12(name: "Member 2")] + pub member2: Span, + #[snip12(name: "Timestamps", kind: "Array")] + pub timestamps: Array, +} + +pub const MY_STRUCT_TYPE_HASH: felt252 + = 0x369cdec45d8c55e70986aed44da0e330375171ba6e25b58e741c0ce02fa8ac; +``` + +Enum with collection types: + +```rust +#[type_hash(name: "My Enum", debug: true)] +pub enum MyEnum { + #[snip12(name: "Member 1")] + Member1: Array, + #[snip12(name: "Member 2")] + Member2: Span, + #[snip12(name: "Timestamps", kind: "Array")] + Timestamps: Array, + #[snip12(name: "Name and Last Name", kind: "(shortstring, shortstring)")] + NameAndLastName: (felt252, felt252), +} + +pub const MY_ENUM_TYPE_HASH: felt252 + = 0x9e3e1ebad4448a8344b3318f9cfda5df237588fd8328e1c2968635f09c735d; +``` + +## Preset types + +The list of supported preset types as defined in the SNIP-12 standard is: + +* TokenAmount +* NftId +* u256 + +### Examples + +Struct with preset types: + +```rust +#[type_hash(name: "My Struct", debug: true)] +pub struct MyStruct { + #[snip12(name: "Token Amount")] + pub token_amount: TokenAmount, + #[snip12(name: "NFT ID")] + pub nft_id: NftId, + #[snip12(name: "Number")] + pub number: u256, +} + +pub const MY_STRUCT_TYPE_HASH: felt252 + = 0x19f63528d68c4f44b7d9003a5a6b7793f5bb6ffc8a22bdec82b413ddf4f9412; +``` + +Enum with preset types: + +```rust +#[type_hash(name: "My Enum", debug: true)] +pub enum MyEnum { + #[snip12(name: "Token Amount")] + TokenAmount: TokenAmount, + #[snip12(name: "NFT ID")] + NftId: NftId, + #[snip12(name: "Number")] + Number: u256, +} + +pub const MY_ENUM_TYPE_HASH: felt252 + = 0x39dd19c7e5c5f89e084b78a26200b712c6ae3265f2bae774471c588858421b7; +``` + +## User-defined types + +User-defined types are currently ***NOT SUPPORTED*** since the macro doesn’t have access to scope outside of the +target struct/enum. In the future it may be supported by extending the syntax to explicitly declare the custom type +definition. diff --git a/docs/content/contracts-cairo/2.x/macros/with_components.mdx b/docs/content/contracts-cairo/2.x/macros/with_components.mdx new file mode 100644 index 00000000..d8bbd7ba --- /dev/null +++ b/docs/content/contracts-cairo/2.x/macros/with_components.mdx @@ -0,0 +1,133 @@ +--- +title: with_components +--- + +This macro simplifies the syntax for adding a set of components to a contract. It: + +* _Imports the corresponding components into the contract._ +* _Adds the corresponding `component!` macro entries._ +* _Adds the storage entries for each component to the Storage struct._ +* _Adds the event entries for each component to the Event struct, or creates the struct if it is missing._ +* _Brings the corresponding internal implementations into scope._ +* _Provides some diagnostics for each specific component to help the developer avoid common mistakes._ + + +Since the macro does not expose any external implementations, developers must make sure to specify explicitly +the ones required by the contract. + + +## Security considerations + +The macro was designed to be simple and effective while still being very hard to misuse. For this reason, the features +that it provides are limited, and things that might make the contract behave in unexpected ways must be +explicitly specified by the developer. It does not specify external implementations, so contracts won’t find +themselves in a situation where external functions are exposed without the developer’s knowledge. It brings +the internal implementations into scope so these functions are available by default, but if they are not used, +they won’t have any effect on the contract’s behavior. + +## Usage + +This is how a contract with multiple components looks when using the macro. + +```rust +#[with_components(Account, SRC5, SRC9, Upgradeable)] +#[starknet::contract(account)] +mod OutsideExecutionAccountUpgradeable { + use openzeppelin_upgrades::interface::IUpgradeable; + use starknet::{ClassHash, ContractAddress}; + + // External + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + #[abi(embed_v0)] + impl OutsideExecutionV2Impl = + SRC9Component::OutsideExecutionV2Impl; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + self.src9.initializer(); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.account.assert_only_self(); + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` + +This is how the same contract looks using regular syntax. + +```rust +#[starknet::contract(account)] +mod OutsideExecutionAccountUpgradeable { + use openzeppelin::account::AccountComponent; + use openzeppelin::account::extensions::SRC9Component; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::ClassHash; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: SRC9Component, storage: src9, event: SRC9Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // External + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + #[abi(embed_v0)] + impl OutsideExecutionV2Impl = + SRC9Component::OutsideExecutionV2Impl; + + // Internal + impl AccountInternalImpl = AccountComponent::InternalImpl; + impl OutsideExecutionInternalImpl = SRC9Component::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + src9: SRC9Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + SRC9Event: SRC9Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + self.src9.initializer(); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.account.assert_only_self(); + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` diff --git a/docs/content/contracts-cairo/2.x/presets.mdx b/docs/content/contracts-cairo/2.x/presets.mdx new file mode 100644 index 00000000..51adcc26 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/presets.mdx @@ -0,0 +1,141 @@ +--- +title: Presets +--- + +Presets are ready-to-deploy contracts provided by the library. Since presets are intended to be very simple +and as generic as possible, there’s no support for custom or complex contracts such as `ERC20Pausable` or `ERC721Mintable`. + + +For contract customization and combination of modules you can use [Wizard for Cairo](https://wizard.openzeppelin.com), our code-generation tool. + + +## Available presets + +List of available presets and their corresponding [Sierra class hashes](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/). Like Contracts for Cairo, +use of preset contracts are subject to the terms of the +[MIT License](https://github.com/OpenZeppelin/cairo-contracts?tab=MIT-1-ov-file#readme). + + +Class hashes were computed using scarb `v{{class_hash_scarb_version}}` and the `scarb --release` profile. + + + +Before version 2.x, class hashes were computed using the `scarb --dev` profile. + + +| Name | Sierra Class Hash | +| --- | --- | +| [`AccountUpgradeable`](/contracts-cairo/2.x/api/account#AccountUpgradeable) | `{{AccountUpgradeableClassHash}}` | +| [`ERC20Upgradeable`](/contracts-cairo/2.x/api/erc20#ERC20Upgradeable) | `{{ERC20UpgradeableClassHash}}` | +| [`ERC721Upgradeable`](/contracts-cairo/2.x/api/erc721#ERC721Upgradeable) | `{{ERC721UpgradeableClassHash}}` | +| [`ERC1155Upgradeable`](/contracts-cairo/2.x/api/erc1155#ERC1155Upgradeable) | `{{ERC1155UpgradeableClassHash}}` | +| [`EthAccountUpgradeable`](/contracts-cairo/2.x/api/account#EthAccountUpgradeable) | `{{EthAccountUpgradeableClassHash}}` | +| [`UniversalDeployer`](/contracts-cairo/2.x/api/udc#UniversalDeployer) | `{{UniversalDeployerClassHash}}` | +| [`VestingWallet`](/contracts-cairo/2.x/api/finance#VestingWallet) | `{{VestingWalletClassHash}}` | + + +[starkli](https://book.starkli.rs/introduction) class-hash command can be used to compute the class hash from a Sierra artifact. + + +## Usage + +These preset contracts are ready-to-deploy which means they should already be declared on the Sepolia network. +Simply deploy the preset class hash and add the appropriate constructor arguments. +Deploying the ERC20Upgradeable preset with [starkli](https://book.starkli.rs/introduction), for example, will look like this: + +```bash +starkli deploy {ERC20Upgradeable-class-hash} \ + \ + --network="sepolia" +``` + +If a class hash has yet to be declared, copy/paste the preset contract code and declare it locally. +Start by [setting up a project](/contracts-cairo#set-up-your-project) and [installing the Contracts for Cairo library](/contracts-cairo#install-the-library). +Copy the target preset contract from the [presets directory](https://github.com/OpenZeppelin/cairo-contracts/blob/release-v{{umbrella_version}}/packages/presets/src) +and paste it in the new project’s `src/lib.cairo` like this: + +```rust +// src/lib.cairo + +#[starknet::contract] +mod ERC20Upgradeable { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_upgrades::interface::IUpgradeable; + use starknet::{ContractAddress, ClassHash}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + fixed_supply: u256, + recipient: ContractAddress, + owner: ContractAddress + ) { + self.ownable.initializer(owner); + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, fixed_supply); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` + +Next, compile the contract. + +```bash +scarb build +``` + +Finally, declare the preset. + +```bash +starkli declare target/dev/my_project_ERC20Upgradeable.contract_class.json \ + --network="sepolia" +``` diff --git a/docs/content/contracts-cairo/2.x/security.mdx b/docs/content/contracts-cairo/2.x/security.mdx new file mode 100644 index 00000000..b7853675 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/security.mdx @@ -0,0 +1,218 @@ +--- +title: Security +--- + +The following documentation provides context, reasoning, and examples of modules found under `openzeppelin_security`. + + +Expect these modules to evolve. + + +## Initializable + +The [Initializable](/contracts-cairo/2.x/api/security#InitializableComponent) component provides a simple mechanism that mimics +the functionality of a constructor. +More specifically, it enables logic to be performed once and only once which is useful to set up a contract’s initial state when a constructor cannot be used, for example when there are circular dependencies at construction time. + +### Usage + +You can use the component in your contracts like this: + +```rust +#[starknet::contract] +mod MyInitializableContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + impl InternalImpl = InitializableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + initializable: InitializableComponent::Storage, + param: felt252 + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + InitializableEvent: InitializableComponent::Event + } + + fn initializer(ref self: ContractState, some_param: felt252) { + // Makes the method callable only once + self.initializable.initialize(); + + // Initialization logic + self.param.write(some_param); + } +} +``` + + +This Initializable pattern should only be used in one function. + + +### Interface + +The component provides the following external functions as part of the `InitializableImpl` implementation: + +```rust +#[starknet::interface] +pub trait InitializableABI { + fn is_initialized() -> bool; +} +``` + +## Pausable + +The [Pausable](/contracts-cairo/2.x/api/security#PausableComponent) component allows contracts to implement an emergency stop mechanism. +This can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug. + +To become pausable, the contract should include `pause` and `unpause` functions (which should be protected). +For methods that should be available only when paused or not, insert calls to `[assert_paused](/contracts-cairo/2.x/api/security#PausableComponent-assert_paused)` and `[assert_not_paused](/contracts-cairo/2.x/api/security#PausableComponent-assert_not_paused)` +respectively. + +### Usage + +For example (using the [Ownable](/contracts-cairo/2.x/api/access#OwnableComponent) component for access control): + +```rust +#[starknet::contract] +mod MyPausableContract { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_security::PausableComponent; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // Pausable + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + pausable: PausableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + PausableEvent: PausableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + #[external(v0)] + fn pause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.pause(); + } + + #[external(v0)] + fn unpause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.unpause(); + } + + #[external(v0)] + fn when_not_paused(ref self: ContractState) { + self.pausable.assert_not_paused(); + // Do something + } + + #[external(v0)] + fn when_paused(ref self: ContractState) { + self.pausable.assert_paused(); + // Do something + } +} +``` + +### Interface + +The component provides the following external functions as part of the `PausableImpl` implementation: + +```rust +#[starknet::interface] +pub trait PausableABI { + fn is_paused() -> bool; +} +``` + +## Reentrancy Guard + +A [reentrancy attack](https://gus-tavo-guim.medium.com/reentrancy-attack-on-smart-contracts-how-to-identify-the-exploitable-and-an-example-of-an-attack-4470a2d8dfe4) occurs when the caller is able to obtain more resources than allowed by recursively calling a target’s function. + +### Usage + +Since Cairo does not support modifiers like Solidity, the [ReentrancyGuard](/contracts-cairo/2.x/api/security#ReentrancyGuardComponent) +component exposes two methods `[start](/contracts-cairo/2.x/api/security#ReentrancyGuardComponent-start)` and `[end](/contracts-cairo/2.x/api/security#ReentrancyGuardComponent-end)` to protect functions against reentrancy attacks. +The protected function must call `start` before the first function statement, and `end` before the return statement, as shown below: + +```rust +#[starknet::contract] +mod MyReentrancyContract { + use openzeppelin_security::ReentrancyGuardComponent; + + component!( + path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent + ); + + impl InternalImpl = ReentrancyGuardComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + reentrancy_guard: ReentrancyGuardComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ReentrancyGuardEvent: ReentrancyGuardComponent::Event + } + + #[external(v0)] + fn protected_function(ref self: ContractState) { + self.reentrancy_guard.start(); + + // Do something + + self.reentrancy_guard.end(); + } + + #[external(v0)] + fn another_protected_function(ref self: ContractState) { + self.reentrancy_guard.start(); + + // Do something + + self.reentrancy_guard.end(); + } +} +``` + + +The guard prevents the execution flow occurring inside `protected_function` +to call itself or `another_protected_function`, and vice versa. + diff --git a/docs/content/contracts-cairo/2.x/udc.mdx b/docs/content/contracts-cairo/2.x/udc.mdx new file mode 100644 index 00000000..db3344dc --- /dev/null +++ b/docs/content/contracts-cairo/2.x/udc.mdx @@ -0,0 +1,114 @@ +--- +title: Universal Deployer Contract +--- + +The Universal Deployer Contract (UDC) is a singleton smart contract that wraps the [deploy syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy) to expose it to any contract that doesn’t implement it, such as account contracts. You can think of it as a standardized generic factory for Starknet contracts. + +Since Starknet has no deployment transaction type, it offers a standardized way to deploy smart contracts by following the [Standard Deployer Interface](https://community.starknet.io/t/snip-deployer-contract-interface/2772) and emitting a [ContractDeployed](/contracts-cairo/2.x/api/udc#IUniversalDeployer-ContractDeployed) event. + +For details on the motivation and the decision making process, see the [Universal Deployer Contract proposal](https://community.starknet.io/t/universal-deployer-contract-proposal/1864). + +## UDC contract address + +The UDC is deployed at address `0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125` on Starknet sepolia and mainnet. + +## Interface + +```rust +#[starknet::interface] +pub trait IUniversalDeployer { + fn deploy_contract( + class_hash: ClassHash, + salt: felt252, + not_from_zero: bool, + calldata: Span + ) -> ContractAddress; +} +``` + +## Deploying a contract with the UDC + +First, [declare](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction) the target contract (if it’s not already declared). +Next, call the UDC’s `deploy_contract` method. +Here’s an implementation example in Cairo: + +```rust +use openzeppelin_utils::interfaces::{IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait}; + +const UDC_ADDRESS: felt252 = 0x04...; + +fn deploy() -> ContractAddress { + let dispatcher = IUniversalDeployerDispatcher { + contract_address: UDC_ADDRESS.try_into().unwrap() + }; + + // Deployment parameters + let class_hash = class_hash_const::< + 0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6 + >(); + let salt = 1234567879; + let not_from_zero = true; + let calldata = array![]; + + // The UDC returns the deployed contract address + dispatcher.deploy_contract(class_hash, salt, not_from_zero, calldata.span()) +} +``` + +## Deployment types + +The Universal Deployer Contract offers two types of addresses to deploy: origin-dependent and origin-independent. +As the names suggest, the origin-dependent type includes the deployer’s address in the address calculation, +whereas, the origin-independent type does not. +The `not_from_zero` boolean parameter ultimately determines the type of deployment. + + + + +When deploying a contract that uses `get_caller_address` in the constructor calldata, remember that the UDC, not the account, deploys that contract. +Therefore, querying `get_caller_address` in a contract’s constructor returns the UDC’s address, _not the account’s address_. + + + +### Origin-dependent + +By making deployments dependent upon the origin address, users can reserve a whole address space to prevent someone else from taking ownership of the address. + +Only the owner of the origin address can deploy to those addresses. + +Achieving this type of deployment necessitates that the origin sets `not_from_zero` to `true` in the [deploy_contract](/contracts-cairo/2.x/api/udc#UniversalDeployer-deploy_contract) call. +Under the hood, the function passes a modified salt to the `deploy_syscall`, which is the hash of the origin’s address with the given salt. + +To deploy a unique contract address pass: + +```js +let deployed_addr = udc.deploy_contract(class_hash, salt, true, calldata.span()); +``` + +### Origin-independent + +Origin-independent contract deployments create contract addresses independent of the deployer and the UDC instance. +Instead, only the class hash, salt, and constructor arguments determine the address. +This type of deployment enables redeployments of accounts and known systems across multiple networks. +To deploy a reproducible deployment, set `not_from_zero` to `false`. + +```rust +let deployed_addr = udc.deploy_contract(class_hash, salt, false, calldata.span()); +``` + +## Version changes + + +See the [previous Universal Deployer API](https://docs.starknet.io/architecture-and-concepts/accounts/#using-the-universal-deployer-contract) for the initial spec. + + +The latest iteration of the UDC includes some notable changes to the API which include: + +* `deployContract` method is replaced with the snake_case [deploy_contract](/contracts-cairo/2.x/api/udc#UniversalDeployer-deploy_contract). +* `unique` parameter is replaced with `not_from_zero` in both the `deploy_contract` method and [ContractDeployed](/contracts-cairo/2.x/api/udc#IUniversalDeployer-ContractDeployed) event. + +## Precomputing contract addresses + +This library offers utility functions written in Cairo to precompute contract addresses. +They include the generic [calculate_contract_address_from_deploy_syscall](/contracts-cairo/2.x/api/utilities#deployments-calculate_contract_address_from_deploy_syscall) as well as the UDC-specific [calculate_contract_address_from_udc](/contracts-cairo/2.x/api/utilities#deployments-calculate_contract_address_from_udc). +Check out the [deployments](/contracts-cairo/2.x/api/utilities#deployments) for more information. diff --git a/docs/content/contracts-cairo/2.x/upgrades.mdx b/docs/content/contracts-cairo/2.x/upgrades.mdx new file mode 100644 index 00000000..38ad50c0 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/upgrades.mdx @@ -0,0 +1,127 @@ +--- +title: Upgrades +--- + +In different blockchains, multiple patterns have been developed for making a contract upgradeable including the widely adopted proxy patterns. + +Starknet has native upgradeability through a syscall that updates the contract source code, removing [the need for proxies](#proxies-in-starknet). + + +Make sure you follow [our security recommendations](#security) before upgrading. + + +## Replacing contract classes + +To better comprehend how upgradeability works in Starknet, it’s important to understand the difference between a contract and its contract class. + +[Contract Classes](https://docs.starknet.io/architecture-and-concepts/smart-contracts/contract-classes/) represent the source code of a program. All +contracts are associated to a class, and many contracts can be instances of the same one. Classes are usually represented by +a [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/), and before a contract of a class can be deployed, +the class hash needs to be declared. + +### `replace_class_syscall` + +The `[replace_class](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#replace_class)` syscall allows a contract to +update its source code by replacing its class hash once deployed. + +```rust +/// Upgrades the contract source code to the new contract class. +fn upgrade(new_class_hash: ClassHash) { + let CLASS_HASH_CANNOT_BE_ZERO: felt252 = 0x1; + assert(!new_class_hash.is_zero(), CLASS_HASH_CANNOT_BE_ZERO); + starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); +} +``` + + +If a contract is deployed without this mechanism, its class hash can still be replaced through [library calls](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#library_call). + + +## `Upgradeable` component + +OpenZeppelin Contracts for Cairo provides [Upgradeable](https://github.com/OpenZeppelin/cairo-contracts/blob/release-v{{umbrella_version}}/packages/upgrades/src/upgradeable.cairo) to add upgradeability support to your contracts. + +### Usage + +Upgrades are often very sensitive operations, and some form of access control is usually required to +avoid unauthorized upgrades. The [Ownable](./access#ownership-and-ownable) module is used in this example. + + +We will be using the following module to implement the [IUpgradeable](/contracts-cairo/2.x/api/upgrades#IUpgradeable) interface described in the API Reference section. + + +```rust +#[starknet::contract] +mod UpgradeableContract { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_upgrades::interface::IUpgradeable; + use starknet::ClassHash; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + // This function can only be called by the owner + self.ownable.assert_only_owner(); + + // Replace the class hash upgrading the contract + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` + +## Security + +Upgrades can be very sensitive operations, and security should always be top of mind while performing one. Please make sure you thoroughly review the changes and their consequences before upgrading. Some aspects to consider are: + +* API changes that might affect integration. For example, changing an external function’s arguments might break existing contracts or offchain systems calling your contract. +* Storage changes that might result in lost data (e.g. changing a storage slot name, making existing storage inaccessible). +* Collisions (e.g. mistakenly reusing the same storage slot from another component) are also possible, although less likely if best practices are followed, for example prepending storage variables with the component’s name (e.g. `ERC20_balances`). +* Always check for [backwards compatibility](./backwards-compatibility) before upgrading between versions of OpenZeppelin Contracts. + +## Proxies in Starknet + +Proxies enable different patterns such as upgrades and clones. But since Starknet achieves the same in different ways is that there’s no support to implement them. + +In the case of contract upgrades, it is achieved by simply changing the contract’s class hash. As of clones, contracts already are like clones of the class they implement. + +Implementing a proxy pattern in Starknet has an important limitation: there is no fallback mechanism to be used +for redirecting every potential function call to the implementation. This means that a generic proxy contract +can’t be implemented. Instead, a limited proxy contract can implement specific functions that forward +their execution to another contract class. +This can still be useful for example to upgrade the logic of some functions. diff --git a/docs/content/contracts-cairo/2.x/utils/constants.js b/docs/content/contracts-cairo/2.x/utils/constants.js new file mode 100644 index 00000000..d7da4149 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/utils/constants.js @@ -0,0 +1,19 @@ +export const UMBRELLA_VERSION = "2.0.0"; +export const CLASS_HASH_SCARB_VERSION = "2.11.4"; + +export const CLASS_HASHES = { + AccountUpgradeableClassHash: + "0x079a9a12fdfa0481e8d8d46599b90226cd7247b2667358bb00636dd864002314", + ERC20UpgradeableClassHash: + "0x065daa9c6005dcbccb0571ffdf530e2e263d1ff00eac2cbd66b2d0fa0871dafa", + ERC721UpgradeableClassHash: + "0x06d1cd9d8c2008d36bd627e204c3e5f565d4e632de4e50b36d2388c7ba7a64ce", + ERC1155UpgradeableClassHash: + "0x036d453774916578336db8f5f18257f0211011270a5c31adf3a2bd86416943b7", + EthAccountUpgradeableClassHash: + "0x070177fca30a0a9025465f16f8174d4ea220f61bf44cb1beecb89459fe966285", + UniversalDeployerClassHash: + "0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8", + VestingWalletClassHash: + "0x010a786d4e5f74d68e0a500aeadbf7a81486f069c06afa242a050a1a09ac42f0", +}; diff --git a/docs/content/contracts-cairo/2.x/utils/replacements.ts b/docs/content/contracts-cairo/2.x/utils/replacements.ts new file mode 100644 index 00000000..c3a1695e --- /dev/null +++ b/docs/content/contracts-cairo/2.x/utils/replacements.ts @@ -0,0 +1,10 @@ +import { CLASS_HASHES, CLASS_HASH_SCARB_VERSION, UMBRELLA_VERSION } from "./constants"; + +export const REPLACEMENTS = { + include: ['**/content/contracts-cairo/2.x/**/*.mdx'], + replacements: { + umbrella_version: UMBRELLA_VERSION, + class_hash_scarb_version: CLASS_HASH_SCARB_VERSION, + ...CLASS_HASHES, + } +} diff --git a/docs/content/contracts-cairo/2.x/wizard.mdx b/docs/content/contracts-cairo/2.x/wizard.mdx new file mode 100644 index 00000000..84f581f5 --- /dev/null +++ b/docs/content/contracts-cairo/2.x/wizard.mdx @@ -0,0 +1,12 @@ +--- +title: Wizard for Cairo +--- + +Not sure where to start? Use the interactive generator below to bootstrap your +contract and learn about the components offered in OpenZeppelin Contracts for Cairo. + + +We strongly recommend checking the [Components](./components) section to understand how to extend from our library. + + + diff --git a/docs/content/contracts-cairo/alpha/access.mdx b/docs/content/contracts-cairo/alpha/access.mdx new file mode 100644 index 00000000..e73654dc --- /dev/null +++ b/docs/content/contracts-cairo/alpha/access.mdx @@ -0,0 +1,515 @@ +--- +title: Access +--- + +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 +[steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7/). + +## 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 for Cairo provides [OwnableComponent](/contracts-cairo/alpha/api/access#OwnableComponent) for implementing ownership in your contracts. + +### Usage + +Integrating this component into a contract first requires assigning an owner. +The implementing contract’s constructor should set the initial owner by passing the owner’s address to Ownable’s +[`initializer`](/contracts-cairo/alpha/api/access#OwnableComponent-initializer) like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_access::ownable::OwnableComponent; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl InternalImpl = OwnableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + // Set the initial owner of the contract + self.ownable.initializer(owner); + } + + (...) +} +``` + +To restrict a function’s access to the owner only, add in the `assert_only_owner` method: + +```rust +#[starknet::contract] +mod MyContract { + (...) + + #[external(v0)] + fn only_owner_allowed(ref self: ContractState) { + // This function can only be called by the owner + self.ownable.assert_only_owner(); + + (...) + } +} +``` + +### Interface + +This is the full interface of the `OwnableMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait OwnableABI { + // IOwnable + fn owner() -> ContractAddress; + fn transfer_ownership(new_owner: ContractAddress); + fn renounce_ownership(); + + // IOwnableCamelOnly + fn transferOwnership(newOwner: ContractAddress); + fn renounceOwnership(); +} +``` + +Ownable also lets you: + +* `transfer_ownership` from the owner account to a new one, and +* `renounce_ownership` for the owner to relinquish this administrative privilege, a common pattern +after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `assert_only_owner` +will no longer be callable! + + +### Two step transfer + +The component also offers a more robust way of transferring ownership via the +[OwnableTwoStepImpl](/contracts-cairo/alpha/api/access#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl) implementation. A two step transfer mechanism helps +to prevent unintended and irreversible owner transfers. Simply replace the `OwnableMixinImpl` +with its respective two step variant: + +```rust +#[abi(embed_v0)] +impl OwnableTwoStepMixinImpl = OwnableComponent::OwnableTwoStepMixinImpl; +``` + +#### Interface + +This is the full interface of the two step `OwnableTwoStepMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait OwnableTwoStepABI { + // IOwnableTwoStep + fn owner() -> ContractAddress; + fn pending_owner() -> ContractAddress; + fn accept_ownership(); + fn transfer_ownership(new_owner: ContractAddress); + fn renounce_ownership(); + + // IOwnableTwoStepCamelOnly + fn pendingOwner() -> ContractAddress; + fn acceptOwnership(); + fn transferOwnership(newOwner: ContractAddress); + fn renounceOwnership(); +} +``` + +## Role-Based `AccessControl` + +While the simplicity of ownership can be useful for simple systems or quick prototyping, different levels of +authorization are often needed. You may want for an account to have permission to ban users from a system, but not +create new tokens. [Role-Based Access Control (RBAC)](https://en.wikipedia.org/wiki/Role-based_access_control) 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 [`assert_only_owner`](/contracts-cairo/alpha/api/access#OwnableComponent-assert_only_owner). This check can be enforced through [`assert_only_role`](/contracts-cairo/alpha/api/access#AccessControlComponent-assert_only_role). +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. + +### Usage + +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. See [Creating role identifiers](#creating-role-identifiers) for information +on creating identifiers. + +Here’s a simple example of implementing [AccessControl](/contracts-cairo/alpha/api/access#AccessControlComponent) on a portion of an ERC20 token contract which defines +and sets a 'minter' role: + +```rust +const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); + +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + use super::MINTER_ROLE; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress, + minter: ContractAddress + ) { + // ERC20-related initialization + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(MINTER_ROLE, minter); + } + + /// This function can only be called by a minter. + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(MINTER_ROLE); + self.erc20.mint(recipient, amount); + } +} +``` + + +Make sure you fully understand how [AccessControl](/contracts-cairo/alpha/api/access#AccessControlComponent) 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](/contracts-cairo/alpha/api/access#OwnableComponent). Where [AccessControl](/contracts-cairo/alpha/api/access#AccessControlComponent) shines the most is in scenarios where granular +permissions are required, which can be implemented by defining _multiple_ roles. + +Let’s augment our ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: + +```rust +const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); +const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); + +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + use super::{MINTER_ROLE, BURNER_ROLE}; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress, + minter: ContractAddress, + burner: ContractAddress + ) { + // ERC20-related initialization + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(MINTER_ROLE, minter); + self.accesscontrol._grant_role(BURNER_ROLE, burner); + } + + /// This function can only be called by a minter. + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(MINTER_ROLE); + self.erc20.mint(recipient, amount); + } + + /// This function can only be called by a burner. + #[external(v0)] + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(BURNER_ROLE); + self.erc20.burn(account, amount); + } +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 roles + +The ERC20 token example above uses [`_grant_role`](/contracts-cairo/alpha/api/access#AccessControlComponent-_grant_role), +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 [`assert_only_role`](/contracts-cairo/alpha/api/access#AccessControlComponent-assert_only_role) 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 +[`grant_role`](/contracts-cairo/alpha/api/access#AccessControlComponent-grant_role) and +[`revoke_role`](/contracts-cairo/alpha/api/access#AccessControlComponent-revoke_role) 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 with the role identifier +of `0`, 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 +[`set_role_admin`](/contracts-cairo/alpha/api/access#AccessControlComponent-set_role_admin) 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 [AccessControlDefaultAdminRules](/contracts-cairo/alpha/api/access#AccessControlDefaultAdminRulesComponent), 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 in between steps. + +Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: + +```rust +const MINTER_ROLE: felt252 = selector!("MINTER_ROLE"); +const BURNER_ROLE: felt252 = selector!("BURNER_ROLE"); + +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_access::accesscontrol::DEFAULT_ADMIN_ROLE; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + use super::{MINTER_ROLE, BURNER_ROLE}; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress, + admin: ContractAddress + ) { + // ERC20-related initialization + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + + // AccessControl-related initialization + self.accesscontrol.initializer(); + self.accesscontrol._grant_role(DEFAULT_ADMIN_ROLE, admin); + } + + /// This function can only be called by a minter. + #[external(v0)] + fn mint(ref self: ContractState, recipient: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(MINTER_ROLE); + self.erc20.mint(recipient, amount); + } + + /// This function can only be called by a burner. + #[external(v0)] + fn burn(ref self: ContractState, account: ContractAddress, amount: u256) { + self.accesscontrol.assert_only_role(BURNER_ROLE); + self.erc20.burn(account, amount); + } +} +``` + + +The `grant_role` and `revoke_role` functions are automatically exposed as `external` functions +from the `AccessControlImpl` by leveraging the `#[abi(embed_v0)]` annotation. + + +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 the 'admin', that +same account can call `grant_role` to give minting or burning permission, and `revoke_role` 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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), +where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. + +### Creating role identifiers + +In the Solidity implementation of AccessControl, contracts generally refer to the +[keccak256 hash](https://docs.soliditylang.org/en/latest/units-and-global-variables.html?highlight=keccak256#mathematical-and-cryptographic-functions) +of a role as the role identifier. + +For example: + +```rust +bytes32 public constant SOME_ROLE = keccak256("SOME_ROLE") +``` + +These identifiers take up 32 bytes (256 bits). + +Cairo field elements (`felt252`) store a maximum of 252 bits. +With this discrepancy, this library maintains an agnostic stance on how contracts should create identifiers. +Some ideas to consider: + +* Use [sn_keccak](https://docs.starknet.io/architecture-and-concepts/cryptography/#starknet_keccak) instead. +* Use Cairo friendly hashing algorithms like Poseidon, which are implemented in the +[Cairo corelib](https://github.com/starkware-libs/cairo/blob/main/corelib/src/poseidon.cairo). + + +The `selector!` macro can be used to compute [sn_keccak](https://docs.starknet.io/architecture-and-concepts/cryptography/#starknet_keccak) in Cairo. + + +### Interface + +This is the full interface of the `AccessControlMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait AccessControlABI { + // IAccessControl + fn has_role(role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(role: felt252) -> felt252; + fn grant_role(role: felt252, account: ContractAddress); + fn revoke_role(role: felt252, account: ContractAddress); + fn renounce_role(role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(role: felt252) -> felt252; + fn grantRole(role: felt252, account: ContractAddress); + fn revokeRole(role: felt252, account: ContractAddress); + fn renounceRole(role: felt252, account: ContractAddress); + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +`AccessControl` also lets you `renounce_role` from the calling account. +The method expects an account as input as an extra security measure, to ensure you are +not renouncing a role from an unintended account. diff --git a/docs/content/contracts-cairo/alpha/accounts.mdx b/docs/content/contracts-cairo/alpha/accounts.mdx new file mode 100644 index 00000000..442c6cf3 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/accounts.mdx @@ -0,0 +1,504 @@ +--- +title: Accounts +--- + +Unlike Ethereum where accounts are derived from a private key, all Starknet accounts are contracts. This means there’s no Externally Owned Account (EOA) +concept on Starknet. + +Instead, the network features native account abstraction and signature validation happens at the contract level. + +For a general overview of account abstraction, see +[Starknet’s documentation](https://docs.starknet.io/architecture-and-concepts/accounts/introduction/). +A more detailed discussion on the topic can be found in +[Starknet Shaman’s forum](https://community.starknet.io/t/starknet-account-abstraction-model-part-1/781). + + +For detailed information on the usage and implementation check the [API Reference](/contracts-cairo/alpha/api/account) section. + + +## What is an account? + +Accounts in Starknet are smart contracts, and so they can be deployed and interacted +with like any other contract, and can be extended to implement any custom logic. However, an account is a special type +of contract that is used to validate and execute transactions. For this reason, it must implement a set of entrypoints +that the protocol uses for this execution flow. The [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) proposal defines a standard interface for accounts, +supporting this execution flow and interoperability with DApps in the ecosystem. + +### ISRC6 Interface + +```rust +/// Represents a call to a target contract function. +struct Call { + to: ContractAddress, + selector: felt252, + calldata: Span +} + +/// Standard Account Interface +#[starknet::interface] +pub trait ISRC6 { + /// Executes a transaction through the account. + fn __execute__(calls: Array); + + /// Asserts whether the transaction is valid to be executed. + fn __validate__(calls: Array) -> felt252; + + /// Asserts whether a given signature for a given hash is valid. + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; +} +``` + + +The `calldata` member of the `Call` struct in the accounts has been updated to `Span` for optimization +purposes, but the interface ID remains the same for backwards compatibility. This inconsistency will be fixed in future releases. + + +[SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) adds the `is_valid_signature` method. This method is not used by the protocol, but it’s useful for +DApps to verify the validity of signatures, supporting features like Sign In with Starknet. + +SNIP-6 also defines that compliant accounts must implement the SRC5 interface following [SNIP-5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md), as +a mechanism for detecting whether a contract is an account or not through introspection. + +### ISRC5 Interface + +```rust +/// Standard Interface Detection +#[starknet::interface] +pub trait ISRC5 { + /// Queries if a contract implements a given interface. + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +[SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) compliant accounts must return `true` when queried for the ISRC6 interface ID. + +Even though these interfaces are not enforced by the protocol, it’s recommended to implement them for enabling +interoperability with the ecosystem. + +### Protocol-level methods + +The Starknet protocol uses a few entrypoints for abstracting the accounts. We already mentioned the first two +as part of the ISRC6 interface, and both are required for enabling accounts to be used for executing transactions. The rest are optional: + +1. `__validate__` verifies the validity of the transaction to be executed. This is usually used to validate signatures, +but the entrypoint implementation can be customized to feature any validation mechanism [with some limitations](https://docs.starknet.io/architecture-and-concepts/accounts/account-functions/#limitations_of_validation). +2. `__execute__` executes the transaction if the validation is successful. +3. `__validate_declare__` optional entrypoint similar to `__validate__` but for transactions +meant to declare other contracts. +4. `__validate_deploy__` optional entrypoint similar to `__validate__` but meant for [counterfactual deployments](./guides/deployment). + + +Although these entrypoints are available to the protocol for its regular transaction flow, they can also be called like any other method. + + +## Starknet Account + +Starknet native account abstraction pattern allows for the creation of custom accounts with different validation schemes, but +usually most account implementations validate transactions using the [Stark curve](https://docs.starknet.io/architecture-and-concepts/cryptography/#stark-curve) which is the most efficient way +of validating signatures since it is a STARK-friendly curve. + +OpenZeppelin Contracts for Cairo provides [AccountComponent](/contracts-cairo/alpha/api/account#AccountComponent) for implementing this validation scheme. + +### Usage + +Constructing an account contract requires integrating both [AccountComponent](/contracts-cairo/alpha/api/account#AccountComponent) and [SRC5Component](/contracts-cairo/alpha/api/introspection#SRC5Component). The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. Here’s an example of a basic contract: + +```rust +#[starknet::contract(account)] +mod MyAccount { + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Account Mixin + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +``` + +### Interface + +This is the full interface of the `AccountMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait AccountABI { + // ISRC6 + fn __execute__(calls: Array); + fn __validate__(calls: Array) -> felt252; + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IDeclarer + fn __validate_declare__(class_hash: felt252) -> felt252; + + // IDeployable + fn __validate_deploy__( + class_hash: felt252, contract_address_salt: felt252, public_key: felt252 + ) -> felt252; + + // IPublicKey + fn get_public_key() -> felt252; + fn set_public_key(new_public_key: felt252, signature: Span); + + // ISRC6CamelOnly + fn isValidSignature(hash: felt252, signature: Array) -> felt252; + + // IPublicKeyCamel + fn getPublicKey() -> felt252; + fn setPublicKey(newPublicKey: felt252, signature: Span); +} +``` + +## Ethereum Account + +Besides the Stark-curve account, OpenZeppelin Contracts for Cairo also offers Ethereum-flavored accounts that use the [secp256k1](https://en.bitcoin.it/wiki/Secp256k1) curve for signature validation. +For this the [EthAccountComponent](/contracts-cairo/alpha/api/account#EthAccountComponent) must be used. + +### Usage + +Constructing a secp256k1 account contract also requires integrating both [EthAccountComponent](/contracts-cairo/alpha/api/account#EthAccountComponent) and [SRC5Component](/contracts-cairo/alpha/api/introspection#SRC5Component). +The contract should also set up the constructor to initialize the public key that will be used as the account’s signer. +Here’s an example of a basic contract: + +```rust +#[starknet::contract(account)] +mod MyEthAccount { + use openzeppelin_account::EthAccountComponent; + use openzeppelin_interfaces::accounts::EthPublicKey; + use openzeppelin_introspection::src5::SRC5Component; + use starknet::ClassHash; + + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // EthAccount Mixin + #[abi(embed_v0)] + impl EthAccountMixinImpl = + EthAccountComponent::EthAccountMixinImpl; + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + eth_account: EthAccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + EthAccountEvent: EthAccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: EthPublicKey) { + self.eth_account.initializer(public_key); + } +} +``` + +### Interface + +This is the full interface of the `EthAccountMixinImpl` implementation: + +```rust +#[starknet::interface] +pub trait EthAccountABI { + // ISRC6 + fn __execute__(calls: Array); + fn __validate__(calls: Array) -> felt252; + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IDeclarer + fn __validate_declare__(class_hash: felt252) -> felt252; + + // IEthDeployable + fn __validate_deploy__( + class_hash: felt252, contract_address_salt: felt252, public_key: EthPublicKey + ) -> felt252; + + // IEthPublicKey + fn get_public_key() -> EthPublicKey; + fn set_public_key(new_public_key: EthPublicKey, signature: Span); + + // ISRC6CamelOnly + fn isValidSignature(hash: felt252, signature: Array) -> felt252; + + // IEthPublicKeyCamel + fn getPublicKey() -> EthPublicKey; + fn setPublicKey(newPublicKey: EthPublicKey, signature: Span); +} + +``` + +## Deploying an account + +In Starknet there are two ways of deploying smart contracts: using the `deploy_syscall` and doing +counterfactual deployments. +The former can be easily done with the [Universal Deployer Contract (UDC)](./udc), a contract that +wraps and exposes the `deploy_syscall` to provide arbitrary deployments through regular contract calls. +But if you don’t have an account to invoke it, you will probably want to use the latter. + +To do counterfactual deployments, you need to implement another protocol-level entrypoint named +`__validate_deploy__`. Check the [counterfactual deployments](./guides/deployment) guide to learn how. + +## Sending transactions + +Let’s now explore how to send transactions through these accounts. + +### Starknet Account + +First, let’s take the example account we created before and deploy it: + +```rust +#[starknet::contract(account)] +mod MyAccount { + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Account Mixin + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +``` + +To deploy the account variant, compile the contract and declare the class hash because custom accounts are likely not declared. +This means that you’ll need an account already deployed. + +Next, create the account JSON with Starknet Foundry’s [custom account setup](https://foundry-rs.github.io/starknet-foundry/starknet/account.html#custom-account-contract) and include the `--class-hash` flag with the declared class hash. +The flag enables custom account variants. + + +The following examples use `sncast` [v0.23.0](https://github.com/foundry-rs/starknet-foundry/releases/tag/v0.23.0). + + +```bash +$ sncast \ + --url http://127.0.0.1:5050 \ + account create \ + --name my-custom-account \ + --class-hash 0x123456... +``` + +This command will output the precomputed contract address and the recommended `max-fee`. +To counterfactually deploy the account, send funds to the address and then deploy the custom account. + +```bash +$ sncast \ + --url http://127.0.0.1:5050 \ + account deploy \ + --name my-custom-account +``` + +Once the account is deployed, set the `--account` flag with the custom account name to send transactions from that account. + +```bash +$ sncast \ + --account my-custom-account \ + --url http://127.0.0.1:5050 \ + invoke \ + --contract-address 0x123... \ + --function "some_function" \ + --calldata 1 2 3 +``` + +### Ethereum Account + +First, let’s take the example account we created before and deploy it: + +```rust +#[starknet::contract(account)] +mod MyEthAccount { + use openzeppelin_account::EthAccountComponent; + use openzeppelin_interfaces::accounts::EthPublicKey; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: EthAccountComponent, storage: eth_account, event: EthAccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // EthAccount Mixin + #[abi(embed_v0)] + impl EthAccountMixinImpl = + EthAccountComponent::EthAccountMixinImpl; + impl EthAccountInternalImpl = EthAccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + eth_account: EthAccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + EthAccountEvent: EthAccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: EthPublicKey) { + self.eth_account.initializer(public_key); + } +} +``` + +Special tooling is required in order to deploy and send transactions with an Ethereum-flavored account contract. +The following examples utilize the [StarknetJS](https://www.starknetjs.com/) library. + +Compile and declare the contract on the target network. +Next, precompute the EthAccount contract address using the declared class hash. + + +The following examples use unreleased features from StarknetJS (`starknetjs@next`) at commit [d002baea0abc1de3ac6e87a671f3dec3757437b3](https://github.com/starknet-io/starknet.js/commit/d002baea0abc1de3ac6e87a671f3dec3757437b3). + + +```javascript +import * as dotenv from 'dotenv'; +import { CallData, EthSigner, hash } from 'starknet'; +import { ABI as ETH_ABI } from '../abis/eth_account.js'; +dotenv.config(); + +const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); +const ethPubKey = await ethSigner.getPubKey(); +const ethAccountClassHash = ''; +const ethCallData = new CallData(ETH_ABI); +const ethAccountConstructorCalldata = ethCallData.compile('constructor', { + public_key: ethPubKey +}) +const salt = '0x12345'; +const deployerAddress = '0x0'; +const ethContractAddress = hash.calculateContractAddressFromHash( + salt, + ethAccountClassHash, + ethAccountConstructorCalldata, + deployerAddress +); +console.log('Pre-calculated EthAccount address: ', ethContractAddress); +``` + +Send funds to the pre-calculated EthAccount address and deploy the contract. + +```javascript +import * as dotenv from 'dotenv'; +import { Account, CallData, EthSigner, RpcProvider, stark } from 'starknet'; +import { ABI as ETH_ABI } from '../abis/eth_account.js'; +dotenv.config(); + +const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); +const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); +const ethPubKey = await ethSigner.getPubKey(); +const ethAccountAddress = '' +const ethAccount = new Account(provider, ethAccountAddress, ethSigner); + +const ethAccountClassHash = '' +const ethCallData = new CallData(ETH_ABI); +const ethAccountConstructorCalldata = ethCallData.compile('constructor', { + public_key: ethPubKey +}) +const salt = '0x12345'; +const deployPayload = { + classHash: ethAccountClassHash, + constructorCalldata: ethAccountConstructorCalldata, + addressSalt: salt, +}; + +const { suggestedMaxFee: feeDeploy } = await ethAccount.estimateAccountDeployFee(deployPayload); +const { transaction_hash, contract_address } = await ethAccount.deployAccount( + deployPayload, + { maxFee: stark.estimatedFeeToMaxFee(feeDeploy, 100) } +); +await provider.waitForTransaction(transaction_hash); +console.log('EthAccount deployed at: ', contract_address); +``` + +Once deployed, connect the EthAccount instance to the target contract which enables calls to come from the EthAccount. +Here’s what an ERC20 transfer from an EthAccount looks like. + +```javascript +import * as dotenv from 'dotenv'; +import { Account, RpcProvider, Contract, EthSigner } from 'starknet'; +dotenv.config(); + +const provider = new RpcProvider({ nodeUrl: process.env.API_URL }); +const ethSigner = new EthSigner(process.env.ETH_PRIVATE_KEY); +const ethAccountAddress = '' +const ethAccount = new Account(provider, ethAccountAddress, ethSigner); + +const erc20 = new Contract(compiledErc20.abi, erc20Address, provider); + +erc20.connect(ethAccount); + +const transferCall = erc20.populate('transfer', { + recipient: recipient.address, + amount: 50n +}); +const tx = await erc20.transfer( + transferCall.calldata, { maxFee: 900_000_000_000_000 } +); +await provider.waitForTransaction(tx.transaction_hash); +``` diff --git a/docs/content/contracts-cairo/alpha/api/access.mdx b/docs/content/contracts-cairo/alpha/api/access.mdx new file mode 100644 index 00000000..002a41fb --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/access.mdx @@ -0,0 +1,1749 @@ +--- +title: Access Control +--- + +This crate provides ways to restrict who can access the functions of a contract or when they can do it. + +- [Ownable](#OwnableComponent) is a simple mechanism with a single "owner" role that can be assigned to a single account. This mechanism can be useful in simple scenarios, but fine grained access needs are likely to outgrow it. +- [AccessControl](#AccessControlComponent) provides a general role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts. + +## Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `IAccessControl` [toc] [#IAccessControl] + + +```rust +use openzeppelin_interfaces::accesscontrol::IAccessControl; +``` + +External interface of AccessControl. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x23700be02858dbe2ac4dc9c9f66d0b6b0ed81ec7f970ca6844500a56ff61751 +``` + +Functions + +- [`has_role(role, account)`](#IAccessControl-has_role) +- [`get_role_admin(role)`](#IAccessControl-get_role_admin) +- [`grant_role(role, account)`](#IAccessControl-grant_role) +- [`revoke_role(role, account)`](#IAccessControl-revoke_role) +- [`renounce_role(role, account)`](#IAccessControl-renounce_role) + +Events + +- [`RoleAdminChanged(role, previous_admin_role, new_admin_role)`](#IAccessControl-RoleAdminChanged) +- [`RoleGranted(role, account, sender)`](#IAccessControl-RoleGranted) +- [`RoleRevoked(role, account, sender)`](#IAccessControl-RoleRevoked) + + +#### Functions [!toc] [#IAccessControl-Functions] + + +Returns whether `account` can act as `role`. + + + +Returns the admin role that controls `role`. See [grant\_role](#IAccessControl-grant_role) and [revoke\_role](#IAccessControl-revoke_role). + +To change a role’s admin, use [set\_role\_admin](#AccessControlComponent-set_role_admin). + + + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [RoleGranted](#IAccessControl-RoleGranted) event. + +Requirements: + +- the caller must have `role`'s admin role. + + + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must have `role`'s admin role. + + + +Revokes `role` from the calling account. + +Roles are often managed via [grant\_role](#IAccessControl-grant_role) and [revoke\_role](#IAccessControl-revoke_role). This function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been granted `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must be `account`. + + +#### Events [!toc] [#IAccessControl-Events] + + +Emitted when `new_admin_role` is set as `role`'s admin role, replacing `previous_admin_role` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite [RoleAdminChanged](#IAccessControl-RoleAdminChanged) not being emitted signaling this. + + + +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an account with the admin role or the deployer address if `_grant_role` is called from the constructor. + + + +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + +- if using `revoke_role`, it is the admin role bearer. +- if using `renounce_role`, it is the role bearer (i.e. `account`). + + +### `IAccessControlWithDelay` [toc] [#IAccessControlWithDelay] + + +```rust +use openzeppelin_interfaces::accesscontrol::IAccessControlWithDelay; +``` + +External interface for the extended `AccessControlWithDelay` functionality. + +Functions + +- [`get_role_status(role, account)`](#IAccessControlWithDelay-get_role_status) +- [`grant_role_with_delay(role, account, delay)`](#IAccessControlWithDelay-grant_role_with_delay) + +Events + +- [`RoleGrantedWithDelay(role, account, sender, delay)`](#IAccessControlWithDelay-RoleGrantedWithDelay) + +#### Functions [!toc] [#IAccessControlWithDelay-Functions] + + +Returns the account’s status for the given role. The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +Requirements: + +- The caller must have `role`'s admin role. +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay) event. + + +#### Events [!toc] [#IAccessControlWithDelay-Events] + + +Emitted when `account` is granted `role` with a delay. + +`sender` is the account that originated the contract call, an account with the admin role or the deployer address if [\_grant\_role\_with\_delay](#AccessControlComponent-_grant_role_with_delay) is called from the constructor. + + +### `IAccessControlDefaultAdminRules` [toc] [#IAccessControlDefaultAdminRules] + + +```rust +use openzeppelin_interfaces::accesscontrol_default_admin_rules::IAccessControlDefaultAdminRules; +``` + +External interface of AccessControlDefaultAdminRules declared to support [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) detection. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x3509b3083c9586afe5dae781146b0608c3846870510f8d4d21ae38676cc33eb +``` + +Functions + +- [`default_admin()`](#IAccessControlDefaultAdminRules-default_admin) +- [`pending_default_admin()`](#IAccessControlDefaultAdminRules-pending_default_admin) +- [`default_admin_delay()`](#IAccessControlDefaultAdminRules-default_admin_delay) +- [`pending_default_admin_delay()`](#IAccessControlDefaultAdminRules-pending_default_admin_delay) +- [`begin_default_admin_transfer(new_admin)`](#IAccessControlDefaultAdminRules-begin_default_admin_transfer) +- [`cancel_default_admin_transfer()`](#IAccessControlDefaultAdminRules-cancel_default_admin_transfer) +- [`accept_default_admin_transfer()`](#IAccessControlDefaultAdminRules-accept_default_admin_transfer) +- [`change_default_admin_delay(new_delay)`](#IAccessControlDefaultAdminRules-change_default_admin_delay) +- [`rollback_default_admin_delay()`](#IAccessControlDefaultAdminRules-rollback_default_admin_delay) +- [`default_admin_delay_increase_wait()`](#IAccessControlDefaultAdminRules-default_admin_delay_increase_wait) + +Events + +- [`DefaultAdminTransferScheduled(new_admin, accept_schedule)`](#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled) +- [`DefaultAdminTransferCanceled()`](#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled) +- [`DefaultAdminDelayChangeScheduled(new_delay, effect_schedule)`](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled) +- [`DefaultAdminDelayChangeCanceled()`](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled) + +#### Functions [!toc] [#IAccessControlDefaultAdminRules-Functions] + + +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + + + +Returns a tuple of a `new_admin` and an `accept_schedule`. + +Returns a tuple of a `new_admin` and an `accept_schedule`. + +After the `accept_schedule` passes, the `new_admin` will be able to accept the `default_admin` role by calling [accept\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-accept_default_admin_transfer), completing the role transfer. + +A zero value only in `accept_schedule` indicates no pending admin transfer. + + +A zero address `new_admin` means that `default_admin` is being renounced. + + + + +Returns the delay required to schedule the acceptance of a `default_admin` transfer started. + +This delay will be added to the current timestamp when calling [begin\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-begin_default_admin_transfer) to set the acceptance schedule. + + +If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this function return the new delay. + + +See [change\_default\_admin\_delay](#IAccessControlDefaultAdminRules-change_default_admin_delay). + + + +Returns a tuple of `new_delay` and an `effect_schedule`. + +After the `effect_schedule` passes, the `new_delay` will get into effect immediately for every new `default_admin` transfer started with [begin\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-begin_default_admin_transfer). + +A zero value only in `effect_schedule` indicates no pending delay change. + +A zero value only for `new_delay` means that the next [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) will be zero after the effect schedule. + + + +Starts a `default_admin` transfer by setting a [pending\_default\_admin](#IAccessControlDefaultAdminRules-pending_default_admin) scheduled for acceptance after the current timestamp plus a [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay). + +Requirements: + +- Only can be called by the current `default_admin`. + +Emits a [DefaultAdminTransferScheduled](#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled) event. + + + +Cancels a `default_admin` transfer previously started with [begin\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-begin_default_admin_transfer). + +A [pending\_default\_admin](#IAccessControlDefaultAdminRules-pending_default_admin) not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current `default_admin`. + +May emit a [DefaultAdminTransferCanceled](#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled) event. + + + +Completes a `default_admin` transfer previously started with [begin\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-begin_default_admin_transfer). + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` must be granted to the caller. +- `DEFAULT_ADMIN_ROLE` must be revoked from the previous holder. +- [pending\_default\_admin](#IAccessControlDefaultAdminRules-pending_default_admin) must be reset to zero value. + +Requirements: + +- Only can be called by the [pending\_default\_admin](#IAccessControlDefaultAdminRules-pending_default_admin)'s `new_admin`. +- The [pending\_default\_admin](#IAccessControlDefaultAdminRules-pending_default_admin)'s `accept_schedule` should’ve passed. + + + +Initiates a [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) update by setting a [pending\_default\_admin\_delay](#IAccessControlDefaultAdminRules-pending_default_admin_delay) scheduled to take effect after the current timestamp plus a [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay). + +This function guarantees that any call to [begin\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-begin_default_admin_transfer) done between the timestamp this method is called and the [pending\_default\_admin\_delay](#IAccessControlDefaultAdminRules-pending_default_admin_delay) effect schedule will use the current [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) set before calling. + +The [pending\_default\_admin\_delay](#IAccessControlDefaultAdminRules-pending_default_admin_delay)'s effect schedule is defined in a way that waiting until the schedule and then calling [begin\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-begin_default_admin_transfer) with the new delay will take at least the same as another `default_admin` complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + new delay` capped by [default\_admin\_delay\_increase\_wait](#IAccessControlDefaultAdminRules-default_admin_delay_increase_wait). +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A [pending\_default\_admin\_delay](#IAccessControlDefaultAdminRules-pending_default_admin_delay) that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current `default_admin`. + +Emits a [DefaultAdminDelayChangeScheduled](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled) event and may emit a [DefaultAdminDelayChangeCanceled](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled) event. + + + + +Cancels a scheduled [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) change. + +Requirements: + +- Only can be called by the current `default_admin`. + +May emit a [DefaultAdminDelayChangeCanceled](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled) event. + + + +Maximum time in seconds for an increase to [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) (that is scheduled using [change\_default\_admin\_delay](#IAccessControlDefaultAdminRules-change_default_admin_delay)) to take effect. Defaults to 5 days. + +When the [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) is scheduled to be increased, it goes into effect after the new delay has passed with the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can be overridden for a custom [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) increase scheduling. + +Make sure to add a reasonable amount of time while overriding this value, otherwise, there’s a risk of setting a high new delay that goes into effect almost immediately without the possibility of human intervention in the case of an input error (e.g. set milliseconds instead of seconds). + + +#### Events [!toc] [#IAccessControlDefaultAdminRules-Events] + + +Emitted when a `default_admin` transfer is started. + +Sets `new_admin` as the next address to become the `default_admin` by calling [accept\_default\_admin\_transfer](#IAccessControlDefaultAdminRules-accept_default_admin_transfer) only after `accept_schedule` passes. + + + +Emitted when a [pending\_default\_admin](#IAccessControlDefaultAdminRules-pending_default_admin) is reset if it was never accepted, regardless of its schedule. + + + +Emitted when a [default\_admin\_delay](#IAccessControlDefaultAdminRules-default_admin_delay) change is started. + +Sets `new_delay` as the next delay to be applied between default admins transfers after `effect_schedule` has passed. + +Emitted when a [pending\_default\_admin\_delay](#IAccessControlDefaultAdminRules-pending_default_admin_delay) is reset if its schedule didn’t pass. + + +## Core + +### `OwnableComponent` [toc] [#OwnableComponent] + + +```rust +use openzeppelin_access::ownable::OwnableComponent; +``` + +`Ownable` provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific functions. + +This module includes the internal `assert_only_owner` to restrict a function to be used only by the owner. + +[Embeddable Mixin Implementations](../components#mixins) + +#### OwnableMixinImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableMixinImpl] + +- [`OwnableImpl`](#OwnableComponent-Embeddable-Impls-OwnableImpl) +- [`OwnableCamelOnlyImpl`](#OwnableComponent-Embeddable-Impls-OwnableCamelOnlyImpl) + +#### OwnableTwoStepMixinImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableTwoStepMixinImpl] + +- [`OwnableTwoStepImpl`](#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl) +- [`OwnableTwoStepCamelOnlyImpl`](#OwnableComponent-Embeddable-Impls-OwnableTwoStepCamelOnlyImpl) + +Embeddable Implementations + +#### OwnableImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableImpl] + +- [`owner(self)`](#OwnableComponent-owner) +- [`transfer_ownership(self, new_owner)`](#OwnableComponent-transfer_ownership) +- [`renounce_ownership(self)`](#OwnableComponent-renounce_ownership) + +#### OwnableTwoStepImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableTwoStepImpl] + +- [`owner(self)`](#OwnableComponent-two-step-owner) +- [`pending_owner(self)`](#OwnableComponent-two-step-pending_owner) +- [`accept_ownership(self)`](#OwnableComponent-two-step-accept_ownership) +- [`transfer_ownership(self, new_owner)`](#OwnableComponent-two-step-transfer_ownership) +- [`renounce_ownership(self)`](#OwnableComponent-two-step-renounce_ownership) + +#### OwnableCamelOnlyImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableCamelOnlyImpl] + +- [`transferOwnership(self, newOwner)`](#OwnableComponent-transferOwnership) +- [`renounceOwnership(self)`](#OwnableComponent-renounceOwnership) + +#### OwnableTwoStepCamelOnlyImpl [!toc] [#OwnableComponent-Embeddable-Impls-OwnableTwoStepCamelOnlyImpl] + +- [`pendingOwner(self)`](#OwnableComponent-two-step-pendingOwner) +- [`acceptOwnership(self)`](#OwnableComponent-two-step-acceptOwnership) +- [`transferOwnership(self, new_owner)`](#OwnableComponent-two-step-transferOwnership) +- [`renounceOwnership(self)`](#OwnableComponent-two-step-renounceOwnership) + +Internal Implementations + +#### InternalImpl [!toc] [#OwnableComponent-InternalImpl] + +- [`initializer(self, owner)`](#OwnableComponent-initializer) +- [`assert_only_owner(self)`](#OwnableComponent-assert_only_owner) +- [`_transfer_ownership(self, new_owner)`](#OwnableComponent-_transfer_ownership) +- [`_propose_owner(self, new_owner)`](#OwnableComponent-_propose_owner) + +Events + +- [`OwnershipTransferStarted(previous_owner, new_owner)`](#OwnableComponent-OwnershipTransferStarted) +- [`OwnershipTransferred(previous_owner, new_owner)`](#OwnableComponent-OwnershipTransferred) + +#### Embeddable functions [!toc] [#OwnableComponent-Embeddable-Functions] + + +Returns the address of the current owner. + + + +Transfers ownership of the contract to a new account (`new_owner`). Can only be called by the current owner. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Leaves the contract without owner. It will not be possible to call `assert_only_owner` functions anymore. Can only be called by the current owner. + + +Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner. + + + +#### Embeddable functions (two step transfer) [!toc] [#OwnableComponent-Embeddable-Functions-Two-Step] + + +Returns the address of the current owner. + + + +Returns the address of the pending owner. + + + +Transfers ownership of the contract to the pending owner. Can only be called by the pending owner. Resets pending owner to zero address. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Starts the two step ownership transfer process, by setting the pending owner. Setting `new_owner` to the zero address is allowed, this can be used to cancel an initiated ownership transfer. + +Can only be called by the current owner. + +Emits an [OwnershipTransferStarted](#OwnableComponent-OwnershipTransferStarted) event. + + + +Leaves the contract without owner. It will not be possible to call `assert_only_owner` functions anymore. Can only be called by the current owner. + + +Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner. + + + + +See [transfer\_ownership](#OwnableComponent-transfer_ownership). + + + +See [renounce\_ownership](#OwnableComponent-renounce_ownership). + + + +See [pending\_owner](#OwnableComponent-two-step-pending_owner). + + + +See [accept\_ownership](#OwnableComponent-two-step-accept_ownership). + + + +See [transfer\_ownership](#OwnableComponent-two-step-transfer_ownership). + + + +See [renounce\_ownership](#OwnableComponent-two-step-renounce_ownership). + + +#### Internal functions [!toc] [#OwnableComponent-Internal-Functions] + + +Initializes the contract and sets `owner` as the initial owner. + +Requirements: + +- `owner` cannot be the zero address. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Panics if called by any account other than the owner. + + + +Transfers ownership of the contract to a new account (`new_owner`). Internal function without access restriction. + +Emits an [OwnershipTransferred](#OwnableComponent-OwnershipTransferred) event. + + + +Sets a new pending owner in a two step transfer. + +Internal function without access restriction. + +Emits an [OwnershipTransferStarted](#OwnableComponent-OwnershipTransferStarted) event. + + +#### Events [!toc] [#OwnableComponent-Events] + + +Emitted when the pending owner is updated. + + + +Emitted when the ownership is transferred. + + +### `AccessControlComponent` [toc] [#AccessControlComponent] + + +```rust +use openzeppelin_access::accesscontrol::AccessControlComponent; +``` + +Component that allows contracts to implement role-based access control mechanisms. Roles are referred to by their `felt252` identifier: + +```rust +const MY_ROLE: felt252 = selector!("MY_ROLE"); +``` + +Roles can be used to represent a set of permissions. To restrict access to a function call, use [`assert_only_role`](#AccessControlComponent-assert_only_role): + +```rust +(...) + +#[external(v0)] +fn foo(ref self: ContractState) { + self.accesscontrol.assert_only_role(MY_ROLE); + + // Do something +} +``` + +Roles can be granted and revoked dynamically via the [grant\_role](#AccessControlComponent-grant_role), [grant\_role\_with\_delay](#IAccessControlWithDelay-grant_role_with_delay) and [revoke\_role](#AccessControlComponent-revoke_role) functions. Each role has an associated admin role, and only accounts that have a role’s admin role can call [grant\_role](#AccessControlComponent-grant_role), [grant\_role\_with\_delay](#IAccessControlWithDelay-grant_role_with_delay) and [revoke\_role](#AccessControlComponent-revoke_role). + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means that only accounts with this role will be able to grant or revoke other roles. More complex role relationships can be created by using [set\_role\_admin](#AccessControlComponent-set_role_admin). + + +The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role. Extra precautions should be taken to secure accounts that have been granted it. See [AccessControlDefaultAdminRulesComponent](#AccessControlDefaultAdminRulesComponent). + + +[Embeddable Mixin Implementations](../components#mixins) + +#### AccessControlMixinImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlMixinImpl] + +- [`AccessControlImpl`](#AccessControlComponent-Embeddable-Impls-AccessControlImpl) +- [`AccessControlCamelImpl`](#AccessControlComponent-Embeddable-Impls-AccessControlCamelImpl) +- [`AccessControlWithDelayImpl`](#AccessControlComponent-Embeddable-Impls-AccessControlWithDelayImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### AccessControlImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlImpl] + +- [`has_role(self, role, account)`](#AccessControlComponent-has_role) +- [`get_role_admin(self, role)`](#AccessControlComponent-get_role_admin) +- [`grant_role(self, role, account)`](#AccessControlComponent-grant_role) +- [`revoke_role(self, role, account)`](#AccessControlComponent-revoke_role) +- [`renounce_role(self, role, account)`](#AccessControlComponent-renounce_role) + +#### AccessControlCamelImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlCamelImpl] + +- [`hasRole(self, role, account)`](#AccessControlComponent-hasRole) +- [`getRoleAdmin(self, role)`](#AccessControlComponent-getRoleAdmin) +- [`grantRole(self, role, account)`](#AccessControlComponent-grantRole) +- [`revokeRole(self, role, account)`](#AccessControlComponent-revokeRole) +- [`renounceRole(self, role, account)`](#AccessControlComponent-renounceRole) + +#### AccessControlWithDelayImpl [!toc] [#AccessControlComponent-Embeddable-Impls-AccessControlWithDelayImpl] + +- [`get_role_status(self, role, account)`](#AccessControlComponent-get_role_status) +- [`grant_role_with_delay(self, role, account, delay)`](#AccessControlComponent-grant_role_with_delay) + +#### SRC5Impl [!toc] [#AccessControlComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#AccessControlComponent-InternalImpl] + +- [`initializer(self)`](#AccessControlComponent-initializer) +- [`assert_only_role(self, role)`](#AccessControlComponent-assert_only_role) +- [`is_role_effective(self, role, account)`](#AccessControlComponent-is_role_effective) +- [`resolve_role_status(self, role, account)`](#AccessControlComponent-resolve_role_status) +- [`is_role_granted(self, role, account)`](#AccessControlComponent-is_role_granted) +- [`set_role_admin(self, role, admin_role)`](#AccessControlComponent-set_role_admin) +- [`_grant_role(self, role, account)`](#AccessControlComponent-_grant_role) +- [`_grant_role_with_delay(self, role, account, delay)`](#AccessControlComponent-_grant_role_with_delay) +- [`_revoke_role(self, role, account)`](#AccessControlComponent-_revoke_role) + +Events + +#### IAccessControl [!toc] [#AccessControlComponent-Events-IAccessControl] + +- [`RoleAdminChanged(role, previous_admin_role, new_admin_role)`](#AccessControlComponent-RoleAdminChanged) +- [`RoleGranted(role, account, sender)`](#AccessControlComponent-RoleGranted) +- [`RoleRevoked(role, account, sender)`](#AccessControlComponent-RoleRevoked) + +#### IAccessControlWithDelay [!toc] [#AccessControlComponent-Events-IAccessControlWithDelay] + +- [`RoleGrantedWithDelay(role, account, sender, delay)`](#AccessControlComponent-RoleGrantedWithDelay) + +#### Embeddable functions [!toc] [#AccessControlComponent-Embeddable-Functions] + + +Returns whether `account` can act as `role`. + + + +Returns the admin role that controls `role`. See [grant\_role](#AccessControlComponent-grant_role) and [revoke\_role](#AccessControlComponent-revoke_role). + +To change a role’s admin, use [set\_role\_admin](#AccessControlComponent-set_role_admin). + + + +Returns the account’s status for the given role. + +The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [RoleGranted](#IAccessControl-RoleGranted) event. + +Requirements: + +- the caller must have `role`'s admin role. + +May emit a [RoleGranted](#IAccessControl-RoleGranted) event. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +Requirements: + +- The caller must have \`role’s admin role. +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay) event. + + + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must have `role`'s admin role. + +May emit a [RoleRevoked](#IAccessControl-RoleRevoked) event. + + + +Revokes `role` from the calling account. + +Roles are often managed via [grant\_role](#AccessControlComponent-grant_role) and [revoke\_role](#AccessControlComponent-revoke_role). This function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been revoked `role`, emits a [RoleRevoked](#IAccessControl-RoleRevoked) event. + +Requirements: + +- the caller must be `account`. + +May emit a [RoleRevoked](#IAccessControl-RoleRevoked) event. + + + +See [ISRC5::supports\_interface](./introspection#ISRC5-supports_interface). + + + +See [has\_role](#AccessControlComponent-has_role). + + + +See [get\_role\_admin](#AccessControlComponent-get_role_admin). + + + +See [grant\_role](#AccessControlComponent-grant_role). + + + +See [revoke\_role](#AccessControlComponent-revoke_role). + + + +See [renounce\_role](#AccessControlComponent-renounce_role). + + +#### Internal functions [!toc] [#AccessControlComponent-Internal-Functions] + + +Initializes the contract by registering the [IAccessControl](#IAccessControl) interface ID. + + + +Validates that the caller can act as the given role. Otherwise it panics. + + + +Returns whether the account can act as the given role. + +The account can act as the role if it is active and the `effective_from` time is before or equal to the current time. + + +If the `effective_from` timepoint is 0, the role is effective immediately. This is backwards compatible with implementations that didn’t use delays but a single boolean flag. + + + + +Returns the account’s status for the given role. + +The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Returns whether the account has the given role granted. + + +The account may not be able to act as the role yet, if a delay was set and has not passed yet. Use `is_role_effective` to check if the account can act as the role. + + + + +Sets `admin_role` as `role`'s admin role. + +Internal function without access restriction. + +Emits a [RoleAdminChanged](#IAccessControl-RoleAdminChanged) event. + + + +Attempts to grant `role` to `account`. The function does nothing if `role` is already effective for `account`. If `role` has been granted to `account`, but is not yet active due to a time delay, the delay is removed and `role` becomes effective immediately. + +Internal function without access restriction. + +May emit a [RoleGranted](#IAccessControl-RoleGranted) event. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +The role will become effective after the given delay has passed. If the role is already active (`Effective`) for the account, the function will panic. If the role has been granted but is not yet active (being in the `Delayed` state), the existing delay will be overwritten with the new `delay`. + +Internal function without access restriction. + +Requirements: + +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay) event. + + + +Revokes `role` from `account`. + +Internal function without access restriction. + +May emit a [RoleRevoked](#IAccessControl-RoleRevoked) event. + + +#### Events [!toc] [#AccessControlComponent-Events] + + +See [IAccessControl::RoleAdminChanged](#IAccessControl-RoleAdminChanged). + + + +See [IAccessControl::RoleGranted](#IAccessControl-RoleGranted). + + + +See [IAccessControlWithDelay::RoleGrantedWithDelay](#IAccessControlWithDelay-RoleGrantedWithDelay). + + + +See [IAccessControl::RoleRevoked](#IAccessControl-RoleRevoked). + + +## Extensions + +### `AccessControlDefaultAdminRulesComponent` [toc] [#AccessControlDefaultAdminRulesComponent] + + +```rust +use openzeppelin_access::accesscontrol::extensions::AccessControlDefaultAdminRulesComponent; +``` + +Extension of [AccessControl](#AccessControlComponent) that allows specifying special rules to manage the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions over other roles that may potentially have privileged rights in the system. + +If a specific role doesn’t have an admin role assigned, the holder of the `DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. + +This contract implements the following risk mitigations on top of [AccessControl](#AccessControlComponent): + +- Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it’s potentially renounced. +- Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. +- Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. +- The delay can be changed by scheduling, see [change\_default\_admin\_delay](#IAccessControlDefaultAdminRules-change_default_admin_delay). +- It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. + +[Embeddable Mixin Implementations](../components#mixins) + +#### AccessControlMixinImpl [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlMixinImpl] + +- [`AccessControlDefaultAdminRulesImpl`](#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlDefaultAdminRulesImpl) +- [`AccessControlImpl`](#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlImpl) +- [`AccessControlCamelImpl`](#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlCamelImpl) +- [`AccessControlWithDelayImpl`](#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlWithDelayImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### AccessControlDefaultAdminRulesImpl [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlDefaultAdminRulesImpl] + +- [`default_admin(self)`](#IAccessControlDefaultAdminRules-default_admin) +- [`pending_default_admin(self)`](#IAccessControlDefaultAdminRules-pending_default_admin) +- [`default_admin_delay(self)`](#IAccessControlDefaultAdminRules-default_admin_delay) +- [`pending_default_admin_delay(self)`](#IAccessControlDefaultAdminRules-pending_default_admin_delay) +- [`begin_default_admin_transfer(self, new_admin)`](#IAccessControlDefaultAdminRules-begin_default_admin_transfer) +- [`cancel_default_admin_transfer(self)`](#IAccessControlDefaultAdminRules-cancel_default_admin_transfer) +- [`accept_default_admin_transfer(self)`](#IAccessControlDefaultAdminRules-accept_default_admin_transfer) +- [`change_default_admin_delay(self, new_delay)`](#IAccessControlDefaultAdminRules-change_default_admin_delay) +- [`rollback_default_admin_delay(self)`](#IAccessControlDefaultAdminRules-rollback_default_admin_delay) +- [`default_admin_delay_increase_wait(self)`](#IAccessControlDefaultAdminRules-default_admin_delay_increase_wait) + +#### AccessControlImpl [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlImpl] + +- [`has_role(self, role, account)`](#AccessControlDefaultAdminRulesComponent-has_role) +- [`get_role_admin(self, role)`](#AccessControlDefaultAdminRulesComponent-get_role_admin) +- [`grant_role(self, role, account)`](#AccessControlDefaultAdminRulesComponent-grant_role) +- [`revoke_role(self, role, account)`](#AccessControlDefaultAdminRulesComponent-revoke_role) +- [`renounce_role(self, role, account)`](#AccessControlDefaultAdminRulesComponent-renounce_role) + +#### AccessControlCamelImpl [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlCamelImpl] + +- [`hasRole(self, role, account)`](#AccessControlDefaultAdminRulesComponent-hasRole) +- [`getRoleAdmin(self, role)`](#AccessControlDefaultAdminRulesComponent-getRoleAdmin) +- [`grantRole(self, role, account)`](#AccessControlDefaultAdminRulesComponent-grantRole) +- [`revokeRole(self, role, account)`](#AccessControlDefaultAdminRulesComponent-revokeRole) +- [`renounceRole(self, role, account)`](#AccessControlDefaultAdminRulesComponent-renounceRole) + +#### AccessControlWithDelayImpl [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-AccessControlWithDelayImpl] + +- [`get_role_status(self, role, account)`](#AccessControlDefaultAdminRulesComponent-get_role_status) +- [`grant_role_with_delay(self, role, account, delay)`](#AccessControlDefaultAdminRulesComponent-grant_role_with_delay) + +#### SRC5Impl [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#AccessControlDefaultAdminRulesComponent-InternalImpl] + +- [`initializer(self, initial_delay, initial_default_admin)`](#AccessControlDefaultAdminRulesComponent-initializer) +- [`assert_only_role(self, role)`](#AccessControlDefaultAdminRulesComponent-assert_only_role) +- [`is_role_effective(self, role, account)`](#AccessControlDefaultAdminRulesComponent-is_role_effective) +- [`resolve_role_status(self, role, account)`](#AccessControlDefaultAdminRulesComponent-resolve_role_status) +- [`is_role_granted(self, role, account)`](#AccessControlDefaultAdminRulesComponent-is_role_granted) +- [`set_role_admin(self, role, admin_role)`](#AccessControlDefaultAdminRulesComponent-set_role_admin) +- [`_grant_role(self, role, account)`](#AccessControlDefaultAdminRulesComponent-_grant_role) +- [`_grant_role_with_delay(self, role, account, delay)`](#AccessControlDefaultAdminRulesComponent-_grant_role_with_delay) +- [`_revoke_role(self, role, account)`](#AccessControlDefaultAdminRulesComponent-_revoke_role) +- [`set_pending_default_admin(self, new_admin, new_schedule)`](#AccessControlDefaultAdminRulesComponent-set_pending_default_admin) +- [`set_pending_delay(self, new_delay, new_schedule)`](#AccessControlDefaultAdminRulesComponent-set_pending_delay) +- [`delay_change_wait(self, new_delay)`](#AccessControlDefaultAdminRulesComponent-delay_change_wait) + +Events + +#### IAccessControl [!toc] [#AccessControlDefaultAdminRulesComponent-Events-IAccessControl] + +- [`RoleAdminChanged(role, previous_admin_role, new_admin_role)`](#AccessControlDefaultAdminRulesComponent-RoleAdminChanged) +- [`RoleGranted(role, account, sender)`](#AccessControlDefaultAdminRulesComponent-RoleGranted) +- [`RoleRevoked(role, account, sender)`](#AccessControlDefaultAdminRulesComponent-RoleRevoked) + +#### IAccessControlWithDelay [!toc] [#AccessControlDefaultAdminRulesComponent-Events-IAccessControlWithDelay] + +- [`RoleGrantedWithDelay(role, account, sender, delay)`](#AccessControlDefaultAdminRulesComponent-RoleGrantedWithDelay) + +#### IAccessControlDefaultAdminRules [!toc] [#AccessControlDefaultAdminRulesComponent-Events-IAccessControlDefaultAdminRules] + +- [`DefaultAdminTransferScheduled(new_admin, accept_schedule)`](#AccessControlDefaultAdminRulesComponent-DefaultAdminTransferScheduled) +- [`DefaultAdminTransferCanceled()`](#AccessControlDefaultAdminRulesComponent-DefaultAdminTransferCanceled) +- [`DefaultAdminDelayChangeScheduled(new_delay, effect_schedule)`](#AccessControlDefaultAdminRulesComponent-DefaultAdminDelayChangeScheduled) +- [`DefaultAdminDelayChangeCanceled()`](#AccessControlDefaultAdminRulesComponent-DefaultAdminDelayChangeCanceled) + +#### Embeddable functions [!toc] [#AccessControlDefaultAdminRulesComponent-Embeddable-Functions] + + +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + + + +Returns a tuple of a `new_admin` and an `accept_schedule`. + +After the `accept_schedule` passes, the `new_admin` will be able to accept the `default_admin` role by calling [accept\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-accept_default_admin_transfer), completing the role transfer. + +A zero value only in `accept_schedule` indicates no pending admin transfer. + + +A zero address `new_admin` means that `default_admin` is being renounced. + + + + +Returns the delay required to schedule the acceptance of a `default_admin` transfer started. + +This delay will be added to the current timestamp when calling [begin\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-begin_default_admin_transfer) to set the acceptance schedule. + + +If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this function returns the new delay. + + +See [change\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-change_default_admin_delay). + + + +Returns a tuple of `new_delay` and an `effect_schedule`. + +After the `effect_schedule` passes, the `new_delay` will get into effect immediately for every new `default_admin` transfer started with [begin\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-begin_default_admin_transfer). + +A zero value only in `effect_schedule` indicates no pending delay change. + +A zero value only for `new_delay` means that the next [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) will be zero after the effect schedule. + + + +Starts a `default_admin` transfer by setting a [pending\_default\_admin](#AccessControlDefaultAdminRulesComponent-pending_default_admin) scheduled for acceptance after the current timestamp plus a [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay). + +Requirements: + +- Only can be called by the current `default_admin`. + +Emits a [DefaultAdminTransferScheduled](#AccessControlDefaultAdminRulesComponent-DefaultAdminTransferScheduled) event. + + + +Cancels a `default_admin` transfer previously started with [begin\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-begin_default_admin_transfer). + +A [pending\_default\_admin](#AccessControlDefaultAdminRulesComponent-pending_default_admin) not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current `default_admin`. + +May emit a [DefaultAdminTransferCanceled](#AccessControlDefaultAdminRulesComponent-DefaultAdminTransferCanceled) event. + + + +Completes a `default_admin` transfer previously started with [begin\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-begin_default_admin_transfer). + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` must be granted to the caller. +- `DEFAULT_ADMIN_ROLE` must be revoked from the previous holder. +- [pending\_default\_admin](#AccessControlDefaultAdminRulesComponent-pending_default_admin) must be reset to zero values. + +Requirements: + +- Only can be called by the [pending\_default\_admin](#AccessControlDefaultAdminRulesComponent-pending_default_admin)'s `new_admin`. +- The [pending\_default\_admin](#AccessControlDefaultAdminRulesComponent-pending_default_admin)'s `accept_schedule` should’ve passed. + + + +Initiates a [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) update by setting a [pending\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-pending_default_admin_delay) scheduled for getting into effect after the current timestamp plus a [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay). + +This function guarantees that any call to [begin\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-begin_default_admin_transfer) done between the timestamp this method is called and the [pending\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-pending_default_admin_delay) effect schedule will use the current [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) set before calling. + +The [pending\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-pending_default_admin_delay)'s effect schedule is defined in a way that waiting until the schedule and then calling [begin\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-begin_default_admin_transfer) with the new delay will take at least the same as another `default_admin` complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp new delay` capped by [default\_admin\_delay\_increase\_wait](#AccessControlDefaultAdminRulesComponent-default_admin_delay_increase_wait). +- When the delay is changed for a shorter one, the schedule is `block.timestamp (current delay - new delay)`. + +A [pending\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-pending_default_admin_delay) that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current `default_admin`. + +Emits a [DefaultAdminDelayChangeScheduled](#AccessControlDefaultAdminRulesComponent-DefaultAdminDelayChangeScheduled) event and may emit a [DefaultAdminDelayChangeCanceled](#AccessControlDefaultAdminRulesComponent-DefaultAdminDelayChangeCanceled) event. + + + +Cancels a scheduled [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) change. + +Requirements: + +- Only can be called by the current `default_admin`. + +May emit a [DefaultAdminDelayChangeCanceled](#AccessControlDefaultAdminRulesComponent-DefaultAdminDelayChangeCanceled) event. + + + +Maximum time in seconds for an increase to [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) (that is scheduled using [change\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-change_default_admin_delay)) to take effect. Defaults to 5 days. + +When the [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) is scheduled to be increased, it goes into effect after the new delay has passed with the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can be overridden for a custom [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) increase scheduling. + + +Make sure to add a reasonable amount of time while overriding this value, otherwise, there’s a risk of setting a high new delay that goes into effect almost immediately without the possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + + + + +Returns whether `account` can act as `role`. + + + +Returns the admin role that controls `role`. See [grant\_role](#AccessControlComponent-grant_role) and [revoke\_role](#AccessControlComponent-revoke_role). + +To change a role’s admin, use [set\_role\_admin](#AccessControlComponent-set_role_admin). + + + +Returns the account’s status for the given role. + +The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [RoleGranted](#AccessControlDefaultAdminRulesComponent-RoleGranted) event. + +Requirements: + +- the caller must have `role`'s admin role. + +May emit a [RoleGranted](#AccessControlDefaultAdminRulesComponent-RoleGranted) event. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +Requirements: + +- The caller must have \`role’s admin role. +- delay must be greater than 0. +- the `role` must not be already effective for `account`. + +May emit a [RoleGrantedWithDelay](#AccessControlDefaultAdminRulesComponent-RoleGrantedWithDelay) event. + + + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [RoleRevoked](#AccessControlDefaultAdminRulesComponent-RoleRevoked) event. + +Requirements: + +- the caller must have `role`'s admin role. + +May emit a [RoleRevoked](#AccessControlDefaultAdminRulesComponent-RoleRevoked) event. + + + +Revokes `role` from the calling account. + +Roles are often managed via [grant\_role](#AccessControlComponent-grant_role) and [revoke\_role](#AccessControlComponent-revoke_role). This function’s purpose is to provide a mechanism for accounts to lose their privileges if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been revoked `role`, emits a [RoleRevoked](#AccessControlDefaultAdminRulesComponent-RoleRevoked) event. + +Requirements: + +- the caller must be `account`. + +May emit a [RoleRevoked](#AccessControlDefaultAdminRulesComponent-RoleRevoked) event. + + + +See [ISRC5::supports\_interface](./introspection#ISRC5-supports_interface). + + + +See [has\_role](#AccessControlDefaultAdminRulesComponent-has_role). + + + +See [get\_role\_admin](#AccessControlDefaultAdminRulesComponent-get_role_admin). + + + +See [grant\_role](#AccessControlDefaultAdminRulesComponent-grant_role). + + + +See [revoke\_role](#AccessControlDefaultAdminRulesComponent-revoke_role). + + + +See [renounce\_role](#AccessControlDefaultAdminRulesComponent-renounce_role). + + +#### Internal functions [!toc] [#AccessControlDefaultAdminRulesComponent-Internal-Functions] + + +Initializes the contract by registering the IAccessControl interface ID and setting the initial delay and default admin. + +Requirements: + +- `initial_default_admin` must not be the zero address. + + + +Validates that the caller can act as the given role. Otherwise it panics. + + + +Returns whether the account can act as the given role. + +The account can act as the role if it is active and the `effective_from` time is before or equal to the current time. + + +If the `effective_from` timepoint is 0, the role is effective immediately. This is backwards compatible with implementations that didn’t use delays but a single boolean flag. + + + + +Returns the account’s status for the given role. + +The possible statuses are: + +- `NotGranted`: the role has not been granted to the account. +- `Delayed`: The role has been granted to the account but is not yet active due to a time delay. +- `Effective`: the role has been granted to the account and is currently active. + + + +Returns whether the account has the given role granted. + + +The account may not be able to act as the role yet, if a delay was set and has not passed yet. Use is\_role\_effective to check if the account can act as the role. + + + + +Sets `admin_role` as \`role’s admin role. + +Internal function without access restriction. + +Requirements: + +- `role` must not be `DEFAULT_ADMIN_ROLE`. + +Emits a [RoleAdminChanged](#AccessControlDefaultAdminRulesComponent-RoleAdminChanged) event. + + + +Attempts to grant `role` to `account`. The function does nothing if `role` is already effective for `account`. If `role` has been granted to `account`, but is not yet active due to a time delay, the delay is removed and `role` becomes effective immediately. + +Internal function without access restriction. + +For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn’t already a `default_admin` or if the role has been previously renounced. + +Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE` assignable again. Make sure to guarantee this is the expected behavior in your implementation. + +May emit a [RoleGranted](#AccessControlDefaultAdminRulesComponent-RoleGranted) event. + + + +Attempts to grant `role` to `account` with the specified activation delay. + +The role will become effective after the given delay has passed. If the role is already active (`Effective`) for the account, the function will panic. If the role has been granted but is not yet active (being in the `Delayed` state), the existing delay will be overwritten with the new `delay`. + +Internal function without access restriction. + +Requirements: + +- `delay` must be greater than 0. +- the `role` must not be already effective for `account`. +- `role` must not be `DEFAULT_ADMIN_ROLE`. + +May emit a [RoleGrantedWithDelay](#AccessControlDefaultAdminRulesComponent-RoleGrantedWithDelay) event. + + + +Attempts to revoke `role` from `account`. The function does nothing if `role` is not effective for `account`. If `role` has been revoked from `account`, but is still active due to a time delay, the delay is removed and `role` becomes inactive immediately. + +Internal function without access restriction. + +May emit a [RoleRevoked](#AccessControlDefaultAdminRulesComponent-RoleRevoked) event. + + + +Setter of the tuple for pending admin and its schedule. + +May emit a DefaultAdminTransferCanceled event. + + + +Setter of the tuple for pending delay and its schedule. + +May emit a DefaultAdminDelayChangeCanceled event. + + + +Returns the amount of seconds to wait after the `new_delay` will become the new `default_admin_delay`. + +The value returned guarantees that if the delay is reduced, it will go into effect after a wait that honors the previously set delay. + +See [default\_admin\_delay\_increase\_wait](#AccessControlDefaultAdminRulesComponent-default_admin_delay_increase_wait). + + +#### Events [!toc] [#AccessControlDefaultAdminRulesComponent-Events] + + +Emitted when `new_admin_role` is set as role’s admin role, replacing `previous_admin_role` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite `RoleAdminChanged` not being emitted signaling this. + + + +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an account with the admin role or the deployer address if `_grant_role` is called from the constructor. + + + +Emitted when `role` is revoked for `account`. + +`sender` is the account that originated the contract call: + +- If using `revoke_role`, it is the admin role bearer. +- If using `renounce_role`, it is the role bearer (i.e. `account`). + + + +Emitted when `account` is granted `role` with a delay. + +`sender` is the account that originated the contract call, an account with the admin role or the deployer address if `_grant_role_with_delay` is called from the constructor. + + + +Emitted when a `default_admin` transfer is started. + +Sets `new_admin` as the next address to become the `default_admin` by calling [accept\_default\_admin\_transfer](#AccessControlDefaultAdminRulesComponent-accept_default_admin_transfer) only after `accept_schedule` passes. + + + +Emitted when a [pending\_default\_admin](#AccessControlDefaultAdminRulesComponent-pending_default_admin) is reset if it was never accepted, regardless of its schedule. + + + + +Emitted when a [default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-default_admin_delay) change is started. + +Sets `new_delay` as the next delay to be applied between default admins transfers after `effect_schedule` has passed. + +Emitted when a [pending\_default\_admin\_delay](#AccessControlDefaultAdminRulesComponent-pending_default_admin_delay) is reset if its schedule didn’t pass. + diff --git a/docs/content/contracts-cairo/alpha/api/account.mdx b/docs/content/contracts-cairo/alpha/api/account.mdx new file mode 100644 index 00000000..332c86da --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/account.mdx @@ -0,0 +1,847 @@ +--- +title: Account +--- + +This crate provides components to implement account contracts that can be used for interacting with the network. + +## Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references documented here are +contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `ISRC6` [toc] [#ISRC6] + + +```rust +use openzeppelin_interfaces::accounts::ISRC6; +``` + +Interface of the SRC6 Standard Account as defined in the [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x2ceccef7f994940b3962a6c67e0ba4fcd37df7d131417c604f91e03caecc1cd +``` + +Functions + +- [`__execute__(calls)`](#ISRC6-__execute__) +- [`__validate__(calls)`](#ISRC6-__validate__) +- [`is_valid_signature(hash, signature)`](#ISRC6-is_valid_signature) + +#### Functions [!toc] [#ISRC6-Functions] + + +Executes the list of calls as a transaction after validation. + +The `Call` struct is defined in [corelib](https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/account.cairo#L3). + + + +Validates a transaction before execution. + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Validates whether a signature is valid or not for the given message hash. + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + +### `ISRC9_V2` [toc] [#ISRC9_V2] + + +```rust +use openzeppelin_interfaces::src9::ISRC9_V2; +``` + +Interface of the SRC9 Standard as defined in the [SNIP-9](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-9.md). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x1d1144bb2138366ff28d8e9ab57456b1d332ac42196230c3a602003c89872 +``` + +Functions + +- [`execute_from_outside_v2(outside_execution, signature)`](#ISRC9_V2-execute_from_outside_v2) +- [`is_valid_outside_execution_nonce(nonce)`](#ISRC9_V2-is_valid_outside_execution_nonce) + +#### Functions [!toc] [#ISRC9_V2-Functions] + + +Allows anyone to submit a transaction on behalf of the account as long as they have the relevant signatures. + +This method allows reentrancy. A call to `__execute__` or `execute_from_outside_v2` can trigger another nested transaction to `execute_from_outside_v2` thus the implementation MUST verify that the provided `signature` matches the hash of `outside_execution` and that `nonce` was not already used. + +The implementation should expect version to be set to 2 in the domain separator. + +Arguments: + +- `outside_execution` - The parameters of the transaction to execute. +- `signature` - A valid signature on the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message encoding of `outside_execution`. + + + +Get the status of a given nonce. `true` if the nonce is available to use. + + +## Core + +### `AccountComponent` [toc] [#AccountComponent] + + +```rust +use openzeppelin_account::AccountComponent; +``` + +Account component implementing [`ISRC6`](#ISRC6) for signatures over the [Starknet curve](https://docs.starknet.io/architecture-and-concepts/cryptography/#stark-curve). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +[Embeddable Mixin Implementations](../components#mixins) + +#### AccountMixinImpl [!toc] [#AccountComponent-Embeddable-Impls-AccountMixinImpl] + +- [`SRC6Impl`](#AccountComponent-Embeddable-Impls-SRC6Impl) +- [`DeclarerImpl`](#AccountComponent-Embeddable-Impls-DeclarerImpl) +- [`DeployableImpl`](#AccountComponent-Embeddable-Impls-DeployableImpl) +- [`PublicKeyImpl`](#AccountComponent-Embeddable-Impls-PublicKeyImpl) +- [`SRC6CamelOnlyImpl`](#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl) +- [`PublicKeyCamelImpl`](#AccountComponent-Embeddable-Impls-PublicKeyCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### SRC6Impl [!toc] [#AccountComponent-Embeddable-Impls-SRC6Impl] + +- [`__execute__(self, calls)`](#AccountComponent-__execute__) +- [`__validate__(self, calls)`](#AccountComponent-__validate__) +- [`is_valid_signature(self, hash, signature)`](#AccountComponent-is_valid_signature) + +#### DeclarerImpl [!toc] [#AccountComponent-Embeddable-Impls-DeclarerImpl] + +- [`__validate_declare__(self, class_hash)`](#AccountComponent-__validate_declare__) + +#### DeployableImpl [!toc] [#AccountComponent-Embeddable-Impls-DeployableImpl] + +- [`__validate_deploy__(self, hash, signature)`](#AccountComponent-__validate_deploy__) + +#### PublicKeyImpl [!toc] [#AccountComponent-Embeddable-Impls-PublicKeyImpl] + +- [`get_public_key(self)`](#AccountComponent-get_public_key) +- [`set_public_key(self, new_public_key, signature)`](#AccountComponent-set_public_key) + +#### SRC6CamelOnlyImpl [!toc] [#AccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl] + +- [`isValidSignature(self, hash, signature)`](#AccountComponent-isValidSignature) + +#### PublicKeyCamelImpl [!toc] [#AccountComponent-Embeddable-Impls-PublicKeyCamelImpl] + +- [`getPublicKey(self)`](#AccountComponent-getPublicKey) +- [`setPublicKey(self, newPublicKey, signature)`](#AccountComponent-setPublicKey) + +#### SRC5Impl [!toc] [#AccountComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#AccountComponent-InternalImpl] + +- [`initializer(self, public_key)`](#AccountComponent-initializer) +- [`assert_only_self(self)`](#AccountComponent-assert_only_self) +- [`assert_valid_new_owner(self, current_owner, new_owner, signature)`](#AccountComponent-assert_valid_new_owner) +- [`validate_transaction(self)`](#AccountComponent-validate_transaction) +- [`_set_public_key(self, new_public_key)`](#AccountComponent-_set_public_key) +- [`_is_valid_signature(self, hash, signature)`](#AccountComponent-_is_valid_signature) + +Events + +- [`OwnerAdded(new_owner_guid)`](#AccountComponent-OwnerAdded) +- [`OwnerRemoved(removed_owner_guid)`](#AccountComponent-OwnerRemoved) + +#### Embeddable functions [!toc] [#AccountComponent-Embeddable-Functions] + + +See [ISRC6::\_\_execute\_\_](#ISRC6-__execute__). + + + +See [ISRC6::\_\_validate\_\_](#ISRC6-__validate__). + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +Validates a [`Declare` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Validates a [`DeployAccount` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#deploy_account_transaction). See [Counterfactual Deployments](../guides/deployment). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Returns the current public key of the account. + + + +Sets a new public key for the account. Only accessible by the account calling itself through `__execute__`. + +Requirements: + +- The caller must be the contract itself. +- The signature must be valid for the new owner. + +Emits both an [OwnerRemoved](#AccountComponent-OwnerRemoved) and an [OwnerAdded](#AccountComponent-OwnerAdded) event. + +The message to be signed is computed in Cairo as follows: + +```javascript +let message_hash = PoseidonTrait::new() + .update_with('StarkNet Message') + .update_with('accept_ownership') + .update_with(get_contract_address()) + .update_with(current_owner) + .finalize(); +``` + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +See [get\_public\_key](#AccountComponent-get_public_key). + + + +See [set\_public\_key](#AccountComponent-set_public_key). + + +#### Internal functions [!toc] [#AccountComponent-Internal-Functions] + + +Initializes the account with the given public key, and registers the `ISRC6` interface ID. + +Emits an [OwnerAdded](#AccountComponent-OwnerAdded) event. + + + +Validates that the caller is the account itself. Otherwise it reverts. + + + +Validates that `new_owner` accepted the ownership of the contract through a signature. + +Requirements: + +- `signature` must be valid for the new owner. + +This function assumes that `current_owner` is the current owner of the contract, and does not validate this assumption. + + + +Validates a transaction signature from the [global context](https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/info.cairo#L61). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Set the public key without validating the caller. + +Emits an [OwnerAdded](#AccountComponent-OwnerAdded) event. + +The usage of this method outside the `set_public_key` function is discouraged. + + + +Validates the provided `signature` for the `hash`, using the account's current public key. + + +#### Events [!toc] [#AccountComponent-Events] + + +Emitted when a `public_key` is added. + + + +Emitted when a `public_key` is removed. + + +### `EthAccountComponent` [toc] [#EthAccountComponent] + + +```rust +use openzeppelin_account::eth_account::EthAccountComponent; +``` + +Account component implementing [`ISRC6`](#ISRC6) for signatures over the [Secp256k1 curve](https://en.bitcoin.it/wiki/Secp256k1). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Point`. + +[Embeddable Mixin Implementations](../components#mixins) + +#### EthAccountMixinImpl [!toc] [#EthAccountComponent-Embeddable-Impls-EthAccountMixinImpl] + +- [`SRC6Impl`](#EthAccountComponent-Embeddable-Impls-SRC6Impl) +- [`DeclarerImpl`](#EthAccountComponent-Embeddable-Impls-DeclarerImpl) +- [`DeployableImpl`](#EthAccountComponent-Embeddable-Impls-DeployableImpl) +- [`PublicKeyImpl`](#EthAccountComponent-Embeddable-Impls-PublicKeyImpl) +- [`SRC6CamelOnlyImpl`](#EthAccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl) +- [`PublicKeyCamelImpl`](#EthAccountComponent-Embeddable-Impls-PublicKeyCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### SRC6Impl [!toc] [#EthAccountComponent-Embeddable-Impls-SRC6Impl] + +- [`__execute__(self, calls)`](#EthAccountComponent-__execute__) +- [`__validate__(self, calls)`](#EthAccountComponent-__validate__) +- [`is_valid_signature(self, hash, signature)`](#EthAccountComponent-is_valid_signature) + +#### DeclarerImpl [!toc] [#EthAccountComponent-Embeddable-Impls-DeclarerImpl] + +- [`__validate_declare__(self, class_hash)`](#EthAccountComponent-__validate_declare__) + +#### DeployableImpl [!toc] [#EthAccountComponent-Embeddable-Impls-DeployableImpl] + +- [`__validate_deploy__(self, hash, signature)`](#EthAccountComponent-__validate_deploy__) + +#### PublicKeyImpl [!toc] [#EthAccountComponent-Embeddable-Impls-PublicKeyImpl] + +- [`get_public_key(self)`](#EthAccountComponent-get_public_key) +- [`set_public_key(self, new_public_key, signature)`](#EthAccountComponent-set_public_key) + +#### SRC6CamelOnlyImpl [!toc] [#EthAccountComponent-Embeddable-Impls-SRC6CamelOnlyImpl] + +- [`isValidSignature(self, hash, signature)`](#EthAccountComponent-isValidSignature) + +#### PublicKeyCamelImpl [!toc] [#EthAccountComponent-Embeddable-Impls-PublicKeyCamelImpl] + +- [`getPublicKey(self)`](#EthAccountComponent-getPublicKey) +- [`setPublicKey(self, newPublicKey, signature)`](#EthAccountComponent-setPublicKey) + +#### SRC5Impl [!toc] [#EthAccountComponent-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#EthAccountComponent-InternalImpl] + +- [`initializer(self, public_key)`](#EthAccountComponent-initializer) +- [`assert_only_self(self)`](#EthAccountComponent-assert_only_self) +- [`assert_valid_new_owner(self, current_owner, new_owner, signature)`](#EthAccountComponent-assert_valid_new_owner) +- [`validate_transaction(self)`](#EthAccountComponent-validate_transaction) +- [`_set_public_key(self, new_public_key)`](#EthAccountComponent-_set_public_key) +- [`_is_valid_signature(self, hash, signature)`](#EthAccountComponent-_is_valid_signature) + +Events + +- [`OwnerAdded(new_owner_guid)`](#EthAccountComponent-OwnerAdded) +- [`OwnerRemoved(removed_owner_guid)`](#EthAccountComponent-OwnerRemoved) + +#### Embeddable functions [!toc] [#EthAccountComponent-Embeddable-Functions] + + +See [ISRC6::\_\_execute\_\_](#ISRC6-__execute__). + + + +See [ISRC6::\_\_validate\_\_](#ISRC6-__validate__). + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +Validates a [`Declare` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Validates a [`DeployAccount` transaction](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#deploy_account_transaction). See [Counterfactual Deployments](../guides/deployment). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Returns the current public key of the account. + + + +Sets a new public key for the account. Only accessible by the account calling itself through `__execute__`. + +Requirements: + +- The caller must be the contract itself. +- The signature must be valid for the new owner. + +Emits both an [OwnerRemoved](#EthAccountComponent-OwnerRemoved) and an [OwnerAdded](#EthAccountComponent-OwnerAdded) event. + +The message to be signed is computed in Cairo as follows: + +```javascript +let message_hash = PoseidonTrait::new() + .update_with('StarkNet Message') + .update_with('accept_ownership') + .update_with(get_contract_address()) + .update_with(current_owner.get_coordinates().unwrap_syscall()) + .finalize(); +``` + + + +See [ISRC6::is\_valid\_signature](#ISRC6-is_valid_signature). + + + +See [get\_public\_key](#EthAccountComponent-get_public_key). + + + +See [set\_public\_key](#EthAccountComponent-set_public_key). + + +#### Internal functions [!toc] [#EthAccountComponent-Internal-Functions] + + +Initializes the account with the given public key, and registers the `ISRC6` interface ID. + +Emits an [OwnerAdded](#EthAccountComponent-OwnerAdded) event. + + + +Validates that the caller is the account itself. Otherwise it reverts. + + + +Validates that `new_owner` accepted the ownership of the contract through a signature. + +Requirements: + +- The signature must be valid for the `new_owner`. + +This function assumes that `current_owner` is the current owner of the contract, and does not validate this assumption. + + + +Validates a transaction signature from the [global context](https://github.com/starkware-libs/cairo/blob/main/corelib/src/starknet/info.cairo#L61). + +Returns the short string `'VALID'` if valid, otherwise it reverts. + + + +Set the public key without validating the caller. + +Emits an [OwnerAdded](#EthAccountComponent-OwnerAdded) event. + +The usage of this method outside the `set_public_key` function is discouraged. + + + +Validates the provided `signature` for the `hash`, using the account's current public key. + + +#### Events [!toc] [#EthAccountComponent-Events] + +The `guid` is computed as the hash of the public key, using the poseidon hash function. + + +Emitted when a `public_key` is added. + + + +Emitted when a `public_key` is removed. + + +## Extensions + +### `SRC9Component` [toc] [#SRC9Component] + + +```rust +use openzeppelin_account::extensions::SRC9Component; +``` + +OutsideExecution component implementing [`ISRC9_V2`](#ISRC9_V2). + +This component is signature-agnostic, meaning it can be integrated into any account contract, as long as the account implements the ISRC6 interface. + +Embeddable Implementations + +#### OutsideExecutionV2Impl [!toc] [#SRC9Component-Embeddable-Impls-OutsideExecutionV2Impl] + +- [`execute_from_outside_v2(self, outside_execution, signature)`](#SRC9Component-execute_from_outside_v2) +- [`is_valid_outside_execution_nonce(self, nonce)`](#SRC9Component-is_valid_outside_execution_nonce) + +Internal Implementations + +#### InternalImpl [!toc] [#SRC9Component-InternalImpl] + +- [`initializer(self)`](#SRC9Component-initializer) + +#### Embeddable functions [!toc] [#SRC9Component-Embeddable-Functions] + + +Allows anyone to submit a transaction on behalf of the account as long as they have the relevant signatures. + +This method allows reentrancy. A call to `__execute__` or `execute_from_outside_v2` can trigger another nested transaction to `execute_from_outside_v2`. This implementation verifies that the provided `signature` matches the hash of `outside_execution` and that `nonce` was not already used. + +Arguments: + +- `outside_execution` - The parameters of the transaction to execute. +- `signature` - A valid signature on the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message encoding of `outside_execution`. + +Requirements: + +- The caller must be the `outside_execution.caller` unless 'ANY\_CALLER' is used. +- The current time must be within the `outside_execution.execute_after` and `outside_execution.execute_before` span. +- The `outside_execution.nonce` must not be used before. +- The `signature` must be valid. + + + +Returns the status of a given nonce. `true` if the nonce is available to use. + + +#### Internal functions [!toc] [#SRC9Component-Internal-Functions] + + +Initializes the account by registering the `ISRC9_V2` interface ID. + + +## Presets + +### `AccountUpgradeable` [toc] [#AccountUpgradeable] + + +```rust +use openzeppelin_presets::AccountUpgradeable; +``` + +Upgradeable account which can change its public key and declare, deploy, or call contracts. Supports outside execution by implementing [SRC9](#SRC9Component). + +[Sierra class hash](../presets) + +```text +{{AccountUpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, public_key)`](#AccountUpgradeable-constructor) + +Embedded Implementations + +AccountComponent + +- [`AccountMixinImpl`](#AccountComponent-Embeddable-Mixin-Impl) + +SRC9Component + +- [`OutsideExecutionV2Impl`](#SRC9Component-Embeddable-Impls-OutsideExecutionV2Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#AccountUpgradeable-upgrade) + +#### Constructor [!toc] [#AccountUpgradeable-Constructor] + + +Sets the account `public_key` and registers the interfaces the contract supports. + + +#### External functions [!toc] [#AccountUpgradeable-External-Functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the account contract itself. +- `new_class_hash` cannot be zero. + + +### `EthAccountUpgradeable` [toc] [#EthAccountUpgradeable] + + +```rust +use openzeppelin_presets::EthAccountUpgradeable; +``` + +Upgradeable account which can change its public key and declare, deploy, or call contracts, using Ethereum signing keys. Supports outside execution by implementing [SRC9](#SRC9Component). + +The `EthPublicKey` type is an alias for `starknet::secp256k1::Secp256k1Point`. + +[Sierra class hash](../presets) + +```text +{{EthAccountUpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, public_key)`](#EthAccountUpgradeable-constructor) + +Embedded Implementations + +EthAccountComponent + +- [`EthAccountMixinImpl`](#EthAccountComponent-Embeddable-Mixin-Impl) + +SRC9Component + +- [`OutsideExecutionV2Impl`](#SRC9Component-Embeddable-Impls-OutsideExecutionV2Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#EthAccountUpgradeable-upgrade) + +#### Constructor [!toc] [#EthAccountUpgradeable-Constructor] + + +Sets the account `public_key` and registers the interfaces the contract supports. + + +#### External functions [!toc] [#EthAccountUpgradeable-External-Functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the account contract itself. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/alpha/api/erc1155.mdx b/docs/content/contracts-cairo/alpha/api/erc1155.mdx new file mode 100644 index 00000000..56cc5d5c --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/erc1155.mdx @@ -0,0 +1,788 @@ +--- +title: ERC1155 +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This module provides interfaces, presets, and utilities related to ERC1155 contracts. + +For an overview of ERC1155, read our [ERC1155 guide](../erc1155). + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### `IERC1155` [toc] [#IERC1155] + + + +```rust +use openzeppelin_interfaces::erc1155::IERC1155; +``` + +Interface of the IERC1155 standard as defined in [EIP1155](https://eips.ethereum.org/EIPS/eip-1155). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x6114a8f75559e1b39fcba08ce02961a1aa082d9256a158dd3e64964e4b1b52 +``` + +Functions + +- [`balance_of(account, token_id)`](#IERC1155-balance_of) +- [`balance_of_batch(accounts, token_ids)`](#IERC1155-balance_of_batch) +- [`safe_transfer_from(from, to, token_id, value, data)`](#IERC1155-safe_transfer_from) +- [`safe_batch_transfer_from(from, to, token_ids, values, data)`](#IERC1155-safe_batch_transfer_from) +- [`set_approval_for_all(operator, approved)`](#IERC1155-set_approval_for_all) +- [`is_approved_for_all(owner, operator)`](#IERC1155-is_approved_for_all) + +Events + +- [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle) +- [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch) +- [`ApprovalForAll(owner, operator, approved)`](#IERC1155-ApprovalForAll) +- [`URI(value, id)`](#IERC1155-URI) + +#### Functions [!toc] [#functions] + + +Returns the amount of `token_id` tokens owned by `account`. + + + +Returns a list of balances derived from the `accounts` and `token_ids` pairs. + + + +Transfers ownership of `value` amount of `token_id` from `from` if `to` is either `IERC1155Receiver` or an account. + +`data` is additional data, it has no specified format and it is passed to `to`. + +Emits a [TransferSingle](#IERC1155-TransferSingle) event. + + + +Transfers ownership of `token_ids` and `values` pairs from `from` if `to` is either `IERC1155Receiver` or an account. + +`data` is additional data, it has no specified format and it is passed to `to`. + +Emits a [TransferBatch](#IERC1155-TransferBatch) event. + + + +Enables or disables approval for `operator` to manage all of the caller's assets. + +Emits an [ApprovalForAll](#IERC1155-ApprovalForAll) event. + + + +Queries if `operator` is an authorized operator for `owner`. + + +#### Events [!toc] [#events] + + +Emitted when `value` amount of `id` token is transferred from `from` to `to` through `operator`. + + + +Emitted when a batch of `values` amount of `ids` tokens are transferred from `from` to `to` through `operator`. + + + +Emitted when `owner` enables or disables `operator` to manage all of the owner's assets. + + + +Emitted when the token URI is updated to `value` for the `id` token. + + +### `IERC1155MetadataURI` [toc] [#IERC1155MetadataURI] + + + +```rust +use openzeppelin_interfaces::erc1155::IERC1155MetadataURI; +``` + +Interface for the optional metadata function in [EIP1155](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +[SRC5 ID](./introspection#ISRC5) + +```text +0xcabe2400d5fe509e1735ba9bad205ba5f3ca6e062da406f72f113feb889ef7 +``` + +Functions + +- [`uri(token_id)`](#IERC1155MetadataURI-uri) + +#### Functions [!toc] [#functions_2] + + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. + + +### `IERC1155Receiver` [toc] [#IERC1155Receiver] + + + +```rust +use openzeppelin_interfaces::erc1155::IERC1155Receiver; +``` + +Interface for contracts that support receiving token transfers from `ERC1155` contracts. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x15e8665b5af20040c3af1670509df02eb916375cdf7d8cbaf7bd553a257515e +``` + +Functions + +- [`on_erc1155_received(operator, from, token_id, value, data)`](#IERC1155Receiver-on_erc1155_received) +- [`on_erc1155_batch_received(operator, from, token_ids, values, data)`](#IERC1155Receiver-on_erc1155_batch_received) + +#### Functions [!toc] [#functions_3] + + +This function is called whenever an ERC1155 `token_id` token is transferred to this `IERC1155Receiver` implementer via [IERC1155::safe\_transfer\_from](#IERC1155-safe_transfer_from) by `operator` from `from`. + + + +This function is called whenever multiple ERC1155 `token_ids` tokens are transferred to this `IERC1155Receiver` implementer via [IERC1155::safe\_batch\_transfer\_from](#IERC1155-safe_batch_transfer_from) by `operator` from `from`. + + +## [](#core)Core + +### `ERC1155Component` [toc] [#ERC1155Component] + + + +```rust +use openzeppelin_token::erc1155::ERC1155Component; +``` + +ERC1155 component implementing [IERC1155](#IERC1155) and [IERC1155MetadataURI](#IERC1155MetadataURI). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +See [Hooks](#ERC1155Component-Hooks) to understand how are hooks used. + +#### Hooks [!toc] [#ERC1155Component-Hooks] + +#### ERC1155HooksTrait [!toc] [#ERC1155Component-ERC1155HooksTrait] + +- [`before_update(self, from, to, token_ids, values)`](#ERC1155Component-before_update) +- [`after_update(self, from, to, token_ids, values)`](#ERC1155Component-after_update) + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC1155MixinImpl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155MixinImpl] + +- [`ERC1155Impl`](#ERC1155Component-Embeddable-Impls-ERC1155Impl) +- [`ERC1155MetadataURIImpl`](#ERC1155Component-Embeddable-Impls-ERC1155MetadataURIImpl) +- [`ERC1155CamelImpl`](#ERC1155Component-Embeddable-Impls-ERC1155CamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls-SRC5Impl) + +Embeddable Implementations + +#### ERC1155Impl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155Impl] + +- [`balance_of(self, account, token_id)`](#ERC1155Component-balance_of) +- [`balance_of_batch(self, accounts, token_ids)`](#ERC1155Component-balance_of_batch) +- [`safe_transfer_from(self, from, to, token_id, value, data)`](#ERC1155Component-safe_transfer_from) +- [`safe_batch_transfer_from(self, from, to, token_ids, values, data)`](#ERC1155Component-safe_batch_transfer_from) +- [`set_approval_for_all(self, operator, approved)`](#ERC1155Component-set_approval_for_all) +- [`is_approved_for_all(self, owner, operator)`](#ERC1155Component-is_approved_for_all) + +#### ERC1155MetadataURIImpl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155MetadataURIImpl] + +- [`uri(self, token_id)`](#ERC1155Component-uri) + +#### ERC1155CamelImpl [!toc] [#ERC1155Component-Embeddable-Impls-ERC1155CamelImpl] + +- [`balanceOf(self, account, tokenId)`](#ERC1155Component-balanceOf) +- [`balanceOfBatch(self, accounts, tokenIds)`](#ERC1155Component-balanceOfBatch) +- [`safeTransferFrom(self, from, to, tokenId, value, data)`](#ERC1155Component-safeTransferFrom) +- [`safeBatchTransferFrom(self, from, to, tokenIds, values, data)`](#ERC1155Component-safeBatchTransferFrom) +- [`setApprovalForAll(self, operator, approved)`](#ERC1155Component-setApprovalForAll) +- [`isApprovedForAll(self, owner, operator)`](#ERC1155Component-isApprovedForAll) + +Internal Functions + +#### InternalImpl [!toc] [#ERC1155Component-InternalImpl] + +- [`initializer(self, base_uri)`](#ERC1155Component-initializer) +- [`initializer_no_metadata(self)`](#ERC1155Component-initializer_no_metadata) +- [`mint_with_acceptance_check(self, to, token_id, value, data)`](#ERC1155Component-mint_with_acceptance_check) +- [`batch_mint_with_acceptance_check(self, to, token_ids, values, data)`](#ERC1155Component-batch_mint_with_acceptance_check) +- [`burn(self, from, token_id, value)`](#ERC1155Component-burn) +- [`batch_burn(self, from, token_ids, values)`](#ERC1155Component-batch_burn) +- [`update_with_acceptance_check(self, from, to, token_ids, values, data)`](#ERC1155Component-update_with_acceptance_check) +- [`update(self, from, to, token_ids, values)`](#ERC1155Component-update) +- [`_set_base_uri(self, base_uri)`](#ERC1155Component-_set_base_uri) + +Events + +IERC1155 + +- [`TransferSingle(operator, from, to, id, value)`](#ERC1155Component-TransferSingle) +- [`TransferBatch(operator, from, to, ids, values)`](#ERC1155Component-TransferBatch) +- [`ApprovalForAll(owner, operator, approved)`](#ERC1155Component-ApprovalForAll) +- [`URI(value, id)`](#ERC1155Component-URI) + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC1155Component is expected to provide an implementation of the ERC1155HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc1155::ERC1155HooksEmptyImpl` which is already available as part of the library for this purpose. + + +Function executed at the beginning of the [update](#ERC1155Component-update) function prior to any other logic. + + + +Function executed at the end of the [update](#ERC1155Component-update) function. + + +#### Embeddable functions [!toc] [#embeddable_functions] + + +Returns the amount of `token_id` tokens owned by `account`. + + + +Returns a list of balances derived from the `accounts` and `token_ids` pairs. + +Requirements: + +- `token_ids` and `accounts` must have the same length. + + + +Transfers ownership of `value` amount of `token_id` from `from` if `to` is either an account or `IERC1155Receiver`. + +`data` is additional data, it has no specified format and it is passed to `to`. + +This function can potentially allow a reentrancy attack when transferring tokens to an untrusted contract, when invoking `on_ERC1155_received` on the receiver. Ensure to follow the checks-effects-interactions pattern and consider employing reentrancy guards when interacting with untrusted contracts. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `from` is not the zero address. +- `to` is not the zero address. +- If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_received` and return the required magic value. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event. + + + +Transfers ownership of `values` and `token_ids` pairs from `from` if `to` is either an account or `IERC1155Receiver`. + +`data` is additional data, it has no specified format and it is passed to `to`. + +This function can potentially allow a reentrancy attack when transferring tokens to an untrusted contract, when invoking `on_ERC1155_batch_received` on the receiver. Ensure to follow the checks-effects-interactions pattern and consider employing reentrancy guards when interacting with untrusted contracts. + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `from` is not the zero address. +- `to` is not the zero address. +- `token_ids` and `values` must have the same length. +- If `to` refers to a non-account contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` and return the acceptance magic value. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event if the arrays contain one element, and [TransferBatch](#ERC1155Component-TransferBatch) otherwise. + + + +Enables or disables approval for `operator` to manage all of the callers assets. + +Requirements: + +- `operator` cannot be the caller. + +Emits an [ApprovalForAll](#ERC1155Component-ApprovalForAll) event. + + + +Queries if `operator` is an authorized operator for `owner`. + + + + +This implementation returns the same URI for **all** token types. It relies on the token type ID substitution mechanism [specified in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +Clients calling this function must replace the `id` substring with the actual token type ID. + + + +See [ERC1155Component::balance\_of](#ERC1155Component-balance_of). + + + +See [ERC1155Component::balance\_of\_batch](#ERC1155Component-balance_of_batch). + + + +See [ERC1155Component::safe\_transfer\_from](#ERC1155Component-safe_transfer_from). + + + +See [ERC1155Component::safe\_batch\_transfer\_from](#ERC1155Component-safe_batch_transfer_from). + + + +See [ERC1155Component::set\_approval\_for\_all](#ERC1155Component-set_approval_for_all). + + + +See [ERC1155Component::is\_approved\_for\_all](#ERC1155Component-is_approved_for_all). + + +#### Internal functions [!toc] [#internal_functions] + + +Initializes the contract by setting the token's base URI as `base_uri`, and registering the supported interfaces. This should only be used inside the contract's constructor. + +Most ERC1155 contracts expose the [IERC1155MetadataURI](#IERC1155MetadataURI) interface which is what this initializer is meant to support. If the contract DOES NOT expose the [IERC1155MetadataURI](#IERC1155MetadataURI) interface, meaning tokens do not have a URI, the contract must instead use [initializer\_no\_metadata](#ERC1155Component-initializer_no_metadata) in the constructor. Failure to abide by these instructions can lead to unexpected issues especially with UIs. + + + +Initializes the contract with no metadata by registering only the IERC1155 interface. + +This initializer should ONLY be used during construction in the very specific instance when the contract does NOT expose the [IERC1155MetadataURI](#IERC1155MetadataURI) interface. Initializing a contract with this initializer means that tokens will not have a URI. + + + +Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`. + +Requirements: + +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_received` and return the acceptance magic value. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event. + + + +Batched version of [mint\_with\_acceptance\_check](#ERC1155Component-mint_with_acceptance_check). + +Requirements: + +- `to` cannot be the zero address. +- `token_ids` and `values` must have the same length. +- If `to` refers to a smart contract, it must implement `IERC1155Receiver::on_ERC1155_batch_received` and return the acceptance magic value. + +Emits a [TransferBatch](#ERC1155Component-TransferBatch) event. + + + +Destroys a `value` amount of tokens of type `token_id` from `from`. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `token_id`. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event. + + + +Batched version of [burn](#ERC1155Component-burn). + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `token_id`. +- `token_ids` and `values` must have the same length. + +Emits a [TransferBatch](#ERC1155Component-TransferBatch) event. + + + +Version of `update` that performs the token acceptance check by calling `onERC1155Received` or `onERC1155BatchReceived` in the receiver if it implements `IERC1155Receiver`, otherwise by checking if it is an account. + +Requirements: + +- `to` is either an account contract or supports the `IERC1155Receiver` interface. +- `token_ids` and `values` must have the same length. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event if the arrays contain one element, and [TransferBatch](#ERC1155Component-TransferBatch) otherwise. + + + +Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` (or `to`) is the zero address. + +Requirements: + +- `token_ids` and `values` must have the same length. + +Emits a [TransferSingle](#ERC1155Component-TransferSingle) event if the arrays contain one element, and [TransferBatch](#ERC1155Component-TransferBatch) otherwise. + +This function can be extended using the [ERC1155HooksTrait](#ERC1155Component-ERC1155HooksTrait), to add functionality before and/or after the transfer, mint, or burn. + +The ERC1155 acceptance check is not performed in this function. See [update\_with\_acceptance\_check](#ERC1155Component-update_with_acceptance_check) instead. + + + +Sets a new URI for all token types, by relying on the token type ID substitution mechanism [specified in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +By this mechanism, any occurrence of the `id` substring in either the URI or any of the values in the JSON file at said URI will be replaced by clients with the token type ID. + +For example, the `https://token-cdn-domain/\id\.json` URI would be interpreted by clients as `https://token-cdn-domain/000000000000...000000000000004cce0.json` for token type ID `0x4cce0`. + +Because these URIs cannot be meaningfully represented by the `URI` event, this function emits no events. + + +#### Events [!toc] [#events_2] + + +See [IERC1155::TransferSingle](#IERC1155-TransferSingle). + + + +See [IERC1155::TransferBatch](#IERC1155-TransferBatch). + + + +See [IERC1155::ApprovalForAll](#IERC1155-ApprovalForAll). + + + + +See [IERC1155::URI](#IERC1155-URI). + + +### `ERC1155ReceiverComponent` [toc] [#ERC1155ReceiverComponent] + + + +```rust +use openzeppelin_token::erc1155::ERC1155ReceiverComponent; +``` + +ERC1155Receiver component implementing [IERC1155Receiver](#IERC1155Receiver). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC1155MixinImpl [!toc] [#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155MixinImpl] + +- [`ERC1155ReceiverImpl`](#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverImpl) +- [`ERC1155ReceiverCamelImpl`](#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls-SRC5Impl) + +Embeddable Implementations + +#### ERC1155ReceiverImpl [!toc] [#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverImpl] + +- [`on_erc1155_received(self, operator, from, token_id, value, data)`](#ERC1155ReceiverComponent-on_erc1155_received) +- [`on_erc1155_batch_received(self, operator, from, token_ids, values, data)`](#ERC1155ReceiverComponent-on_erc1155_batch_received) + +#### ERC1155ReceiverCamelImpl [!toc] [#ERC1155ReceiverComponent-Embeddable-Impls-ERC1155ReceiverCamelImpl] + +- [`onERC1155Received(self, operator, from, tokenId, value, data)`](#ERC1155ReceiverComponent-onERC1155Received) +- [`onERC1155BatchReceived(self, operator, from, tokenIds, values, data)`](#ERC1155ReceiverComponent-onERC1155BatchReceived) + +Internal Functions + +#### InternalImpl [!toc] [#ERC1155ReceiverComponent-InternalImpl] + +- [`initializer(self)`](#ERC1155ReceiverComponent-initializer) + +#### Embeddable functions [!toc] [#embeddable_functions_2] + + +Returns the `IERC1155Receiver` interface ID. + + + +Returns the `IERC1155Receiver` interface ID. + + + +See [ERC1155ReceiverComponent::on\_erc1155\_received](#ERC1155ReceiverComponent-on_erc1155_received). + + + +See [ERC1155ReceiverComponent::on\_erc1155\_batch\_received](#ERC1155ReceiverComponent-on_erc1155_batch_received). + + +#### Internal functions [!toc] [#internal_functions_2] + + +Registers the `IERC1155Receiver` interface ID as supported through introspection. + + +## [](#presets)Presets + +### `ERC1155Upgradeable` [toc] [#ERC1155Upgradeable] + + + +```rust +use openzeppelin_presets::ERC1155; +``` + +Upgradeable ERC1155 contract leveraging [ERC1155Component](#ERC1155Component). + +[Sierra class hash](../presets) + +```text +{{ERC1155UpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, base_uri, recipient, token_ids, values, owner)`](#ERC1155Upgradeable-constructor) + +Embedded Implementations + +ERC1155Component + +- [`ERC1155MixinImpl`](#ERC1155Component-Embeddable-Mixin-Impl) + +OwnableMixinImpl + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#ERC1155Upgradeable-upgrade) + +#### Constructor [!toc] [#ERC1155Upgradeable-constructor-section] + + +Sets the `base_uri` for all tokens and registers the supported interfaces. Mints the `values` for `token_ids` tokens to `recipient`. Assigns `owner` as the contract owner with permissions to upgrade. + +Requirements: + +- `to` is either an account contract (supporting ISRC6) or supports the `IERC1155Receiver` interface. +- `token_ids` and `values` must have the same length. + + +#### External Functions [!toc] [#ERC1155Upgradeable-external-functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/alpha/api/erc20.mdx b/docs/content/contracts-cairo/alpha/api/erc20.mdx new file mode 100644 index 00000000..c7d28f93 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/erc20.mdx @@ -0,0 +1,1585 @@ +--- +title: ERC20 +--- + +This module provides interfaces, presets, and utilities related to ERC20 contracts. + +For an overview of ERC20, read our [ERC20 guide](../erc20). + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `IERC20` [toc] [#IERC20] + + +```rust +use openzeppelin_interfaces::erc20::IERC20; +``` + +Interface of the IERC20 standard as defined in [EIP-20](https://eips.ethereum.org/EIPS/eip-20). + +Functions + +- [`total_supply()`](#IERC20-total_supply) +- [`balance_of(account)`](#IERC20-balance_of) +- [`allowance(owner, spender)`](#IERC20-allowance) +- [`transfer(recipient, amount)`](#IERC20-transfer) +- [`transfer_from(sender, recipient, amount)`](#IERC20-transfer_from) +- [`approve(spender, amount)`](#IERC20-approve) + +Events + +- [`Transfer(from, to, value)`](#IERC20-Transfer) +- [`Approval(owner, spender, value)`](#IERC20-Approval) + +#### Functions [!toc] [#IERC20-Functions] + + +Returns the amount of tokens in existence. + + + +Returns the amount of tokens owned by `account`. + + + +Returns the remaining number of tokens that `spender` is allowed to spend on behalf of `owner` through [transfer\_from](#transfer_from). This is zero by default. + +This value changes when [approve](#IERC20-approve) or [transfer\_from](#IERC20-transfer_from) are called. + + + +Moves `amount` tokens from the caller's token balance to `to`. Returns `true` on success, reverts otherwise. + +Emits a [Transfer](#IERC20-Transfer) event. + + + +Moves `amount` tokens from `sender` to `recipient` using the allowance mechanism. `amount` is then deducted from the caller's allowance. Returns `true` on success, reverts otherwise. + +Emits a [Transfer](#IERC20-Transfer) event. + + + +Sets `amount` as the allowance of `spender` over the caller's tokens. Returns `true` on success, reverts otherwise. + +Emits an [Approval](#ERC20-Approval) event. + + +#### Events [!toc] [#IERC20-Events] + + +Emitted when `value` tokens are moved from one address (`from`) to another (`to`). + +Note that `value` may be zero. + + + +Emitted when the allowance of a `spender` for an `owner` is set. `value` is the new allowance. + + +### `IERC20Metadata` [toc] [#IERC20Metadata] + + +```rust +use openzeppelin_interfaces::erc20::IERC20Metadata; +``` + +Interface for the optional metadata functions in [EIP-20](https://eips.ethereum.org/EIPS/eip-20). + +Functions + +- [`name()`](#IERC20Metadata-name) +- [`symbol()`](#IERC20Metadata-symbol) +- [`decimals()`](#IERC20Metadata-decimals) + +#### Functions [!toc] [#IERC20Metadata-Functions] + + +Returns the name of the token. + + + +Returns the ticker symbol of the token. + + + +Returns the number of decimals the token uses - e.g. `8` means to divide the token amount by `100000000` to get its user-readable representation. + +For example, if `decimals` equals `2`, a balance of `505` tokens should be displayed to a user as `5.05` (`505 / 10 ** 2`). + +Tokens usually opt for a value of `18`, imitating the relationship between Ether and Wei. This is the default value returned by this function. To create a custom decimals implementation, see [Customizing decimals](../erc20#customizing_decimals). + +This information is only used for *display* purposes: it in no way affects any of the arithmetic of the contract. + + +### `IERC20Permit` [toc] [#IERC20Permit] + + +```rust +use openzeppelin_interfaces::erc20::IERC20Permit; +``` + +Interface of the ERC20Permit standard to support gasless token approvals as defined in [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Functions + +- [`permit(owner, spender, amount, deadline, signature)`](#IERC20Permit-permit) +- [`nonces(owner)`](#IERC20Permit-nonces) +- [`DOMAIN_SEPARATOR()`](#IERC20Permit-DOMAIN_SEPARATOR) + +#### Functions [!toc] [#IERC20Permit-Functions] + + +Sets `amount` as the allowance of `spender` over `owner`'s tokens after validating the signature. + + + +Returns the current nonce of `owner`. A nonce value must be included whenever a signature for `permit` call is generated. + + + +Returns the domain separator used in generating a message hash for `permit` signature. The domain hashing logic follows the [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) standard. + + +### `IERC4626` [toc] [#IERC4626] + + +```rust +use openzeppelin_interfaces::erc4626::IERC4626; +``` + +Interface of the IERC4626 standard as defined in [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626). + +Functions + +- [`asset()`](#IERC4626-asset) +- [`total_assets()`](#IERC4626-total_assets) +- [`convert_to_shares(assets)`](#IERC4626-convert_to_shares) +- [`convert_to_assets(shares)`](#IERC4626-convert_to_assets) +- [`max_deposit(receiver)`](#IERC4626-max_deposit) +- [`preview_deposit(assets)`](#IERC4626-preview_deposit) +- [`deposit(assets, receiver)`](#IERC4626-deposit) +- [`max_mint(receiver)`](#IERC4626-max_mint) +- [`preview_mint(shares)`](#IERC4626-preview_mint) +- [`mint(shares, receiver)`](#IERC4626-mint) +- [`max_withdraw(owner)`](#IERC4626-max_withdraw) +- [`preview_withdraw(assets)`](#IERC4626-preview_withdraw) +- [`withdraw(assets, receiver, owner)`](#IERC4626-withdraw) +- [`max_redeem(owner)`](#IERC4626-max_redeem) +- [`preview_redeem(shares)`](#IERC4626-preview_redeem) +- [`redeem(shares, receiver, owner)`](#IERC4626-redeem) + +Events + +- [`Deposit(sender, owner, assets, shares)`](#IERC4626-Deposit) +- [`Withdraw(sender, receiver, owner, assets, shares)`](#IERC4626-Withdraw) + +#### Functions [!toc] [#IERC4626-Functions] + + +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + +Requirements: + +- MUST be an ERC20 token contract. +- MUST NOT panic. + + + +Returns the total amount of the underlying asset that is "managed" by Vault. + +Requirements: + +- SHOULD include any compounding that occurs from yield. +- MUST be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT panic. + + + +Returns the amount of shares that the Vault would exchange for the amount of `assets` provided irrespective of slippage or fees. + +Requirements: + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT panic unless due to integer overflow caused by an unreasonably large input. +- MUST round down towards 0. + +This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from. + + + +Returns the amount of assets that the Vault would exchange for the amount of `shares` provided irrespective of slippage or fees. + +Requirements: + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT panic unless due to integer overflow caused by an unreasonably large input. +- MUST round down towards 0. + +This calculation MAY NOT reflect the "per-user" price-per-share, and instead should reflect the "average-user's" price-per-share, meaning what the average user should expect to see when exchanging to and from. + + + +Returns the maximum amount of the underlying asset that can be deposited into the Vault for `receiver`, through a deposit call. + +Requirements: + +- MUST return a limited value if receiver is subject to some deposit limit. +- MUST return 2 \** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit call in the same transaction i.e. [IERC4626::deposit](#IERC4626-deposit) should return the same or more shares as `preview_deposit` if called in the same transaction. +- MUST NOT account for deposit limits like those returned from [IERC4626::max\_deposit](#IERC4626-max_deposit) and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_shares](#IERC4626-convert_to_shares) and `preview_deposit` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. + + + +Mints Vault shares to `receiver` by depositing exactly amount of `assets`. + +Requirements: + +- MUST emit the [IERC4626::Deposit](#IERC4626-Deposit) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the deposit execution, and are accounted for during deposit. +- MUST panic if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). + +Most implementations will require pre-approval of the Vault with the Vault's underlying asset token. + + + +Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. + +Requirements: + +- MUST return a limited value if receiver is subject to some mint limit. +- MUST return 2 \** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a `mint` call in the same transaction. I.e. [IERC4626::mint](#IERC4626-mint) should return the same or fewer assets as `preview_mint` if called in the same transaction. +- MUST NOT account for mint limits like those returned from [IERC4626::max\_mint](#IERC4626-max_mint) and should always act as though the mint would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_assets](#IERC4626-convert_to_assets) and `preview_mint` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by minting. + + + +Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + +Requirements: + +- MUST emit the [IERC4626::Deposit](#IERC4626-Deposit) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint execution, and are accounted for during mint. +- MUST panic if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc). + +Most implementations will require pre-approval of the Vault with the Vault's underlying asset token. + + + +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call. + +Requirements: + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw call in the same transaction i.e. [IERC4626::withdraw](#IERC4626-withdraw) should return the same or fewer shares as `preview_withdraw` if called in the same transaction. +- MUST NOT account for withdrawal limits like those returned from [IERC4626::max\_withdraw](#IERC4626-max_withdraw) and should always act as though the withdrawal would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_shares](#IERC4626-convert_to_shares) and `preview_withdraw` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing. + + + +Burns shares from owner and sends exactly assets of underlying tokens to receiver. + +Requirements: + +- MUST emit the [IERC4626::Withdraw](#IERC4626-Withdraw) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the withdraw execution, and are accounted for during withdraw. +- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). + +Some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. + + + +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a redeem call. + +Requirements: + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST return `ERC20::balance_of(owner)` if `owner` is not subject to any withdrawal limit or timelock. +- MUST NOT panic. + + + +Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions. + +Requirements: + +- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call in the same transaction i.e. [IERC4626::redeem](#IERC4626-redeem) should return the same or more assets as preview\_redeem if called in the same transaction. +- MUST NOT account for redemption limits like those returned from [IERC4626::max\_redeem](#IERC4626-max_redeem) and should always act as though the redemption would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT panic. + +Any unfavorable discrepancy between [IERC4626::convert\_to\_assets](#IERC4626-convert_to_assets) and `preview_redeem` SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by redeeming. + + + +Burns exactly shares from owner and sends assets of underlying tokens to receiver. + +Requirements: + +- MUST emit the [IERC4626::Withdraw](#IERC4626-Withdraw) event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the redeem execution, and are accounted for during redeem. +- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc). + +Some implementations will require pre-requesting to the Vault before a withdrawal may be performed. Those methods should be performed separately. + + +#### Events [!toc] [#IERC4626-Events] + + +Emitted when `sender` exchanges `assets` for `shares` and transfers those `shares` to `owner`. + + + +Emitted when `sender` exchanges `shares`, owned by `owner`, for `assets` and transfers those `assets` to `receiver`. + + +## Core + +### `ERC20Component` [toc] [#ERC20Component] + + +```rust +use openzeppelin_token::erc20::ERC20Component; +``` + +ERC20 component extending [IERC20](#IERC20) and [IERC20Metadata](#IERC20Metadata). + +See [Hooks](#ERC20Component-Hooks) to understand how are hooks used. + +Hooks + +#### ERC20HooksTrait [!toc] [#ERC20Component-ERC20HooksTrait] + +- [`before_update(self, from, recipient, amount)`](#ERC20Component-before_update) +- [`after_update(self, from, recipient, amount)`](#ERC20Component-after_update) + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC20MixinImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20MixinImpl] + +- [`ERC20Impl`](#ERC20Component-Embeddable-Impls-ERC20Impl) +- [`ERC20MetadataImpl`](#ERC20Component-Embeddable-Impls-ERC20MetadataImpl) +- [`ERC20CamelOnlyImpl`](#ERC20Component-Embeddable-Impls-ERC20CamelOnlyImpl) + +Embeddable Implementations + +#### ERC20Impl [!toc] [#ERC20Component-Embeddable-Impls-ERC20Impl] + +- [`total_supply(self)`](#ERC20Component-total_supply) +- [`balance_of(self, account)`](#ERC20Component-balance_of) +- [`allowance(self, owner, spender)`](#ERC20Component-allowance) +- [`transfer(self, recipient, amount)`](#ERC20Component-transfer) +- [`transfer_from(self, sender, recipient, amount)`](#ERC20Component-transfer_from) +- [`approve(self, spender, amount)`](#ERC20Component-approve) + +#### ERC20MetadataImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20MetadataImpl] + +- [`name(self)`](#ERC20Component-name) +- [`symbol(self)`](#ERC20Component-symbol) +- [`decimals(self)`](#ERC20Component-decimals) + +#### ERC20CamelOnlyImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20CamelOnlyImpl] + +- [`totalSupply(self)`](#ERC20Component-totalSupply) +- [`balanceOf(self, account)`](#ERC20Component-balanceOf) +- [`transferFrom(self, sender, recipient, amount)`](#ERC20Component-transferFrom) + +#### ERC20PermitImpl [!toc] [#ERC20Component-Embeddable-Impls-ERC20PermitImpl] + +- [`permit(self, owner, spender, amount, deadline, signature)`](#ERC20Component-permit) +- [`nonces(self, owner)`](#ERC20Component-nonces) +- [`DOMAIN_SEPARATOR(self)`](#ERC20Component-DOMAIN_SEPARATOR) + +#### SNIP12MetadataExternalImpl [!toc] [#ERC20Component-Embeddable-Impls-SNIP12MetadataExternalImpl] + +- [`snip12_metadata(self)`](#ERC20Component-snip12_metadata) + +Internal implementations + +#### InternalImpl [!toc] [#ERC20Component-InternalImpl] + +- [`initializer(self, name, symbol)`](#ERC20Component-initializer) +- [`mint(self, recipient, amount)`](#ERC20Component-mint) +- [`burn(self, account, amount)`](#ERC20Component-burn) +- [`update(self, from, to, amount)`](#ERC20Component-update) +- [`_transfer(self, sender, recipient, amount)`](#ERC20Component-_transfer) +- [`_approve(self, owner, spender, amount)`](#ERC20Component-_approve) +- [`_spend_allowance(self, owner, spender, amount)`](#ERC20Component-_spend_allowance) + +Events + +- [`Transfer(from, to, value)`](#ERC20Component-Transfer) +- [`Approval(owner, spender, value)`](#ERC20Component-Approval) + +#### Hooks [!toc] [#ERC20Component-Hooks] + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC20Component is expected to provide an implementation of the ERC20HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc20::ERC20HooksEmptyImpl` which is already available as part of the library for this purpose. + + +Function executed at the beginning of the [update](#ERC20Component-update) function prior to any other logic. + + + +Function executed at the end of the [update](#ERC20Component-update) function. + + +#### Embeddable functions [!toc] [#ERC20Component-Embeddable-Functions] + + +See [IERC20::total\_supply](#IERC20-total_supply). + + + +See [IERC20::balance\_of](#IERC20-balance_of). + + + +See [IERC20::allowance](#IERC20-allowance). + + + +See [IERC20::transfer](#IERC20-transfer). + +Requirements: + +- `recipient` cannot be the zero address. +- The caller must have a balance of at least `amount`. + + + +See [IERC20::transfer\_from](#IERC20-transfer_from). + +Requirements: + +- `sender` cannot be the zero address. +- `sender` must have a balance of at least `amount`. +- `recipient` cannot be the zero address. +- The caller must have allowance for `sender`'s tokens of at least `amount`. + + + +See [IERC20::approve](#IERC20-approve). + +Requirements: + +- `spender` cannot be the zero address. + + + +See [IERC20Metadata::name](#IERC20Metadata-name). + + + +See [IERC20Metadata::symbol](#IERC20Metadata-symbol). + + + +See [IERC20Metadata::decimals](#IERC20Metadata-decimals). + + + +See [IERC20::total\_supply](#IERC20-total_supply). + +Supports the Cairo v0 convention of writing external methods in camelCase as discussed [here](https://github.com/OpenZeppelin/cairo-contracts/discussions/34). + + + +See [IERC20::balance\_of](#IERC20-balance_of). + +Supports the Cairo v0 convention of writing external methods in camelCase as discussed [here](https://github.com/OpenZeppelin/cairo-contracts/discussions/34). + + + +See [IERC20::transfer\_from](#IERC20-transfer_from). + +Supports the Cairo v0 convention of writing external methods in camelCase as discussed [here](https://github.com/OpenZeppelin/cairo-contracts/discussions/34). + + + +Sets `amount` as the allowance of `spender` over `owner`'s tokens after validating the signature. + +Requirements: + +- `owner` is a deployed account contract. +- `spender` is not the zero address. +- `deadline` is not a timestamp in the past. +- `signature` is a valid signature that can be validated with a call to `owner` account. +- `signature` must use the current nonce of the `owner`. + +Emits an [Approval](#ERC20-Approval) event. Every successful call increases \`owner's nonce by one. + + + +Returns the current nonce of `owner`. A nonce value must be included whenever a signature for `permit` call is generated. + + + +Returns the domain separator used in generating a message hash for `permit` signature. The domain hashing logic follows the [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) standard. + + + +Returns the domain name and version used to generate the message hash for permit signature. + +The returned tuple contains: + +- `t.0`: The name used in the [SNIP12Metadata](./utilities#snip12) implementation. +- `t.1`: The version used in the [SNIP12Metadata](./utilities#snip12) implementation. + + +#### Internal functions [!toc] [#ERC20Component-Internal-Functions] + + +Initializes the contract by setting the token name and symbol. This should be used inside of the contract's constructor. + + + +Creates an `amount` number of tokens and assigns them to `recipient`. + +Emits a [Transfer](#ERC20Component-Transfer) event with `from` being the zero address. + +Requirements: + +- `recipient` cannot be the zero address. + + + +Destroys `amount` number of tokens from `account`. + +Emits a [Transfer](#ERC20Component-Transfer) event with `to` set to the zero address. + +Requirements: + +- `account` cannot be the zero address. + + + +Transfers an `amount` of tokens from `from` to `to`, or alternatively mints (or burns) if `from` (or `to`) is the zero address. + +This function can be extended using the [ERC20HooksTrait](#ERC20Component-ERC20HooksTrait), to add functionality before and/or after the transfer, mint, or burn. + +Emits a [Transfer](#ERC20Component-Transfer) event. + + + +Moves `amount` of tokens from `from` to `to`. + +This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic token fees, slashing mechanisms, etc. + +Emits a [Transfer](#ERC20Component-Transfer) event. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `from` must have a balance of at least `amount`. + + + +Sets `amount` as the allowance of `spender` over `owner`'s tokens. + +This internal function does not check for access permissions but can be useful as a building block, for example to implement automatic allowances on behalf of other addresses. + +Emits an [Approval](#ERC20Component-Approval) event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + + + +Updates `owner`'s allowance for `spender` based on spent `amount`. + +This internal function does not update the allowance value in the case of infinite allowance. + +Possibly emits an [Approval](#ERC20Component-Approval) event. + + +#### Events [!toc] [#ERC20Component-Events] + + +See [IERC20::Transfer](#IERC20-Transfer). + + + +See [IERC20::Approval](#IERC20-Approval). + + +## Extensions + +### `ERC4626Component` [toc] [#ERC4626Component] + + +```rust +use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component; +``` + +Extension of ERC20 that implements the [IERC4626](#IERC4626) interface which allows the minting and burning of "shares" in exchange for an underlying "asset." The component leverages traits to configure fees, limits, and decimals. + +[Immutable Component Config](../components#immutable-config) + +#### constants [!toc] [#ERC4626Component-constants] + +- [`UNDERLYING_DECIMALS`](#ERC4626Component-IC-UNDERLYING_DECIMALS) +- [`DECIMALS_OFFSET`](#ERC4626Component-IC-DECIMALS_OFFSET) + +#### functions [!toc] [#ERC4626Component-functions] + +- [`validate()`](#ERC4626Component-IC-validate) + +Hooks + +#### FeeConfigTrait [!toc] [#ERC4626Component-FeeConfigTrait] + +- [`calculate_deposit_fee(self, assets, shares)`](#ERC4626Component-calculate_deposit_fee) +- [`calculate_mint_fee(self, assets, shares)`](#ERC4626Component-calculate_mint_fee) +- [`calculate_withdraw_fee(self, assets, shares)`](#ERC4626Component-calculate_withdraw_fee) +- [`calculate_redeem_fee(self, assets, shares)`](#ERC4626Component-calculate_redeem_fee) + +#### LimitConfigTrait [!toc] [#ERC4626Component-LimitConfigTrait] + +- [`deposit_limit(self, receiver)`](#ERC4626Component-deposit_limit) +- [`mint_limit(self, receiver)`](#ERC4626Component-mint_limit) +- [`withdraw_limit(self, owner)`](#ERC4626Component-withdraw_limit) +- [`redeem_limit(self, owner)`](#ERC4626Component-redeem_limit) + +#### ERC4626HooksTrait [!toc] [#ERC4626Component-ERC4626HooksTrait] + +- [`before_deposit(self, caller, receiver, assets, shares, fee)`](#ERC4626Component-before_deposit) +- [`after_deposit(self, caller, receiver, assets, shares, fee)`](#ERC4626Component-after_deposit) +- [`before_withdraw(self, caller, receiver, owner, assets, shares, fee)`](#ERC4626Component-before_withdraw) +- [`after_withdraw(self, caller, receiver, owner, assets, shares, fee)`](#ERC4626Component-after_withdraw) + +#### AssetsManagementTrait [!toc] [#ERC4626Component-AssetsManagementTrait] + +- [`get_total_assets(self)`](#ERC4626Component-get_total_assets) +- [`transfer_assets_in(self, from, assets)`](#ERC4626Component-transfer_assets_in) +- [`transfer_assets_out(self, to, assets)`](#ERC4626Component-transfer_assets_out) + +Embeddable Implementations + +#### ERC4626Impl [!toc] [#ERC4626Component-Embeddable-Impls-ERC4626Impl] + +- [`asset(self)`](#ERC4626Component-asset) +- [`total_assets(self)`](#ERC4626Component-total_assets) +- [`convert_to_shares(self, assets)`](#ERC4626Component-convert_to_shares) +- [`convert_to_assets(self, shares)`](#ERC4626Component-convert_to_assets) +- [`max_deposit(self, receiver)`](#ERC4626Component-max_deposit) +- [`preview_deposit(self, assets)`](#ERC4626Component-preview_deposit) +- [`deposit(self, assets, receiver)`](#ERC4626Component-deposit) +- [`max_mint(self, receiver)`](#ERC4626Component-max_mint) +- [`preview_mint(self, shares)`](#ERC4626Component-preview_mint) +- [`mint(self, shares, receiver)`](#ERC4626Component-mint) +- [`max_withdraw(self, owner)`](#ERC4626Component-max_withdraw) +- [`preview_withdraw(self, assets)`](#ERC4626Component-preview_withdraw) +- [`withdraw(self, assets, receiver, owner)`](#ERC4626Component-withdraw) +- [`max_redeem(self, owner)`](#ERC4626Component-max_redeem) +- [`preview_redeem(self, shares)`](#ERC4626Component-preview_redeem) +- [`redeem(self, shares, receiver, owner)`](#ERC4626Component-redeem) + +#### ERC20Impl [!toc] [#ERC4626Component-Embeddable-Impls-ERC20Impl] + +- [`total_supply(self)`](#ERC20Component-total_supply) +- [`balance_of(self, account)`](#ERC20Component-balance_of) +- [`allowance(self, owner, spender)`](#ERC20Component-allowance) +- [`transfer(self, recipient, amount)`](#ERC20Component-transfer) +- [`transfer_from(self, sender, recipient, amount)`](#ERC20Component-transfer_from) +- [`approve(self, spender, amount)`](#ERC20Component-approve) + +#### ERC4626MetadataImpl [!toc] [#ERC4626Component-Embeddable-Impls-ERC4626MetadataImpl] + +- [`name(self)`](#ERC4626Component-name) +- [`symbol(self)`](#ERC4626Component-symbol) +- [`decimals(self)`](#ERC4626Component-decimals) + +Internal functions + +#### InternalImpl [!toc] [#ERC4626Component-InternalImpl] + +- [`initializer(self, asset_address)`](#ERC4626Component-initializer) +- [`_deposit(self, caller, receiver, assets, shares)`](#ERC4626Component-_deposit) +- [`_withdraw(self, caller, receiver, owner, assets, shares)`](#ERC4626Component-_withdraw) +- [`_convert_to_shares(self, assets, rounding)`](#ERC4626Component-_convert_to_shares) +- [`_convert_to_assets(self, shares, rounding)`](#ERC4626Component-_convert_to_assets) + +#### Immutable Config [!toc] [#ERC4626Component-Immutable-Config] + + +Should match the underlying asset's decimals. The default value is `18`. + + + +Corresponds to the representational offset between `UNDERLYING_DECIMALS` and the vault decimals. The greater the offset, the more expensive it is for attackers to execute an inflation attack. + + + +Validates the given implementation of the contract's configuration. + +Requirements: + +- `UNDERLYING_DECIMALS` + `DECIMALS_OFFSET` cannot exceed 255 (max u8). + +This function is called by the contract's initializer. + + +#### Hooks [!toc] [#ERC4626Component-Hooks] + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC4626Component is expected to provide an implementation of the ERC4626HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc20::extensions::erc4626::ERC4626EmptyHooks` which is already available as part of the library for this purpose. + +#### FeeConfigTrait [!toc] [#ERC4626Component-FeeConfigTrait] + +The logic for calculating entry and exit fees is expected to be defined at the contract level. Defaults to no entry or exit fees. + +The FeeConfigTrait hooks directly into the preview methods of the ERC4626 component. The preview methods must return as close to the exact amount of shares or assets as possible if the actual (previewed) operation occurred in the same transaction (according to [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec). All operations use their corresponding preview method as the value of assets or shares being moved to or from the user. The fees calculated in FeeConfigTrait are used to adjust the final asset and share amounts used in both the preview and the actual operations. + +To transfer fees, this trait needs to be coordinated with `ERC4626Component::ERC4626Hooks`. + +See implementation examples: + +- Contract charging fees in assets: [ERC4626AssetsFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L253) +- Contract charging fees in shares: [ERC4626SharesFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L426) + + +Calculates the entry fee for a deposit during [preview\_deposit](#ERC4626Component-preview_deposit). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [after\_deposit](#ERC4626Component-after_deposit) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be minted to the fee recipient. + + + +Calculates the entry fee for a mint during [preview\_mint](#ERC4626Component-preview_mint). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [after\_deposit](#ERC4626Component-after_deposit) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be minted to the fee recipient. + + + +Calculates the exit fee for a withdraw during [preview\_withdraw](#ERC4626Component-preview_withdraw). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [before\_withdraw](#ERC4626Component-before_withdraw) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be transferred from the owner to the fee recipient. + + + +Calculates the exit fee for a redeem during [preview\_redeem](#ERC4626Component-preview_redeem). The returned fee affects the final asset and share amounts. Fees are not transferred automatically and must be handled in the [before\_withdraw](#ERC4626Component-before_withdraw) hook: asset fees should be transferred from the vault's management to the fee recipient, while share fees should be transferred from the owner to the fee recipient. + + +#### LimitConfigTrait [!toc] [#ERC4626Component-LimitConfigTrait] + +Sets limits to the target exchange type and is expected to be defined at the contract level. These limits correspond directly to the `max_` i.e. `deposit_limit` → `max_deposit`. + +The [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec states that the `max_` methods must take into account all global and user-specific limits. If an operation is disabled (even temporarily), the corresponding limit MUST be `0` and MUST NOT panic. + + +The max deposit allowed. + +Defaults (`Option::None`) to 2 \** 256 - 1. + + + +The max mint allowed. + +Defaults (`Option::None`) to 2 \** 256 - 1. + + + +The max withdraw allowed. + +Defaults (`Option::None`) to the full asset balance of `owner` converted from shares. + + + +The max redeem allowed. + +Defaults (`Option::None`) to the full asset balance of `owner`. + + +#### ERC4626HooksTrait [!toc] [#ERC4626Component-ERC4626HooksTrait] + +Allows contracts to hook logic into deposit and withdraw transactions. This is where contracts can transfer fees. + +ERC4626 preview methods must be inclusive of any entry or exit fees. Fees are calculated using [FeeConfigTrait](#ERC4626Component-FeeConfigTrait) methods and automatically adjust the final asset and share amounts. Fee transfers are handled in `ERC4626HooksTrait` methods. + +Special care must be taken when calling external contracts in these hooks. In that case, consider implementing reentrancy protections. For example, in the `withdraw` flow, the `withdraw_limit` is checked **before** the `before_withdraw` hook is invoked. If this hook performs a reentrant call that invokes `withdraw` again, the subsequent check on `withdraw_limit` will be done before the first withdrawal's core logic (e.g., burning shares and transferring assets) is executed. This could lead to bypassing withdrawal constraints or draining funds. + +See the [ERC4626AssetsFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L253) and [ERC4626SharesFeesMock](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L426) examples. + + +Hooks into [\_deposit](#ERC4626Component-_deposit). + +Executes logic before transferring assets and minting shares. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will spend and receive, respectively. Asset fees are included in assets; share fees are excluded from shares. + + + +Hooks into [\_deposit](#ERC4626Component-_deposit). + +Executes logic after transferring assets and minting shares. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will spend and receive, respectively. Asset fees are included in assets; share fees are excluded from shares. + + + +Hooks into [\_withdraw](#ERC4626Component-_withdraw). + +Executes logic before burning shares and transferring assets. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will receive and spend, respectively. Asset fees are excluded from assets; share fees are included in shares. + + + +Hooks into [\_withdraw](#ERC4626Component-_withdraw). + +Executes logic after burning shares and transferring assets. The fee is calculated via [FeeConfigTrait](#ERC4626Component-FeeConfigTrait). Assets and shares represent the actual amounts the user will receive and spend, respectively. Asset fees are excluded from assets; share fees are included in shares. + + +#### AssetsManagementTrait [!toc] [#ERC4626Component-AssetsManagementTrait] + +Defines how the ERC4626 vault manages its underlying assets. This trait provides the core asset management functionality for the vault, abstracting the actual storage and transfer mechanisms. It enables two primary implementation patterns: + +1. **Self-managed assets**: The vault contract holds assets directly on its own address. This is the default behavior provided by `ERC4626SelfAssetsManagement` implementation. +2. **External vault**: Assets are managed by an external contract, allowing for more complex asset management strategies. The exact implementation is expected to be defined by the contract implementing the ERC4626 component. + +The trait methods are called during deposit, withdrawal, and total assets calculations, ensuring that the vault's share pricing remains accurate regardless of the underlying asset management strategy. + +Implementations must ensure that `get_total_assets` returns the actual amount of assets that can be withdrawn by users. Inaccurate reporting can lead to incorrect share valuations and potential economic attacks. + +See implementation examples: + +- Self-managed vault: [ERC4626SelfAssetsManagement](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20/extensions/erc4626/erc4626.cairo#L760). +- External vault: [ERC4626ExternalAssetsManagement](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo#L92). + + +Returns the total amount of underlying assets under the vault's management. Used for share price calculations and determining the vault's total value. + +This method should return the actual amount of assets that the vault controls and that can be used to satisfy withdrawal requests. For self-managed vaults, this is typically the vault contract's token balance. For external vaults, this should include any assets deposited in external protocols, minus any that are locked or unredeemable. + +The accuracy of this method is critical for proper vault operation: - Overreporting can lead to share dilution and user losses. - Underreporting can lead to share inflation and potential economic attacks. + + + +Transfers assets from an external address into the vault's management. Called during `deposit` and `mint` operations. + +This method should handle the actual transfer of underlying assets from the `from` address into the vault's control. For self-managed vaults, this typically means transferring tokens to the vault contract's address. For external vaults, this might involve transferring into an external contract. + +Requirements: + +- MUST transfer exactly `assets` amount of the underlying token. +- SHOULD revert if the transfer fails or insufficient allowance/balance. + + + +Transfers assets from the vault's management to an external address. Called during withdraw and redeem operations. + +This method should handle the actual transfer of underlying assets from the vault's control to the `to` address. For self-managed vaults, this typically means transferring tokens from the vault contract's address. For external vaults, this might involve withdrawing from an external contract first. + +Requirements: + +- MUST transfer exactly `assets` amount of the underlying token. +- SHOULD revert if insufficient assets are available or transfer fails. + + +#### Embeddable functions [!toc] [#ERC4626Component-Embeddable-Functions] + + +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + + + +Returns the total amount of the underlying asset that is "managed" by Vault. + + + +Returns the amount of shares that the Vault would exchange for the amount of assets provided irrespective of slippage or fees. + +As per the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec, this may panic *only* if there's an overflow from an unreasonably large input. + + + +Returns the amount of assets that the Vault would exchange for the amount of shares provided irrespective of slippage or fees. + +As per the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec, this may panic *only* if there's an overflow from an unreasonably large input. + + + +Returns the maximum amount of the underlying asset that can be deposited into the Vault for the `receiver`, through a [deposit](#ERC4626Component-deposit) call. + +The default max deposit value is 2 \** 256 - 1. + +This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::deposit\_limit](#ERC4626Component-deposit_limit). + + + +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions. + +The default deposit preview value is the full amount of shares. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_deposit\_fee](#ERC4626Component-calculate_deposit_fee). + +This method must be inclusive of entry fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Mints Vault shares to `receiver` by depositing exactly `assets` of underlying tokens. Returns the amount of newly-minted shares. + +Requirements: + +- `assets` is less than or equal to the max deposit amount for `receiver`. + +Emits a [Deposit](#IERC4626-Deposit) event. + + + +Returns the maximum amount of the Vault shares that can be minted for `receiver` through a [mint](#ERC4626Component-mint) call. + +The default max mint value is 2 \** 256 - 1. + +This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::mint\_limit](#ERC4626Component-mint_limit). + + + +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions. + +The default mint preview value is the full amount of assets. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_mint\_fee](#ERC4626Component-calculate_mint_fee). + +This method must be inclusive of entry fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Mints exactly Vault `shares` to `receiver` by depositing amount of underlying tokens. Returns the amount deposited assets. + +Requirements: + +- `shares` is less than or equal to the max shares amount for `receiver`. + +Emits a [Deposit](#IERC4626-Deposit) event. + + + +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a [withdraw](#ERC4626Component-withdraw) call. + +The default max withdraw value is the full balance of assets for `owner` (converted from shares). This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::withdraw\_limit](#ERC4626Component-withdraw_limit). + +With customized limits, the maximum withdraw amount will either be the custom limit itself or `owner`'s total asset balance, whichever value is less. + + + +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions. + +The default withdraw preview value is the full amount of shares. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_withdraw\_fee](#ERC4626Component-calculate_withdraw_fee). + +This method must be inclusive of exit fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Burns shares from `owner` and sends exactly `assets` of underlying tokens to `receiver`. + +Requirements: + +- `assets` is less than or equal to the max withdraw amount of `owner`. + +Emits a [Withdraw](#IERC4626-Withdraw) event. + + + +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a [redeem](#ERC4626Component-redeem) call. + +The default max redeem value is the full balance of assets for `owner`. This can be changed in the implementing contract by defining custom logic in [LimitConfigTrait::redeem\_limit](#ERC4626Component-redeem_limit). + +With customized limits, the maximum redeem amount will either be the custom limit itself or `owner`'s total asset balance, whichever value is less. + + + +Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, given current on-chain conditions. + +The default redeem preview value is the full amount of assets. This can be changed to account for fees, for example, in the implementing contract by defining custom logic in [FeeConfigTrait::calculate\_redeem\_fee](#ERC4626Component-calculate_redeem_fee). + +This method must be inclusive of exit fees to be compliant with the [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) spec. + + + +Burns exactly `shares` from `owner` and sends assets of underlying tokens to `receiver`. + +Requirements: + +- `shares` is less than or equal to the max redeem amount of `owner`. + +Emits a [Withdraw](#IERC4626-Withdraw) event. + + + +Returns the name of the token. + + + +Returns the ticker symbol of the token, usually a shorter version of the name. + + + +Returns the cumulative number of decimals which includes both `UNDERLYING_DECIMALS` and `OFFSET_DECIMALS`. Both of which must be defined in the [ImmutableConfig](#ERC4626Component-Immutable-Config) inside the implementing contract. + + +#### Internal functions [!toc] [#ERC4626Component-Internal-Functions] + + +Validates the [ImmutableConfig](#ERC4626Component-Immutable-Config) constants and sets the `asset_address` to the vault. This should be set in the contract's constructor. + +Requirements: + +- `asset_address` cannot be the zero address. + + + +Internal logic for [deposit](#ERC4626Component-deposit) and [mint](#ERC4626Component-mint). + +Transfers `assets` from `caller` to the Vault contract then mints `shares` to `receiver`. Fees can be transferred in the `ERC4626Hooks::after_deposit` hook which is executed after assets are transferred and shares are minted. + +Requirements: + +- [ERC20::transfer\_from](#ERC20Component-transfer_from) must return true. + +Emits two [ERC20::Transfer](#ERC20Component-Transfer) events (`ERC20::mint` and `ERC20::transfer_from`). + +Emits a [Deposit](#IERC4626-Deposit) event. + + + +Internal logic for [withdraw](#ERC4626Component-withdraw) and [redeem](#ERC4626Component-redeem). + +Burns `shares` from `owner` and then transfers `assets` to `receiver`. Fees can be transferred in the `ERC4626Hooks::before_withdraw` hook which is executed before shares are burned and assets are transferred. + +Requirements: + +- [ERC20::transfer](#ERC20Component-transfer) must return true. + +Emits two [ERC20::Transfer](#ERC20Component-Transfer) events (`ERC20::burn` and `ERC20::transfer`). + +Emits a [Withdraw](#IERC4626-Withdraw) event. + + + +Internal conversion function (from assets to shares) with support for `rounding` direction. + + + +Internal conversion function (from shares to assets) with support for `rounding` direction. + + +## Presets + +### `ERC20Upgradeable` [toc] [#ERC20Upgradeable] + + +```rust +use openzeppelin_presets::ERC20Upgradeable; +``` + +Upgradeable ERC20 contract leveraging [ERC20Component](#ERC20Component) with a fixed-supply mechanism for token distribution. + +[Sierra class hash](../presets) + +```text +{{ERC20UpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, name, symbol, fixed_supply, recipient, owner)`](#ERC20Upgradeable-constructor) + +Embedded Implementations + +#### ERC20MixinImpl [!toc] [#ERC20Upgradeable-Embedded-Impls-ERC20MixinImpl] + +- [`ERC20MixinImpl`](#ERC20Component-Embeddable-Mixin-Impl) + +#### OwnableMixinImpl [!toc] [#ERC20Upgradeable-Embedded-Impls-OwnableMixinImpl] + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#ERC20Upgradeable-upgrade) + +#### Constructor [!toc] [#ERC20Upgradeable-Constructor] + + +Sets the `name` and `symbol` and mints `fixed_supply` tokens to `recipient`. Assigns `owner` as the contract owner with permissions to upgrade. + + +#### External functions [!toc] [#ERC20Upgradeable-External-Functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/alpha/api/erc721.mdx b/docs/content/contracts-cairo/alpha/api/erc721.mdx new file mode 100644 index 00000000..c093c879 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/erc721.mdx @@ -0,0 +1,1159 @@ +--- +title: ERC721 +--- + +This module provides interfaces, presets, and utilities related to ERC721 contracts. + +For an overview of ERC721, read our [ERC721 guide](../erc721). + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +### `IERC721` [toc] [#IERC721] + + +```rust +use openzeppelin_interfaces::erc721::IERC721; +``` + +Interface of the IERC721 standard as defined in [EIP721](https://eips.ethereum.org/EIPS/eip-721). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x33eb2f84c309543403fd69f0d0f363781ef06ef6faeb0131ff16ea3175bd943 +``` + +Functions + +- [`balance_of(account)`](#IERC721-balance_of) +- [`owner_of(token_id)`](#IERC721-owner_of) +- [`safe_transfer_from(from, to, token_id, data)`](#IERC721-safe_transfer_from) +- [`transfer_from(from, to, token_id)`](#IERC721-transfer_from) +- [`approve(to, token_id)`](#IERC721-approve) +- [`set_approval_for_all(operator, approved)`](#IERC721-set_approval_for_all) +- [`get_approved(token_id)`](#IERC721-get_approved) +- [`is_approved_for_all(owner, operator)`](#IERC721-is_approved_for_all) + +Events + +- [`Approval(owner, approved, token_id)`](#IERC721-Approval) +- [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll) +- [`Transfer(from, to, token_id)`](#IERC721-Transfer) + +#### Functions [!toc] [#IERC721-Functions] + + +Returns the number of NFTs owned by `account`. + + + +Returns the owner address of `token_id`. + + + +Transfer ownership of `token_id` from `from` to `to`, checking first that `to` is aware of the ERC721 protocol to prevent tokens being locked forever. For information regarding how contracts communicate their awareness of the ERC721 protocol, see [Receiving Tokens](../erc721#receiving_tokens). + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Transfer ownership of `token_id` from `from` to `to`. + +Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 transfers or else they may be permanently lost. Usage of [IERC721::safe\_transfer\_from](#IERC721-safe_transfer_from) prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Change or reaffirm the approved address for an NFT. + +Emits an [Approval](#IERC721-Approval) event. + + + +Enable or disable approval for `operator` to manage all of the caller's assets. + +Emits an [ApprovalForAll](#IERC721-ApprovalForAll) event. + + + +Returns the address approved for `token_id`. + + + +Query if `operator` is an authorized operator for `owner`. + + +#### Events [!toc] [#IERC721-Events] + + +Emitted when `owner` enables `approved` to manage the `token_id` token. + + + +Emitted when `owner` enables or disables `operator` to manage the `token_id` token. + + + +Emitted when `token_id` token is transferred from `from` to `to`. + + +### `IERC721Metadata` [toc] [#IERC721Metadata] + + +```rust +use openzeppelin_interfaces::erc721::IERC721Metadata; +``` + +Interface for the optional metadata functions in [EIP721](https://eips.ethereum.org/EIPS/eip-721). + +[SRC5 ID](./introspection#ISRC5) + +```text +0xabbcd595a567dce909050a1038e055daccb3c42af06f0add544fa90ee91f25 +``` + +Functions + +- [`name()`](#IERC721Metadata-name) +- [`symbol()`](#IERC721Metadata-symbol) +- [`token_uri(token_id)`](#IERC721Metadata-token_uri) + +#### Functions [!toc] [#IERC721Metadata-Functions] + + +Returns the NFT name. + + + +Returns the NFT ticker symbol. + + + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. + + +### `IERC721Receiver` [toc] [#IERC721Receiver] + + +```rust +use openzeppelin_interfaces::erc721::IERC721Receiver; +``` + +Interface for contracts that support receiving `safe_transfer_from` transfers. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x3a0dff5f70d80458ad14ae37bb182a728e3c8cdda0402a5daa86620bdf910bc +``` + +Functions + +- [`on_erc721_received(operator, from, token_id, data)`](#IERC721Receiver-on_erc721_received) + +#### Functions [!toc] [#IERC721Receiver-Functions] + + +Whenever an IERC721 `token_id` token is transferred to this non-account contract via [IERC721::safe\_transfer\_from](#IERC721-safe_transfer_from) by `operator` from `from`, this function is called. + + +### `IERC721Enumerable` [toc] [#IERC721Enumerable] + + +Interface for the optional enumerable functions in [EIP721](https://eips.ethereum.org/EIPS/eip-721). + +[SRC5 ID](./introspection#ISRC5) + +```text +0x16bc0f502eeaf65ce0b3acb5eea656e2f26979ce6750e8502a82f377e538c87 +``` + +Functions + +- [`total_supply()`](#IERC721Enumerable-total_supply) +- [`token_by_index(index)`](#IERC721Enumerable-token_by_index) +- [`token_of_owner_by_index(owner, index)`](#IERC721Enumerable-token_of_owner_by_index) + +#### Functions [!toc] [#IERC721Enumerable-Functions] + + +Returns the total amount of tokens stored by the contract. + + + +Returns a token id at a given `index` of all the tokens stored by the contract. Use along with [IERC721Enumerable::total\_supply](#IERC721Enumerable-total_supply) to enumerate all tokens. + + + +Returns the token id owned by `owner` at a given `index` of its token list. Use along with [IERC721::balance\_of](#IERC721-balance_of) to enumerate all of `owner`'s tokens. + + +## Core + +### `ERC721Component` [toc] [#ERC721Component] + + +```rust +use openzeppelin_token::erc721::ERC721Component; +``` + +ERC721 component implementing [IERC721](#IERC721) and [IERC721Metadata](#IERC721Metadata). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +See [Hooks](#ERC721Component-Hooks) to understand how are hooks used. + +Hooks + +#### ERC721HooksTrait [!toc] [#ERC721Component-ERC721HooksTrait] + +- [`before_update(self, to, token_id, auth)`](#ERC721Component-before_update) +- [`after_update(self, to, token_id, auth)`](#ERC721Component-after_update) + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERC721MixinImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721MixinImpl] + +- [`ERC721Impl`](#ERC721Component-Embeddable-Impls-ERC721Impl) +- [`ERC721MetadataImpl`](#ERC721Component-Embeddable-Impls-ERC721MetadataImpl) +- [`ERC721CamelOnlyImpl`](#ERC721Component-Embeddable-Impls-ERC721CamelOnlyImpl) +- [`ERC721MetadataCamelOnlyImpl`](#ERC721Component-Embeddable-Impls-ERC721MetadataCamelOnlyImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### ERC721Impl [!toc] [#ERC721Component-Embeddable-Impls-ERC721Impl] + +- [`balance_of(self, account)`](#ERC721Component-balance_of) +- [`owner_of(self, token_id)`](#ERC721Component-owner_of) +- [`safe_transfer_from(self, from, to, token_id, data)`](#ERC721Component-safe_transfer_from) +- [`transfer_from(self, from, to, token_id)`](#ERC721Component-transfer_from) +- [`approve(self, to, token_id)`](#ERC721Component-approve) +- [`set_approval_for_all(self, operator, approved)`](#ERC721Component-set_approval_for_all) +- [`get_approved(self, token_id)`](#ERC721Component-get_approved) +- [`is_approved_for_all(self, owner, operator)`](#ERC721Component-is_approved_for_all) + +#### ERC721MetadataImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721MetadataImpl] + +- [`name(self)`](#ERC721Component-name) +- [`symbol(self)`](#ERC721Component-symbol) +- [`token_uri(self, token_id)`](#ERC721Component-token_uri) + +#### ERC721CamelOnlyImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721CamelOnlyImpl] + +- [`balanceOf(self, account)`](#ERC721Component-balanceOf) +- [`ownerOf(self, tokenId)`](#ERC721Component-ownerOf) +- [`safeTransferFrom(self, from, to, tokenId, data)`](#ERC721Component-safeTransferFrom) +- [`transferFrom(self, from, to, tokenId)`](#ERC721Component-transferFrom) +- [`setApprovalForAll(self, operator, approved)`](#ERC721Component-setApprovalForAll) +- [`getApproved(self, tokenId)`](#ERC721Component-getApproved) +- [`isApprovedForAll(self, owner, operator)`](#ERC721Component-isApprovedForAll) + +#### ERC721MetadataCamelOnlyImpl [!toc] [#ERC721Component-Embeddable-Impls-ERC721MetadataCamelOnlyImpl] + +- [`tokenURI(self, tokenId)`](#ERC721Component-tokenURI) + +#### SRC5Impl [!toc] [#ERC721Component-Embeddable-Impls-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +Internal functions + +#### InternalImpl [!toc] [#ERC721Component-InternalImpl] + +- [`initializer(self, name, symbol, base_uri)`](#ERC721Component-initializer) +- [`initializer_no_metadata(self)`](#ERC721Component-initializer_no_metadata) +- [`exists(self, token_id)`](#ERC721Component-exists) +- [`transfer(self, from, to, token_id)`](#ERC721Component-transfer) +- [`mint(self, to, token_id)`](#ERC721Component-mint) +- [`safe_transfer(self, from, to, token_id, data)`](#ERC721Component-safe_transfer) +- [`safe_mint(self, to, token_id, data)`](#ERC721Component-safe_mint) +- [`burn(self, token_id)`](#ERC721Component-burn) +- [`update(self, to, token_id, auth)`](#ERC721Component-update) +- [`_owner_of(self, token_id)`](#ERC721Component-_owner_of) +- [`_require_owned(self, token_id)`](#ERC721Component-_require_owned) +- [`_approve(self, to, token_id, auth)`](#ERC721Component-_approve) +- [`_approve_with_optional_event(self, to, token_id, auth, emit_event)`](#ERC721Component-_approve_with_optional_event) +- [`_set_approval_for_all(self, owner, operator, approved)`](#ERC721Component-_set_approval_for_all) +- [`_set_base_uri(self, base_uri)`](#ERC721Component-_set_base_uri) +- [`_base_uri(self)`](#ERC721Component-_base_uri) +- [`_is_authorized(self, owner, spender, token_id)`](#ERC721Component-_is_authorized) +- [`_check_authorized(self, owner, spender, token_id)`](#ERC721Component-_check_authorized) + +Events + +IERC721 + +- [`Approval(owner, approved, token_id)`](#ERC721Component-Approval) +- [`ApprovalForAll(owner, operator, approved)`](#ERC721Component-ApprovalForAll) +- [`Transfer(from, to, token_id)`](#ERC721Component-Transfer) + +#### Hooks [!toc] [#ERC721Component-Hooks] + +Hooks are functions which implementations can extend the functionality of the component source code. Every contract using ERC721Component is expected to provide an implementation of the ERC721HooksTrait. For basic token contracts, an empty implementation with no logic must be provided. + +You can use `openzeppelin_token::erc721::ERC721HooksEmptyImpl` which is already available as part of the library for this purpose. + + +Function executed at the beginning of the [update](#ERC721Component-update) function prior to any other logic. + + + +Function executed at the end of the [update](#ERC721Component-update) function. + + +#### [](#embeddable_functions)Embeddable functions [!toc] [#embeddable_functions] + + +See [IERC721::balance\_of](#IERC721-balance_of). + + + +See [IERC721::owner\_of](#IERC721-owner_of). + +Requirements: + +- `token_id` exists. + + + +See [IERC721::safe\_transfer\_from](#IERC721-safe_transfer_from). + +Requirements: + +- Caller is either approved or the `token_id` owner. +- `to` is not the zero address. +- `from` is not the zero address. +- `token_id` exists. +- `to` is either an account contract or supports the [IERC721Receiver](#IERC721Receiver) interface. + + + +See [IERC721::transfer\_from](#IERC721-transfer_from). + +Requirements: + +- Caller either approved or the `token_id` owner. +- `to` is not the zero address. +- `from` is not the zero address. +- `token_id` exists. + + + +See [IERC721::approve](#IERC721-approve). + +Requirements: + +- The caller is either an approved operator or the `token_id` owner. +- `to` cannot be the token owner or the zero address. +- `token_id` exists. + + + +See [IERC721::set\_approval\_for\_all](#IERC721-set_approval_for_all). + +Requirements: + +- `operator` is not the zero address. + + + +See [IERC721::get\_approved](#IERC721-get_approved). + +Requirements: + +- `token_id` exists. + + + +See [IERC721::is\_approved\_for\_all](#IERC721-is_approved_for_all). + + + +See [IERC721Metadata::name](#IERC721Metadata-name). + + + +See [IERC721Metadata::symbol](#IERC721Metadata-symbol). + + + +Returns the Uniform Resource Identifier (URI) for the `token_id` token. If a base URI is set, the resulting URI for each token will be the concatenation of the base URI and the token ID. For example, the base URI `https://token-cdn-domain/` would be returned as `https://token-cdn-domain/123` for token ID `123`. + +If the URI is not set for `token_id`, the return value will be an empty `ByteArray`. + + + +See [ERC721Component::balance\_of](#ERC721Component-balance_of). + + + +See [ERC721Component::owner\_of](#ERC721Component-owner_of). + + + +See [ERC721Component::safe\_transfer\_from](#ERC721Component-safe_transfer_from). + + + +See [ERC721Component::transfer\_from](#ERC721Component-transfer_from). + + + +See [ERC721Component::set\_approval\_for\_all](#ERC721Component-set_approval_for_all). + + + +See [ERC721Component::get\_approved](#ERC721Component-get_approved). + + + +See [ERC721Component::is\_approved\_for\_all](#ERC721Component-is_approved_for_all). + + + +See [ERC721Component::token\_uri](#ERC721Component-token_uri). + + +#### [](#internal_functions)Internal functions [!toc] [#internal_functions] + + +Initializes the contract by setting the token name and symbol. This should be used inside the contract's constructor. + +Most ERC721 contracts expose the [IERC721Metadata](#IERC721Metadata) interface which is what this initializer is meant to support. If the contract DOES NOT expose the [IERC721Metadata](#IERC721Metadata) interface, meaning the token does not have a name, symbol, or URI, the contract must instead use [initializer\_no\_metadata](#ERC721Component-initializer_no_metadata) in the constructor. Failure to abide by these instructions can lead to unexpected issues especially with UIs. + + + +Initializes the contract with no metadata by registering only the IERC721 interface. + +This initializer should ONLY be used during construction in the very specific instance when the contract does NOT expose the [IERC721Metadata](#IERC721Metadata) interface. Initializing a contract with this initializer means that tokens will not have a name, symbol, or URI. + + + +Internal function that returns whether `token_id` exists. + +Tokens start existing when they are minted ([mint](#ERC721-mint)), and stop existing when they are burned ([burn](#ERC721-burn)). + + + +Transfers `token_id` from `from` to `to`. + +Internal function without access restriction. + +This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + +Requirements: + +- `to` is not the zero address. +- `from` is the token owner. +- `token_id` exists. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Mints `token_id` and transfers it to `to`. Internal function without access restriction. + +This method may lead to the loss of tokens if `to` is not aware of the ERC721 protocol. + +Requirements: + +- `to` is not the zero address. +- `token_id` does not exist. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Transfers ownership of `token_id` from `from` if `to` is either an account or `IERC721Receiver`. + +`data` is additional data, it has no specified format and is forwarded in `IERC721Receiver::on_erc721_received` to `to`. + +This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + +Requirements: + +- `to` cannot be the zero address. +- `from` must be the token owner. +- `token_id` exists. +- `to` is either an account contract or supports the `IERC721Receiver` interface. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Mints `token_id` if `to` is either an account or `IERC721Receiver`. + +`data` is additional data, it has no specified format and is forwarded in `IERC721Receiver::on_erc721_received` to `to`. + +This method makes an external call to the recipient contract, which can lead to reentrancy vulnerabilities. + +Requirements: + +- `token_id` does not exist. +- `to` is either an account contract or supports the `IERC721Receiver` interface. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Destroys `token_id`. The approval is cleared when the token is burned. + +This internal function does not check if the caller is authorized to operate on the token. + +Requirements: + +- `token_id` exists. + +Emits a [Transfer](#IERC721-Transfer) event. + + + +Transfers `token_id` from its current owner to `to`, or alternatively mints (or burns) if the current owner (or `to`) is the zero address. Returns the owner of the `token_id` before the update. + +The `auth` argument is optional. If the value passed is non-zero, then this function will check that `auth` is either the owner of the token, or approved to operate on the token (by the owner). + +Emits a [Transfer](#IERC721-Transfer) event. + +This function can be extended using the `ERC721HooksTrait`, to add functionality before and/or after the transfer, mint, or burn. + + + +Internal function that returns the owner address of `token_id`. + + + +Version of [\_owner\_of](#ERC721Component-_owner_of) that panics if owner is the zero address. + + + +Approve `to` to operate on `token_id` + +The `auth` argument is optional. If the value passed is non-zero, then this function will check that `auth` is either the owner of the token, or approved to operate on all tokens held by this owner. + +Emits an [Approval](#IERC721-Approval) event. + + + +Variant of [\_approve](#ERC721Component-_approve) with an optional flag to enable or disable the `Approval` event. The event is not emitted in the context of transfers. + +If `auth` is zero and `emit_event` is false, this function will not check that the token exists. + +Requirements: + +- if `auth` is non-zero, it must be either the owner of the token or approved to operate on all of its tokens. + +May emit an [Approval](#IERC721-Approval) event. + + + +Enables or disables approval for `operator` to manage all of the `owner` assets. + +Requirements: + +- `operator` is not the zero address. + +Emits an [Approval](#IERC721-Approval) event. + + + +Internal function that sets the `base_uri`. + + + +Base URI for computing [token\_uri](#IERC721Metadata-token_uri). + +If set, the resulting URI for each token will be the concatenation of the base URI and the token ID. Returns an empty `ByteArray` if not set. + + + +Returns whether `spender` is allowed to manage `owner`'s tokens, or `token_id` in particular (ignoring whether it is owned by `owner`). + +This function assumes that `owner` is the actual owner of `token_id` and does not verify this assumption. + + + +Checks if `spender` can operate on `token_id`, assuming the provided `owner` is the actual owner. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. +- `spender` must be the owner of `token_id` or be approved to operate on it. + +This function assumes that `owner` is the actual owner of `token_id` and does not verify this assumption. + + +#### [](#events_2)Events [!toc] [#events_2] + + +See [IERC721::Approval](#IERC721-Approval). + + + +See [IERC721::ApprovalForAll](#IERC721-ApprovalForAll). + + + +See [IERC721::Transfer](#IERC721-Transfer). + + +### `ERC721ReceiverComponent` [toc] [#ERC721ReceiverComponent] + + +```rust +use openzeppelin_token::erc721::ERC721ReceiverComponent; +``` + +ERC721Receiver component implementing [IERC721Receiver](#IERC721Receiver). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +[Embeddable Mixin Implementations](../components#mixins) + +#### ERCReceiverMixinImpl [!toc] [#ERC721ReceiverComponent-Embeddable-Impls-ERCReceiverMixinImpl] + +- [`ERC721ReceiverImpl`](#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl) +- [`ERC721ReceiverCamelImpl`](#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) + +Embeddable Implementations + +#### ERC721ReceiverImpl [!toc] [#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverImpl] + +- [`on_erc721_received(self, operator, from, token_id, data)`](#ERC721ReceiverComponent-on_erc721_received) + +#### ERC721ReceiverCamelImpl [!toc] [#ERC721ReceiverComponent-Embeddable-Impls-ERC721ReceiverCamelImpl] + +- [`onERC721Received(self, operator, from, tokenId, data)`](#ERC721ReceiverComponent-onERC721Received) + +Internal Functions + +#### InternalImpl [!toc] [#ERC721ReceiverComponent-InternalImpl] + +- [`initializer(self)`](#ERC721ReceiverComponent-initializer) + +#### [](#embeddable_functions_2)Embeddable functions [!toc] [#embeddable_functions_2] + + +Returns the `IERC721Receiver` interface ID. + + + +See [ERC721ReceiverComponent::on\_erc721\_received](#ERC721ReceiverComponent-on_erc721_received). + + +#### [](#internal_functions_2)Internal functions [!toc] [#internal_functions_2] + + +Registers the `IERC721Receiver` interface ID as supported through introspection. + + +## Extensions + +### `ERC721EnumerableComponent` [toc] [#ERC721EnumerableComponent] + + +```rust +use openzeppelin_token::erc721::extensions::ERC721EnumerableComponent; +``` + +Extension of ERC721 as defined in the EIP that adds enumerability of all the token ids in the contract as well as all token ids owned by each account. This extension allows contracts to publish their entire list of NFTs and make them discoverable. + +Implementing [ERC721Component](#ERC721Component) is a requirement for this component to be implemented. + +To properly track token ids, this extension requires that the [ERC721EnumerableComponent::before\_update](#ERC721EnumerableComponent-before_update) function is called before every transfer, mint, or burn operation. For this, the [ERC721HooksTrait::before\_update](#ERC721Component-before_update) hook must be used. Here's how the hook should be implemented in a contract: + +```[ +#[starknet::contract] +mod ERC721EnumerableContract { + (...) + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: ERC721EnumerableComponent, storage: erc721_enumerable, event: ERC721EnumerableEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + impl ERC721HooksImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.erc721_enumerable.before_update(to, token_id); + } + } +} +``` + +Embeddable Implementations + +#### ERC721EnumerableImpl [!toc] [#ERC721EnumerableComponent-Embeddable-Impls-ERC721EnumerableImpl] + +- [`total_supply(self)`](#ERC721EnumerableComponent-total_supply) +- [`token_by_index(self, index)`](#ERC721EnumerableComponent-token_by_index) +- [`token_of_owner_by_index(self, address, index)`](#ERC721EnumerableComponent-token_of_owner_by_index) + +Internal functions + +#### InternalImpl [!toc] [#ERC721EnumerableComponent-InternalImpl] + +- [`initializer(self)`](#ERC721EnumerableComponent-initializer) +- [`before_update(self, to, token_id)`](#ERC721EnumerableComponent-before_update) +- [`all_tokens_of_owner(self, owner)`](#ERC721EnumerableComponent-all_tokens_of_owner) +- [`_add_token_to_owner_enumeration(self, to, token_id)`](#ERC721EnumerableComponent-_add_token_to_owner_enumeration) +- [`_add_token_to_all_tokens_enumeration(self, token_id)`](#ERC721EnumerableComponent-_add_token_to_all_tokens_enumeration) +- [`_remove_token_from_owner_enumeration(self, from, token_id)`](#ERC721EnumerableComponent-_remove_token_from_owner_enumeration) +- [`_remove_token_from_all_tokens_enumeration(self, token_id)`](#ERC721EnumerableComponent-_remove_token_from_all_tokens_enumeration) + +#### [](#ERC721EnumerableComponent-Embeddable-functions)Embeddable functions [!toc] [#ERC721EnumerableComponent-Embeddable-functions] + + +Returns the current amount of votes that `account` has. + + + +See [IERC721Enumerable::token\_by\_index](#IERC721Enumerable-token_by_index). + +Requirements: + +- `index` is less than the total token supply. + + + +See [IERC721Enumerable::token\_of\_owner\_by\_index](#IERC721Enumerable-token_of_owner_by_index). + +Requirements: + +- `index` is less than `owner`'s token balance. +- `owner` is not the zero address. + + +#### [](#ERC721EnumerableComponent-Internal-functions)Internal functions [!toc] [#ERC721EnumerableComponent-Internal-functions] + + +Registers the `IERC721Enumerable` interface ID as supported through introspection. + + + +Updates the ownership and token-tracking data structures. + +When a token is minted (or burned), `token_id` is added to (or removed from) the token-tracking structures. + +When a token is transferred, minted, or burned, the ownership-tracking data structures reflect the change in ownership of `token_id`. + +This must be added to the implementing contract's [ERC721HooksTrait::before\_update](#ERC721Component-before_update) hook. + + + +Returns a list of all token ids owned by the specified `owner`. This function provides a more efficient alternative to calling `ERC721::balance_of` and iterating through tokens with `ERC721Enumerable::token_of_owner_by_index`. + +Requirements: + +- `owner` is not the zero address. + + + +Adds token to this extension's ownership-tracking data structures. + + + +Adds token to this extension's token-tracking data structures. + + + +Removes a token from this extension's ownership-tracking data structures. + +This has 0(1) time complexity but alters the indexed order of owned tokens by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. + + + +Removes `token_id` from this extension's token-tracking data structures. + +This has 0(1) time complexity but alters the indexed order by swapping `token_id` and the index thereof with the last token id and the index thereof e.g. removing `1` from `[1, 2, 3, 4]` results in `[4, 2, 3]`. + + +## Presets + +### `ERC721Upgradeable` [toc] [#ERC721Upgradeable] + + +```rust +use openzeppelin_presets::ERC721Upgradeable; +``` + +Upgradeable ERC721 contract leveraging [ERC721Component](#ERC721Component). + +[Sierra class hash](../presets) + +```text +{{ERC721UpgradeableClassHash}} +``` + +Constructor + +- [`constructor(self, name, symbol, recipient, token_ids, base_uri, owner)`](#ERC721Upgradeable-constructor) + +Embedded Implementations + +ERC721MixinImpl + +- [`ERC721MixinImpl`](#ERC721Component-Embeddable-Mixin-Impl) + +OwnableMixinImpl + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +External Functions + +- [`upgrade(self, new_class_hash)`](#ERC721Upgradeable-upgrade) + +#### [](#ERC721Upgradeable-constructor-section)Constructor [!toc] [#ERC721Upgradeable-constructor-section] + + +Sets the `name` and `symbol`. Mints `token_ids` tokens to `recipient` and sets the `base_uri`. Assigns `owner` as the contract owner with permissions to upgrade. + + +#### [](#ERC721Upgradeable-external-functions)External functions [!toc] [#ERC721Upgradeable-external-functions] + + +Upgrades the contract to a new implementation given by `new_class_hash`. + +Requirements: + +- The caller is the contract owner. +- `new_class_hash` cannot be zero. + diff --git a/docs/content/contracts-cairo/alpha/api/finance.mdx b/docs/content/contracts-cairo/alpha/api/finance.mdx new file mode 100644 index 00000000..f0981e42 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/finance.mdx @@ -0,0 +1,333 @@ +--- +title: Finance +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate includes primitives for financial systems. + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### `IVesting` [toc] [#IVesting] + + + +```rust +use openzeppelin_interfaces::vesting::IVesting; +``` + +Common interface for contracts implementing the vesting functionality. + +Functions + +- [`start()`](#IVesting-start) +- [`cliff()`](#IVesting-cliff) +- [`duration()`](#IVesting-duration) +- [`end()`](#IVesting-end) +- [`released(token)`](#IVesting-released) +- [`releasable(token)`](#IVesting-releasable) +- [`vested_amount(token, timestamp)`](#IVesting-vested_amount) +- [`release(token)`](#IVesting-release) + +Events + +- [`AmountReleased(token, amount)`](#IVesting-AmountReleased) + +#### Functions [!toc] [#IVesting-Functions] + + +Returns the timestamp marking the beginning of the vesting period. + + + +Returns the timestamp marking the end of the cliff period. + + + +Returns the total duration of the vesting period. + + + +Returns the timestamp marking the end of the vesting period. + + + +Returns the already released amount for a given `token`. + + + +Returns the amount of a given `token` that can be released at the time of the call. + + + +Returns the total vested amount of a specified `token` at a given `timestamp`. + + + +Releases the amount of a given `token` that has already vested and returns that amount. + +May emit an [AmountReleased](#IVesting-AmountReleased) event. + + +#### Events [!toc] [#IVesting-Events] + + +Emitted when vested tokens are released to the beneficiary. + + +## [](#vesting)Vesting + +### `VestingComponent` [toc] [#VestingComponent] + + + +```rust +use openzeppelin_finance::vesting::VestingComponent; +``` + +Vesting component implementing the [`IVesting`](#IVesting) interface. + +Vesting Schedule Trait Implementations + +#### functions [!toc] [#VestingComponent-Vesting-Schedule-functions] + +- [`calculate_vested_amount(self, token, total_allocation, timestamp, start, duration, cliff)`](#VestingComponent-calculate_vested_amount) + +Embeddable Implementations + +#### VestingImpl [!toc] [#VestingComponent-Embeddable-Impls-VestingImpl] + +- [`start(self)`](#VestingComponent-start) +- [`cliff(self)`](#VestingComponent-cliff) +- [`duration(self)`](#VestingComponent-duration) +- [`end(self)`](#VestingComponent-end) +- [`released(self, token)`](#VestingComponent-released) +- [`releasable(self, token)`](#VestingComponent-releasable) +- [`vested_amount(self, token, timestamp)`](#VestingComponent-vested_amount) +- [`release(self, token)`](#VestingComponent-release) + +Internal implementations + +#### InternalImpl [!toc] [#VestingComponent-InternalImpl] + +- [`initializer(self, start, duration, cliff_duration)`](#VestingComponent-initializer) +- [`resolve_vested_amount(self, token, timestamp)`](#VestingComponent-resolve_vested_amount) + +A trait that defines the logic for calculating the vested amount based on a given timestamp. + +You can read more about the trait's purpose and how to use it [here](../finance#vesting-schedule). + + +Calculates and returns the vested amount at a given `timestamp` based on the core vesting parameters. + + +#### Functions [!toc] [#VestingComponent-Functions] + + +Returns the timestamp marking the beginning of the vesting period. + + + +Returns the timestamp marking the end of the cliff period. + + + +Returns the total duration of the vesting period. + + + +Returns the timestamp marking the end of the vesting period. + + + +Returns the already released amount for a given `token`. + + + +Returns the amount of a given `token` that can be released at the time of the call. + + + +Returns the total vested amount of a specified `token` at a given `timestamp`. + + + +Releases the amount of a given `token` that has already vested and returns that amount. + +If the releasable amount is zero, this function won't emit the event or attempt to transfer the tokens. + +Requirements: + +- `transfer` call to the `token` must return `true` indicating a successful transfer. + +May emit an [AmountReleased](#IVesting-AmountReleased) event. + + +#### Internal functions [!toc] [#VestingComponent-Internal-Functions] + + +Initializes the component by setting the vesting `start`, `duration` and `cliff_duration`. To prevent reinitialization, this should only be used inside of a contract's constructor. + +Requirements: + +- `cliff_duration` must be less than or equal to `duration`. + + + +Returns the vested amount that's calculated using the [VestingSchedule](#VestingComponent-Vesting-Schedule) trait implementation. + + +### `LinearVestingSchedule` [toc] [#LinearVestingSchedule] + + + +```rust +use openzeppelin_finance::vesting::LinearVestingSchedule; +``` + +Defines the logic for calculating the vested amount, incorporating a cliff period. It returns 0 before the cliff ends. After the cliff period, the vested amount returned is directly proportional to the time passed since the start of the vesting schedule. + +## [](#presets)Presets + +### `VestingWallet` [toc] [#VestingWallet] + + + +```rust +use openzeppelin::presets::VestingWallet; +``` + +A non-upgradable contract leveraging [VestingComponent](#VestingComponent) and [OwnableComponent](./access#OwnableComponent). + +The contract is intentionally designed to be non-upgradable to ensure that neither the vesting initiator nor the vesting beneficiary can modify the vesting schedule without the consent of the other party. + +[Sierra class hash](../presets) + +```text +{{VestingWalletClassHash}} +``` + +Constructor + +- [`constructor(self, beneficiary, start, duration, cliff_duration)`](#VestingWallet-constructor) + +Embedded Implementations + +VestingComponent + +- [`VestingImpl`](#VestingComponent-Embeddable-Impls-VestingImpl) + +OwnableComponent + +- [`OwnableMixinImpl`](./access#OwnableComponent-Mixin-Impl) + +#### Constructor [!toc] [#VestingWallet-constructor-section] + + +Initializes the vesting component by setting the vesting `start`, `duration` and `cliff_duration`. Assigns `beneficiary` as the contract owner and the vesting beneficiary. + +Requirements: + +- `cliff_duration` must be less than or equal to `duration`. + diff --git a/docs/content/contracts-cairo/alpha/api/governance.mdx b/docs/content/contracts-cairo/alpha/api/governance.mdx new file mode 100644 index 00000000..e5d5bbe1 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/governance.mdx @@ -0,0 +1,4064 @@ +--- +title: Governance +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate includes primitives for on-chain governance. + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### `IGovernor` [toc] [#IGovernor] + + + +```rust +use openzeppelin_interfaces::governor::IGovernor; +``` + +Interface of a governor contract. + +[SRC5 ID](./introspection#ISRC5) + +```text +0x1100a1f8546595b5bd75a6cd8fcc5b015370655e66f275963321c5cd0357ac9 +``` + +Functions + +- [`name()`](#IGovernor-name) +- [`version()`](#IGovernor-version) +- [`COUNTING_MODE()`](#IGovernor-COUNTING_MODE) +- [`hash_proposal(calls, description_hash)`](#IGovernor-hash_proposal) +- [`state(proposal_id)`](#IGovernor-state) +- [`proposal_threshold()`](#IGovernor-proposal_threshold) +- [`proposal_snapshot(proposal_id)`](#IGovernor-proposal_snapshot) +- [`proposal_deadline(proposal_id)`](#IGovernor-proposal_deadline) +- [`proposal_proposer(proposal_id)`](#IGovernor-proposal_proposer) +- [`proposal_eta(proposal_id)`](#IGovernor-proposal_eta) +- [`proposal_needs_queuing(proposal_id)`](#IGovernor-proposal_needs_queuing) +- [`voting_delay()`](#IGovernor-voting_delay) +- [`voting_period()`](#IGovernor-voting_period) +- [`quorum(timepoint)`](#IGovernor-quorum) +- [`get_votes(account, timepoint)`](#IGovernor-get_votes) +- [`get_votes_with_params(account, timepoint, params)`](#IGovernor-get_votes_with_params) +- [`has_voted(proposal_id, account)`](#IGovernor-has_voted) +- [`propose(calls, description)`](#IGovernor-propose) +- [`queue(calls, description_hash)`](#IGovernor-queue) +- [`execute(calls, description_hash)`](#IGovernor-execute) +- [`cancel(proposal_id, description_hash)`](#IGovernor-cancel) +- [`cast_vote(proposal_id, support)`](#IGovernor-cast_vote) +- [`cast_vote_with_reason(proposal_id, support, reason)`](#IGovernor-cast_vote_with_reason) +- [`cast_vote_with_reason_and_params(proposal_id, support, reason, params)`](#IGovernor-cast_vote_with_reason_and_params) +- [`cast_vote_by_sig(proposal_id, support, reason, signature)`](#IGovernor-cast_vote_by_sig) +- [`cast_vote_with_reason_and_params_by_sig(proposal_id, support, reason, params, signature)`](#IGovernor-cast_vote_with_reason_and_params_by_sig) +- [`nonces(voter)`](#IGovernor-nonces) +- [`relay(call)`](#IGovernor-relay) + +Events + +- [`ProposalCreated(proposal_id, proposer, calls, signatures, vote_start, vote_end, description)`](#IGovernor-ProposalCreated) +- [`ProposalQueued(proposal_id, eta_seconds)`](#IGovernor-ProposalQueued) +- [`ProposalExecuted(proposal_id)`](#IGovernor-ProposalExecuted) +- [`ProposalCanceled(proposal_id)`](#IGovernor-ProposalCanceled) +- [`VoteCast(voter, proposal_id, support, weight, reason)`](#IGovernor-VoteCast) +- [`VoteCastWithParams(voter, proposal_id, support, weight, reason, params)`](#IGovernor-VoteCastWithParams) + +#### Functions [!toc] [#IGovernor-Functions] + + +Name of the governor instance (used in building the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +Version of the governor instance (used in building [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +A description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + + +The string can be decoded by the standard [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) JavaScript class. + + + + +Hashing function used to (re)build the proposal id from the proposal details. + + + +Returns the state of a proposal, given its id. + + + +The number of votes required in order for a voter to become a proposer. + + + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the following block. + + + +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is possible to cast a vote during this block. + + + +The account that created a proposal. + + + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the executor's clock which may be different. In most cases this will be a timestamp. + + + +Whether a proposal needs to be queued before execution. This indicates if the proposal needs to go through a timelock. + + + +Delay between when a proposal is created and when the vote starts. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + + + +Delay between when a vote starts and when it ends. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +The `voting_delay` can delay the start of the vote. This must be considered when setting the voting duration compared to the voting delay. + +This value is stored when the proposal is submitted so that possible changes to the value do not affect proposals that have already been submitted. + + + +Minimum number of votes required for a proposal to be successful. + +The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows the quorum to scale depending on values such as the total supply of a token at this timepoint. + + + +Returns the voting power of an `account` at a specific `timepoint`. + +This can be implemented in a number of ways, for example by reading the delegated balance from one (or multiple) `ERC20Votes` tokens. + + + +Returns the voting power of an `account` at a specific `timepoint`, given additional encoded parameters. + + + +Returns whether an `account` has cast a vote on a proposal. + + + +Creates a new proposal. Vote starts after a delay specified by `voting_delay` and lasts for a duration specified by `voting_period`. + +The state of the Governor and targets may change between the proposal creation and its execution. This may be the result of third party actions on the targeted contracts, or other governor proposals. For example, the balance of this contract could be updated or its access control permissions may be modified, possibly compromising the proposal's ability to execute successfully (e.g. the governor doesn't have enough value to cover a proposal with multiple transfers). + +Returns the id of the proposal. + + + +Queue a proposal. Some governors require this step to be performed before execution can happen. If queuing is not necessary, this function may revert. + +Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + +Returns the id of the proposal. + + + +Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the deadline to be reached. Depending on the governor it might also be required that the proposal was queued and that some delay passed. + +Some modules can modify the requirements for execution, for example by adding an additional timelock (See `timelock_controller`). + +Returns the id of the proposal. + + + +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. before the vote starts. + +Returns the id of the proposal. + + + +Cast a vote on a proposal. + +Returns the weight of the vote. + + + +Cast a vote on a proposal with a `reason`. + +Returns the weight of the vote. + + + +Cast a vote on a proposal with a reason and additional encoded parameters. + +Returns the weight of the vote. + + + +Cast a vote on a proposal using the voter's signature. + +Returns the weight of the vote. + + + +Cast a vote on a proposal with a reason and additional encoded parameters using the `voter`'s signature. + +Returns the weight of the vote. + + + +Returns the next unused nonce for an address. + + + +Relays a transaction or function call to an arbitrary target. + +In cases where the governance executor is some contract other than the governor itself, like when using a timelock, this function can be invoked in a governance proposal to recover tokens that were sent to the governor contract by mistake. + +If the executor is simply the governor itself, use of `relay` is redundant. + + +#### Events [!toc] [#IGovernor-Events] + + +Emitted when a proposal is created. + + + +Emitted when a proposal is queued. + + + +Emitted when a proposal is executed. + + + +Emitted when a proposal is canceled. + + + +Emitted when a vote is cast. + + + +Emitted when a vote is cast with params. + + +### `IMultisig` [toc] [#IMultisig] + + + +```rust +use openzeppelin_interfaces::multisig::IMultisig; +``` + +Interface of a multisig contract. + +Functions + +- [`get_quorum()`](#IMultisig-get_quorum) +- [`is_signer(signer)`](#IMultisig-is_signer) +- [`get_signers()`](#IMultisig-get_signers) +- [`is_confirmed(id)`](#IMultisig-is_confirmed) +- [`is_confirmed_by(id, signer)`](#IMultisig-is_confirmed_by) +- [`is_executed(id)`](#IMultisig-is_executed) +- [`get_submitted_block(id)`](#IMultisig-get_submitted_block) +- [`get_transaction_state(id)`](#IMultisig-get_transaction_state) +- [`get_transaction_confirmations(id)`](#IMultisig-get_transaction_confirmations) +- [`hash_transaction(to, selector, calldata, salt)`](#IMultisig-hash_transaction) +- [`hash_transaction_batch(calls, salt)`](#IMultisig-hash_transaction_batch) +- [`add_signers(new_quorum, signers_to_add)`](#IMultisig-add_signers) +- [`remove_signers(new_quorum, signers_to_remove)`](#IMultisig-remove_signers) +- [`replace_signer(signer_to_remove, signer_to_add)`](#IMultisig-replace_signer) +- [`change_quorum(new_quorum)`](#IMultisig-change_quorum) +- [`submit_transaction(to, selector, calldata, salt)`](#IMultisig-submit_transaction) +- [`submit_transaction_batch(calls, salt)`](#IMultisig-submit_transaction_batch) +- [`confirm_transaction(id)`](#IMultisig-confirm_transaction) +- [`revoke_confirmation(id)`](#IMultisig-revoke_confirmation) +- [`execute_transaction(to, selector, calldata, salt)`](#IMultisig-execute_transaction) +- [`execute_transaction_batch(calls, salt)`](#IMultisig-execute_transaction_batch) + +Events + +- [`SignerAdded(signer)`](#IMultisig-SignerAdded) +- [`SignerRemoved(signer)`](#IMultisig-SignerRemoved) +- [`QuorumUpdated(old_quorum, new_quorum)`](#IMultisig-QuorumUpdated) +- [`TransactionSubmitted(id, signer)`](#IMultisig-TransactionSubmitted) +- [`TransactionConfirmed(id, signer)`](#IMultisig-TransactionConfirmed) +- [`ConfirmationRevoked(id, signer)`](#IMultisig-ConfirmationRevoked) +- [`TransactionExecuted(id)`](#IMultisig-TransactionExecuted) +- [`CallSalt(id, salt)`](#IMultisig-CallSalt) + +#### Functions [!toc] [#IMultisig-Functions] + + +Returns the current quorum value. The quorum is the minimum number of confirmations required to approve a transaction. + + + +Returns whether the given `signer` is registered. Only registered signers can submit, confirm, or execute transactions. + + + +Returns the list of all current signers. + + + +Returns whether the transaction with the given `id` has been confirmed. + + + +Returns whether the transaction with the given `id` has been confirmed by the specified `signer`. + + + +Returns whether the transaction with the given `id` has been executed. + + + +Returns the block number when the transaction with the given `id` was submitted. + + + +Returns the current state of the transaction with the given `id`. + + + +Returns the number of confirmations from registered signers for the transaction with the specified `id`. + + + +Returns the computed identifier of a transaction containing a single call. + + + +Returns the computed identifier of a transaction containing a batch of calls. + + + +Adds new signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after addition. + +Emits a [SignerAdded](#IMultisig-SignerAdded) event for each signer added. + +Emits a [QuorumUpdated](#IMultisig-QuorumUpdated) event if the quorum changes. + + + +Removes signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after removal. + +Emits a [SignerRemoved](#IMultisig-SignerRemoved) event for each signer removed. + +Emits a [QuorumUpdated](#IMultisig-QuorumUpdated) event if the quorum changes. + + + +Replaces an existing signer with a new signer. + +Requirements: + +- The caller must be the contract itself. +- `signer_to_remove` must be an existing signer. +- `signer_to_add` must not be an existing signer. + +Emits a [SignerRemoved](#IMultisig-SignerRemoved) event for the removed signer. + +Emits a [SignerAdded](#IMultisig-SignerAdded) event for the new signer. + + + +Updates the quorum value to `new_quorum` if it differs from the current quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be non-zero. +- `new_quorum` must be less than or equal to the total number of signers. + +Emits a [QuorumUpdated](#IMultisig-QuorumUpdated) event if the quorum changes. + + + +Submits a new transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#IMultisig-TransactionSubmitted) event. + +Emits a [CallSalt](#IMultisig-CallSalt) event if `salt` is not zero. + + + + +Submits a new batch transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#IMultisig-TransactionSubmitted) event. + +Emits a [CallSalt](#IMultisig-CallSalt) event if `salt` is not zero. + + + +Confirms a transaction with the given `id`. + +Requirements: + +- The caller must be a registered signer. +- The transaction must exist and not be executed. +- The caller must not have already confirmed the transaction. + +Emits a [TransactionConfirmed](#IMultisig-TransactionConfirmed) event. + + + +Revokes a previous confirmation for a transaction with the given `id`. + +Requirements: + +- The transaction must exist and not be executed. +- The caller must have previously confirmed the transaction. + +Emits a [ConfirmationRevoked](#IMultisig-ConfirmationRevoked) event. + + + +Executes a confirmed transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#IMultisig-TransactionExecuted) event. + + + +Executes a confirmed batch transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#IMultisig-TransactionExecuted) event. + + +#### Events [!toc] [#IMultisig-Events] + + +Emitted when a new `signer` is added. + + + +Emitted when a `signer` is removed. + + + +Emitted when the `quorum` value is updated. + + + +Emitted when a new transaction is submitted by a `signer`. + + + +Emitted when a transaction is confirmed by a `signer`. + + + +Emitted when a `signer` revokes his confirmation. + + + +Emitted when a transaction is executed. + + + +Emitted when a new transaction is submitted with non-zero salt. + + +### `ITimelock` [toc] [#ITimelock] + + + +```rust +use openzeppelin_interfaces::timelock::ITimelock; +``` + +Interface of a timelock contract. + +Functions + +- [`is_operation(id)`](#ITimelock-is_operation) +- [`is_operation_pending(id)`](#ITimelock-is_operation_pending) +- [`is_operation_ready(id)`](#ITimelock-is_operation_ready) +- [`is_operation_done(id)`](#ITimelock-is_operation_done) +- [`get_timestamp(id)`](#ITimelock-get_timestamp) +- [`get_operation_state(id)`](#ITimelock-get_operation_state) +- [`get_min_delay()`](#ITimelock-get_min_delay) +- [`hash_operation(call, predecessor, salt)`](#ITimelock-hash_operation) +- [`hash_operation_batch(calls, predecessor, salt)`](#ITimelock-hash_operation_batch) +- [`schedule(call, predecessor, salt, delay)`](#ITimelock-schedule) +- [`schedule_batch(calls, predecessor, salt, delay)`](#ITimelock-schedule_batch) +- [`cancel(id)`](#ITimelock-cancel) +- [`execute(call, predecessor, salt)`](#ITimelock-execute) +- [`execute_batch(calls, predecessor, salt)`](#ITimelock-execute_batch) +- [`update_delay(new_delay)`](#ITimelock-update_delay) + +Events + +- [`CallScheduled(id, index, call, predecessor, delay)`](#ITimelock-CallScheduled) +- [`CallExecuted(id, index, call)`](#ITimelock-CallExecuted) +- [`CallSalt(id, salt)`](#ITimelock-CallSalt) +- [`CallCancelled(id)`](#ITimelock-CallCancelled) +- [`MinDelayChanged(old_duration, new_duration)`](#ITimelock-MinDelayChanged) + +#### Functions [!toc] [#ITimelock-Functions] + + +Returns whether `id` corresponds to a registered operation. This includes the OperationStates: `Waiting`, `Ready`, and `Done`. + + + +Returns whether the `id` OperationState is pending or not. Note that a pending operation may be either `Waiting` or `Ready`. + + + +Returns whether the `id` OperationState is `Ready` or not. + + + +Returns whether the `id` OperationState is `Done` or not. + + + +Returns the timestamp at which `id` becomes `Ready`. + +`0` means the OperationState is `Unset` and `1` means the OperationState is `Done`. + + + + +Returns the current state of the operation with the given `id`. + +The possible states are: + +- `Unset`: the operation has not been scheduled or has been canceled. +- `Waiting`: the operation has been scheduled and is pending the scheduled delay. +- `Ready`: the timer has expired, and the operation is eligible for execution. +- `Done`: the operation has been executed. + + + +Returns the minimum delay in seconds for an operation to become valid. This value can be changed by executing an operation that calls `update_delay`. + + + +Returns the identifier of an operation containing a single transaction. + + + +Returns the identifier of an operation containing a batch of transactions. + + + +Schedule an operation containing a single transaction. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. + +Emits [CallScheduled](#ITimelock-CallScheduled) event. Emits [CallSalt](#ITimelock-CallSalt) event if `salt` is not zero. + + + +Schedule an operation containing a batch of transactions. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. + +Emits one [CallScheduled](#ITimelock-CallScheduled) event for each transaction in the batch. Emits [CallSalt](#ITimelock-CallSalt) event if `salt` is not zero. + + + +Cancels an operation. A canceled operation returns to `Unset` OperationState. + +Requirements: + +- The caller must have the `CANCELLER_ROLE` role. +- `id` must be a pending operation. + +Emits a [CallCancelled](#ITimelock-CallCancelled) event. + + + +Execute a (Ready) operation containing a single Call. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#ITimelock-CallExecuted) event. + +This function can reenter, but it doesn't pose a risk because [`_after_call(self: @ContractState, id: felt252)` internal](#TimelockControllerComponent-_after_call) checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Execute a (Ready) operation containing a batch of Calls. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#ITimelock-CallExecuted) event for each Call. + +This function can reenter, but it doesn't pose a risk because `_after_call` checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Changes the minimum timelock duration for future operations. + +Requirements: + +- The caller must be the timelock itself. This can only be achieved by scheduling and later executing an operation where the timelock is the target and the data is the serialized call to this function. + +Emits a [MinDelayChanged](#ITimelock-MinDelayChanged) event. + + +#### Events [!toc] [#ITimelock-Events] + + +Emitted when `call` is scheduled as part of operation `id`. + + + +Emitted when `call` is performed as part of operation `id`. + + + +Emitted when a new proposal is scheduled with non-zero salt. + + + +Emitted when operation `id` is cancelled. + + + +Emitted when the minimum delay for future operations is modified. + + +### `IVotes` [toc] [#IVotes] + + + +```rust +use openzeppelin_interfaces::votes::IVotes; +``` + +Common interface for Votes-enabled contracts. + +Functions + +- [`get_votes(account)`](#IVotes-get_votes) +- [`get_past_votes(account, timepoint)`](#IVotes-get_past_votes) +- [`get_past_total_supply(timepoint)`](#IVotes-get_past_total_supply) +- [`delegates(account)`](#IVotes-delegates) +- [`delegate(delegatee)`](#IVotes-delegate) +- [`delegate_by_sig(delegator, delegatee, nonce, expiry, signature)`](#IVotes-delegate_by_sig) +- [`clock()`](#IVotes-clock) +- [`CLOCK_MODE()`](#IVotes-CLOCK_MODE) + +#### Functions [!toc] [#IVotes-Functions] + + +Returns the current amount of votes that `account` has. + + + +Returns the amount of votes that `account` had at a specific moment in the past. + + + +Returns the total supply of votes available at a specific moment in the past. + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. Votes that have not been delegated are still part of total supply, even though they would not participate in a vote. + + + +Returns the delegate that `account` has chosen. + + + +Delegates votes from the sender to `delegatee`. + + + +Delegates votes from `delegator` to `delegatee` through a [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message signature validation. + + + +Returns the current timepoint determined by the contract's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the contract is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + +## [](#governor)Governor + +This modular system of Governor components allows the deployment of easily customizable on-chain voting protocols. + +For a walkthrough of how to implement a Governor, check the [Governor](../governance/governor) page. + +### `GovernorComponent` [toc] [#GovernorComponent] + + + +```rust +use openzeppelin_governance::governor::GovernorComponent; +``` + +Core of the governance system. + +The extension traits presented below are what make the GovernorComponent a modular and configurable system. The embeddable and internal implementations depends on these trait. They can be implemented locally in the contract, or through the provided library [component extensions](#governor_extensions). + +Implementing [SRC5Component](./introspection#SRC5Component) is a requirement for this component to be implemented. + +Extensions traits + +#### GovernorSettingsTrait [!toc] [#GovernorComponent-GovernorSettingsTrait] + +- [`voting_delay(self)`](#GovernorComponent-GovernorSettingsTrait-voting_delay) +- [`voting_period(self)`](#GovernorComponent-GovernorSettingsTrait-voting_period) +- [`proposal_threshold(self)`](#GovernorComponent-GovernorSettingsTrait-proposal_threshold) + +#### GovernorQuorumTrait [!toc] [#GovernorComponent-GovernorQuorumTrait] + +- [`quorum(self, timepoint)`](#GovernorComponent-GovernorQuorumTrait-quorum) + +#### GovernorCountingTrait [!toc] [#GovernorComponent-GovernorCountingTrait] + +- [`counting_mode(self)`](#GovernorComponent-GovernorCountingTrait-counting_mode) +- [`count_vote(self, proposal_id, account, support, total_weight, params)`](#GovernorComponent-GovernorCountingTrait-count_vote) +- [`has_voted(self, proposal_id, account)`](#GovernorComponent-GovernorCountingTrait-has_voted) +- [`quorum_reached(self, proposal_id)`](#GovernorComponent-GovernorCountingTrait-quorum_reached) +- [`vote_succeeded(self, proposal_id)`](#GovernorComponent-GovernorCountingTrait-vote_succeeded) + +#### GovernorVotesTrait [!toc] [#GovernorComponent-GovernorVotesTrait] + +- [`clock(self)`](#GovernorComponent-GovernorVotesTrait-clock) +- [`CLOCK_MODE(self)`](#GovernorComponent-GovernorVotesTrait-CLOCK_MODE) +- [`get_votes(self, account, timepoint, params)`](#GovernorComponent-GovernorVotesTrait-get_votes) + +#### GovernorExecutionTrait [!toc] [#GovernorComponent-GovernorExecutionTrait] + +- [`state(self, proposal_id)`](#GovernorComponent-GovernorExecutionTrait-state) +- [`executor(self)`](#GovernorComponent-GovernorExecutionTrait-executor) +- [`execute_operations(self, proposal_id, calls, description_hash)`](#GovernorComponent-GovernorExecutionTrait-execute_operations) +- [`queue_operations(self, proposal_id, calls, description_hash)`](#GovernorComponent-GovernorExecutionTrait-queue_operations) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorComponent-GovernorExecutionTrait-proposal_needs_queuing) +- [`cancel_operations(self, proposal_id, description_hash)`](#GovernorComponent-GovernorExecutionTrait-cancel_operations) + +Embeddable Implementations + +#### GovernorImpl [!toc] [#GovernorComponent-GovernorImpl] + +- [`name(self)`](#GovernorComponent-name) +- [`version(self)`](#GovernorComponent-version) +- [`COUNTING_MODE(self)`](#GovernorComponent-COUNTING_MODE) +- [`hash_proposal(self, calls, description_hash)`](#GovernorComponent-hash_proposal) +- [`state(self, proposal_id)`](#GovernorComponent-state) +- [`proposal_threshold(self)`](#GovernorComponent-proposal_threshold) +- [`proposal_snapshot(self, proposal_id)`](#GovernorComponent-proposal_snapshot) +- [`proposal_deadline(self, proposal_id)`](#GovernorComponent-proposal_deadline) +- [`proposal_proposer(self, proposal_id)`](#GovernorComponent-proposal_proposer) +- [`proposal_eta(self, proposal_id)`](#GovernorComponent-proposal_eta) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorComponent-proposal_needs_queuing) +- [`voting_delay(self)`](#GovernorComponent-voting_delay) +- [`voting_period(self)`](#GovernorComponent-voting_period) +- [`quorum(self, timepoint)`](#GovernorComponent-quorum) +- [`get_votes(self, account, timepoint)`](#GovernorComponent-get_votes) +- [`get_votes_with_params(self, account, timepoint, params)`](#GovernorComponent-get_votes_with_params) +- [`has_voted(self, proposal_id, account)`](#GovernorComponent-has_voted) +- [`propose(self, calls, description)`](#GovernorComponent-propose) +- [`queue(self, calls, description_hash)`](#GovernorComponent-queue) +- [`execute(self, calls, description_hash)`](#GovernorComponent-execute) +- [`cancel(self, proposal_id, description_hash)`](#GovernorComponent-cancel) +- [`cast_vote(self, proposal_id, support)`](#GovernorComponent-cast_vote) +- [`cast_vote_with_reason(self, proposal_id, support, reason)`](#GovernorComponent-cast_vote_with_reason) +- [`cast_vote_with_reason_and_params(self, proposal_id, support, reason, params)`](#GovernorComponent-cast_vote_with_reason_and_params) +- [`cast_vote_by_sig(self, proposal_id, support, reason, signature)`](#GovernorComponent-cast_vote_by_sig) +- [`cast_vote_with_reason_and_params_by_sig(self, proposal_id, support, reason, params, signature)`](#GovernorComponent-cast_vote_with_reason_and_params_by_sig) +- [`nonces(self, voter)`](#GovernorComponent-nonces) +- [`relay(self, call)`](#GovernorComponent-relay) + +Internal Implementations + +#### InternalImpl [!toc] [#GovernorComponent-InternalImpl] + +- [`initializer(self)`](#GovernorComponent-initializer) +- [`get_proposal(self, proposal_id)`](#GovernorComponent-get_proposal) +- [`is_valid_description_for_proposer(self, proposer, description)`](#GovernorComponent-is_valid_description_for_proposer) +- [`_hash_proposal(self, calls, description_hash)`](#GovernorComponent-_hash_proposal) +- [`_proposal_snapshot(self, proposal_id)`](#GovernorComponent-_proposal_snapshot) +- [`_proposal_deadline(self, proposal_id)`](#GovernorComponent-_proposal_deadline) +- [`_proposal_proposer(self, proposal_id)`](#GovernorComponent-_proposal_proposer) +- [`_proposal_eta(self, proposal_id)`](#GovernorComponent-_proposal_eta) + +#### InternalExtendedImpl [!toc] [#GovernorComponent-InternalExtendedImpl] + +- [`assert_only_governance(self)`](#GovernorComponent-assert_only_governance) +- [`validate_state(self, proposal_id, allowed_states)`](#GovernorComponent-validate_state) +- [`use_nonce(self, voter)`](#GovernorComponent-use_nonce) +- [`_get_votes(self, account, timepoint, params)`](#GovernorComponent-_get_votes) +- [`_proposal_threshold(self)`](#GovernorComponent-_proposal_threshold) +- [`_state(self, proposal_id)`](#GovernorComponent-_state) +- [`_propose(self, calls, description, proposer)`](#GovernorComponent-_propose) +- [`_cancel(self, proposal_id, description_hash)`](#GovernorComponent-_cancel) +- [`_count_vote(self, proposal_id, account, support, total_weight, params)`](#GovernorComponent-_count_vote) +- [`_cast_vote(self, proposal_id, voter, support, reason, params)`](#GovernorComponent-_cast_vote) + +Events + +- [`ProposalCreated(proposal_id, proposer, calls, signatures, vote_start, vote_end, description)`](#GovernorComponent-ProposalCreated) +- [`ProposalQueued(proposal_id)`](#GovernorComponent-ProposalQueued) +- [`ProposalExecuted(proposal_id)`](#GovernorComponent-ProposalExecuted) +- [`ProposalCanceled(proposal_id)`](#GovernorComponent-ProposalCanceled) +- [`VoteCast(voter, proposal_id, support, weight, reason)`](#GovernorComponent-VoteCast) +- [`VoteCastWithParams(voter, proposal_id, support, weight, reason, params)`](#GovernorComponent-VoteCastWithParams) + +#### Extensions traits functions [!toc] [#GovernorComponent-Extensions-Traits] + + +Must return the delay, in number of timepoints, between when the proposal is created and when the vote starts. This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + + + +Must return the delay, in number of timepoints, between the vote start and vote end. + + + +Must return the minimum number of votes that an account must have to create a proposal. + + + +Must return the minimum number of votes required for a proposal to succeed. + + + +Must return a description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs to show correct vote options and interpret the results. See [COUNTING\_MODE](#GovernorComponent-COUNTING_MODE) for more details. + + + +Must register a vote for `proposal_id` by `account` with a given `support`, voting `weight` and voting `params`. + +Support is generic and can represent various things depending on the voting system used. + + + +Must return whether an account has cast a vote on a proposal. + + + +Must return whether the minimum quorum has been reached for a proposal. + + + +Must return whether a proposal has succeeded or not. + + + +Returns the current timepoint determined by the governor's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the governor is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + + +Must return the voting power of an account at a specific timepoint with the given parameters. + + + +Must return the state of a proposal at the current time. + +The state can be either: + +- `Pending`: The proposal does not exist yet. +- `Active`: The proposal is active. +- `Canceled`: The proposal has been canceled. +- `Defeated`: The proposal has been defeated. +- `Succeeded`: The proposal has succeeded. +- `Queued`: The proposal has been queued. +- `Executed`: The proposal has been executed. + + + +Must return the address through which the governor executes action. Should be used to specify whether the module execute actions through another contract such as a timelock. + +MUST be the governor itself, or an instance of TimelockController with the governor as the only proposer, canceller, and executor. + +When the executor is not the governor itself (i.e. a timelock), it can call functions that are restricted with the `assert_only_governance` guard, and also potentially execute transactions on behalf of the governor. Because of this, this module is designed to work with the TimelockController as the unique potential external executor. + + + + +Execution mechanism. Can be used to modify the way operations are executed (for example adding a vault/timelock). + + + +Queuing mechanism. Can be used to modify the way queuing is performed (for example adding a vault/timelock). + +Requirements: + +- Must return a timestamp that describes the expected ETA for execution. If the returned value is 0, the core will consider queueing did not succeed, and the public `queue` function will revert. + + + +Must return whether proposals need to be queued before execution. This usually indicates if the proposal needs to go through a timelock. + + + +Cancel mechanism. Can be used to modify the way canceling is performed (for example adding a vault/timelock). + + +#### Embeddable functions [!toc] [#GovernorComponent-Embeddable-Functions] + + +Name of the governor instance (used in building the [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +Version of the governor instance (used in building [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) domain separator). + + + +A description of the possible `support` values for `cast_vote` and the way these votes are counted, meant to be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +The string can be decoded by the standard [`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) JavaScript class. + + + + +Hashing function used to (re)build the proposal id from the proposal details. + + + +Returns the state of a proposal, given its id. + + + +The number of votes required in order for a voter to become a proposer. + + + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the following block. + + + +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is possible to cast a vote during this block. + + + +The account that created a proposal. + + + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the executor's clock which may be different. In most cases this will be a timestamp. + + + +Whether a proposal needs to be queued before execution. This indicates if the proposal needs to go through a timelock. + + + +Delay between when a proposal is created and when the vote starts. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a proposal starts. + + + +Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock (see [ERC-6372](https://eips.ethereum.org/EIPS/eip-6372)) this contract uses. + +The `voting_delay` can delay the start of the vote. This must be considered when setting the voting duration compared to the voting delay. + +This value is stored when the proposal is submitted so that possible changes to the value do not affect proposals that have already been submitted. + + + + +Minimum number of votes required for a proposal to be successful. + +The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows the quorum to scale depending on values such as the total supply of a token at this timepoint. + + + +Returns the voting power of an `account` at a specific `timepoint`. + +This can be implemented in a number of ways, for example by reading the delegated balance from one (or multiple) `ERC20Votes` tokens. + + + +Returns the voting power of an account at a specific timepoint, given additional encoded parameters. + + + +Returns whether an account has cast a vote on a proposal. + + + +Creates a new proposal. Voting starts after the delay specified by `voting_delay` and lasts for a duration specified by `voting_period`. Returns the id of the proposal. + +This function has opt-in frontrunning protection, described in `is_valid_description_for_proposer`. + +The state of the Governor and targets may change between the proposal creation and its execution. This may be the result of third party actions on the targeted contracts, or other governor proposals. For example, the balance of this contract could be updated or its access control permissions may be modified, possibly compromising the proposal's ability to execute successfully (e.g. the governor doesn't have enough value to cover a proposal with multiple transfers). + +Requirements: + +- The proposer must be authorized to submit the proposal. +- The proposer must have enough votes to submit the proposal if `proposal_threshold` is greater than zero. +- The proposal must not already exist. + +Emits a [ProposalCreated](#GovernorComponent-ProposalCreated) event. + + + +Queues a proposal. Some governors require this step to be performed before execution can happen. If queuing is not necessary, this function may revert. Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Succeeded` state. +- The queue operation must return a non-zero ETA. + +Emits a [ProposalQueued](#GovernorComponent-ProposalQueued) event. + + + + +Executes a successful proposal. This requires the quorum to be reached, the vote to be successful, and the deadline to be reached. Depending on the governor it might also be required that the proposal was queued and that some delay passed. + +Some modules can modify the requirements for execution, for example by adding an additional timelock (See `timelock_controller`). + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Succeeded` or `Queued` state. + +Emits a [ProposalExecuted](#GovernorComponent-ProposalExecuted) event. + + + +Cancels a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. before the vote starts. + +Returns the id of the proposal. + +Requirements: + +- The proposal must be in the `Pending` state. +- The caller must be the proposer of the proposal. + +Emits a [ProposalCanceled](#GovernorComponent-ProposalCanceled) event. + + + +Cast a vote. + +Requirements: + +- The proposal must be active. + +Emits a [VoteCast](#GovernorComponent-VoteCast) event. + + + +Cast a vote with a `reason`. + +Requirements: + +- The proposal must be active. + +Emits a [VoteCast](#GovernorComponent-VoteCast) event. + + + +Cast a vote with a `reason` and additional serialized `params`. + +Requirements: + +- The proposal must be active. + +Emits either: + +- [VoteCast](#GovernorComponent-VoteCast) event if no params are provided. +- [VoteCastWithParams](#GovernorComponent-VoteCastWithParams) event otherwise. + + + +Cast a vote using the `voter`'s signature. + +Requirements: + +- The proposal must be active. +- The nonce in the signed message must match the account's current nonce. +- `voter` must implement `SRC6::is_valid_signature`. +- `signature` must be valid for the message hash. + +Emits a [VoteCast](#GovernorComponent-VoteCast) event. + + + +Cast a vote with a `reason` and additional serialized `params` using the `voter`'s signature. + +Requirements: + +- The proposal must be active. +- The nonce in the signed message must match the account's current nonce. +- `voter` must implement `SRC6::is_valid_signature`. +- `signature` must be valid for the message hash. + +Emits either: + +- [VoteCast](#GovernorComponent-VoteCast) event if no params are provided. +- [VoteCastWithParams](#GovernorComponent-VoteCastWithParams) event otherwise. + + + +Returns the next unused nonce for an address. + + + +Relays a transaction or function call to an arbitrary target. + +In cases where the governance executor is some contract other than the governor itself, like when using a timelock, this function can be invoked in a governance proposal to recover tokens that were sent to the governor contract by mistake. + +If the executor is simply the governor itself, use of `relay` is redundant. + + +#### Internal functions [!toc] [#GovernorComponent-Internal-Functions] + + +Initializes the contract by registering the supported interface id. + + + +Returns the proposal object given its id. + + + +Checks if the proposer is authorized to submit a proposal with the given description. + +If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string (case insensitive), then the submission of this proposal will only be authorized to said address. + +This is used for frontrunning protection. By adding this pattern at the end of their proposal, one can ensure that no other address can submit the same proposal. An attacker would have to either remove or change that part, which would result in a different proposal id. + +In Starknet, the Sequencer ensures the order of transactions, but frontrunning can still be achieved by nodes, and potentially other actors in the future with sequencer decentralization. + +If the description does not match this pattern, it is unrestricted and anyone can submit it. This includes: + +- If the `0x???` part is not a valid hex string. +- If the `0x???` part is a valid hex string, but does not contain exactly 64 hex digits. +- If it ends with the expected suffix followed by newlines or other whitespace. +- If it ends with some other similar suffix, e.g. `#other=abc`. +- If it does not end with any such suffix. + + + +Returns the proposal id computed from the given parameters. + +The proposal id is computed as a Pedersen hash of: + +- The array of calls being proposed +- The description hash + + + +Timepoint used to retrieve user's votes and quorum. If using block number, the snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the following block. + + + +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is possible to cast a vote during this block. + + + +The account that created a proposal. + + + +The time when a queued proposal becomes executable ("ETA"). Unlike `proposal_snapshot` and `proposal_deadline`, this doesn't use the governor clock, and instead relies on the executor's clock which may be different. In most cases this will be a timestamp. + + + +Asserts that the caller is the governance executor. + +When the executor is not the governor itself (i.e. a timelock), it can call functions that are restricted with this modifier, and also potentially execute transactions on behalf of the governor. Because of this, this module is designed to work with the TimelockController as the unique potential external executor. The timelock MUST have the governor as the only proposer, canceller, and executor. + + + +Validates that a proposal is in the expected state. Otherwise it panics. + + + +Consumes a nonce, returns the current value, and increments nonce. + + + +Internal wrapper for `GovernorVotesTrait::get_votes`. + + + +Internal wrapper for `GovernorProposeTrait::proposal_threshold`. + + + +Returns the state of a proposal, given its id. + +Requirements: + +- The proposal must exist. + + + +Internal propose mechanism. Returns the proposal id. + +Requirements: + +- The proposal must not already exist. + +Emits a [ProposalCreated](#GovernorComponent-ProposalCreated) event. + + + +Internal cancel mechanism with minimal restrictions. + +A proposal can be cancelled in any state other than Canceled or Executed. + +Once cancelled, a proposal can't be re-submitted. + + + +Internal wrapper for `GovernorCountingTrait::count_vote`. + + + +Internal vote-casting mechanism. + +Checks that the vote is pending and that it has not been cast yet. This function retrieves the voting weight using `get_votes` and then calls the `_count_vote` internal function. + +Emits either: + +- [VoteCast](#GovernorComponent-VoteCast) event if no params are provided. +- [VoteCastWithParams](#GovernorComponent-VoteCastWithParams) event otherwise. + + +#### Events [!toc] [#GovernorComponent-Events] + + +Emitted when a proposal is created. + + + +Emitted when a proposal is queued. + + + +Emitted when a proposal is executed. + + + +Emitted when a proposal is canceled. + + + +Emitted when a vote is cast. + + + +Emitted when a vote is cast with params. + + +## [](#governor_extensions)Governor extensions + +The Governor component can (and must) be extended by implementing the [extensions traits](#GovernorComponent-Extensions-Traits-Traits) to add the desired functionality. This can be achieved by directly implementing the traits on your contract, or by using a set of ready-to-use extensions provided by the library, which are presented below. + +### `GovernorCoreExecutionComponent` [toc] [#GovernorCoreExecutionComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorCoreExecutionComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) providing an execution mechanism directly through the Governor itself. For a timelocked execution mechanism, see [GovernorTimelockExecutionComponent](#GovernorTimelockExecutionComponent). + +Extension traits implementations + +#### GovernorExecution [!toc] [#GovernorCoreExecutionComponent-GovernorExecution] + +- [`state(self, proposal_id)`](#GovernorCoreExecutionComponent-state) +- [`executor(self)`](#GovernorCoreExecutionComponent-executor) +- [`execute_operations(self, proposal_id, calls, description_hash)`](#GovernorCoreExecutionComponent-execute_operations) +- [`queue_operations(self, proposal_id, calls, description_hash)`](#GovernorCoreExecutionComponent-queue_operations) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorCoreExecutionComponent-proposal_needs_queuing) +- [`cancel_operations(self, proposal_id, description_hash)`](#GovernorCoreExecutionComponent-cancel_operations) + +#### Extension traits functions [!toc] [#GovernorCoreExecutionComponent-Extension-Traits-Functions] + + +Returns the state of a proposal given its id. + +Requirements: + +- The proposal must exist. + + + +Returns the executor address. + +In this case, it returns the governor contract address since execution is performed directly through it. + + + +Executes the proposal's operations directly through the governor contract. + + + +In this implementation, queuing is not required so it returns 0. + + + +In this implementation, it always returns false. + + + +Cancels a proposal's operations. + + +### `GovernorCountingSimpleComponent` [toc] [#GovernorCountingSimpleComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorCountingSimpleComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for simple vote counting with three options. + +Extension traits implementations + +#### GovernorCounting [!toc] [#GovernorCountingSimpleComponent-GovernorCounting] + +- [`counting_mode(self)`](#GovernorCountingSimpleComponent-counting_mode) +- [`count_vote(self, proposal_id, account, support, total_weight, params)`](#GovernorCountingSimpleComponent-count_vote) +- [`has_voted(self, proposal_id, account)`](#GovernorCountingSimpleComponent-has_voted) +- [`quorum_reached(self, proposal_id)`](#GovernorCountingSimpleComponent-quorum_reached) +- [`vote_succeeded(self, proposal_id)`](#GovernorCountingSimpleComponent-vote_succeeded) + +#### Extension traits functions [!toc] [#GovernorCountingSimpleComponent-Extension-Traits-Functions] + + +Returns `"support=bravo&quorum=for,abstain"`. + +- `support=bravo` indicates that the support follows the Governor Bravo format where voters can vote For, Against, or Abstain +- `quorum=for,abstain` indicates that both For and Abstain votes count toward quorum + + + +Records a vote for a proposal. + +The support value follows the `VoteType` enum (0=Against, 1=For, 2=Abstain). + +Returns the weight that was counted. + + + +Returns whether an account has cast a vote on a proposal. + + + + +Returns whether a proposal has reached quorum. + +In this implementation, both For and Abstain votes count toward quorum. + + + +Returns whether a proposal has succeeded. + +In this implementation, the For votes must be strictly greater than Against votes. + + +### `GovernorSettingsComponent` [toc] [#GovernorSettingsComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorSettingsComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for settings that are updatable through governance. + +Extension traits implementations + +#### GovernorSettings [!toc] [#GovernorSettingsComponent-GovernorSettings] + +- [`voting_delay(self)`](#GovernorSettingsComponent-voting_delay) +- [`voting_period(self)`](#GovernorSettingsComponent-voting_period) +- [`proposal_threshold(self)`](#GovernorSettingsComponent-proposal_threshold) + +Embeddable implementations + +#### GovernorSettingsAdminImpl [!toc] [#GovernorSettingsComponent-GovernorSettingsAdminImpl] + +- [`set_voting_delay(self, new_voting_delay)`](#GovernorSettingsComponent-set_voting_delay) +- [`set_voting_period(self, new_voting_period)`](#GovernorSettingsComponent-set_voting_period) +- [`set_proposal_threshold(self, new_proposal_threshold)`](#GovernorSettingsComponent-set_proposal_threshold) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorSettingsComponent-InternalImpl] + +- [`initializer(self, new_voting_delay, new_voting_period, new_proposal_threshold)`](#GovernorSettingsComponent-initializer) +- [`assert_only_governance(self)`](#GovernorSettingsComponent-assert_only_governance) +- [`_set_voting_delay(self, new_voting_delay)`](#GovernorSettingsComponent-_set_voting_delay) +- [`_set_voting_period(self, new_voting_period)`](#GovernorSettingsComponent-_set_voting_period) +- [`_set_proposal_threshold(self, new_proposal_threshold)`](#GovernorSettingsComponent-_set_proposal_threshold) + +Events + +- [`VotingDelayUpdated(old_voting_delay, new_voting_delay)`](#GovernorSettingsComponent-VotingDelayUpdated) +- [`VotingPeriodUpdated(old_voting_period, new_voting_period)`](#GovernorSettingsComponent-VotingPeriodUpdated) +- [`ProposalThresholdUpdated(old_proposal_threshold, new_proposal_threshold)`](#GovernorSettingsComponent-ProposalThresholdUpdated) + +#### Extension traits functions [!toc] [#GovernorSettingsComponent-ExtensionTraitsFunctions] + + +Returns the delay, between when a proposal is created and when voting starts. + + + +Returns the time period, during which votes can be cast. + + + +Returns the minimum number of votes required for an account to create a proposal. + + +#### Embeddable functions [!toc] [#GovernorSettingsComponent-EmbeddableFunctions] + + +Sets the voting delay. + +Requirements: + +- Caller must be the governance executor. + +This function does not emit an event if the new voting delay is the same as the old one. + +May emit a [VotingDelayUpdated](#GovernorSettingsComponent-VotingDelayUpdated) event. + + + +Sets the voting period. + +This function does not emit an event if the new voting period is the same as the old one. + +Requirements: + +- Caller must be the governance executor. +- `new_voting_period` must be greater than 0. + +May emit a [VotingPeriodUpdated](#GovernorSettingsComponent-VotingPeriodUpdated) event. + + + +Sets the proposal threshold. + +This function does not emit an event if the new proposal threshold is the same as the old one. + +Requirements: + +- Caller must be the governance executor. + +May emit a [ProposalThresholdUpdated](#GovernorSettingsComponent-ProposalThresholdUpdated) event. + + +#### Internal functions [!toc] [#GovernorSettingsComponent-InternalFunctions] + + +Initializes the component by setting the default values. + +Requirements: + +- `new_voting_period` must be greater than 0. + +Emits a [VotingDelayUpdated](#GovernorSettingsComponent-VotingDelayUpdated), [VotingPeriodUpdated](#GovernorSettingsComponent-VotingPeriodUpdated), and [ProposalThresholdUpdated](#GovernorSettingsComponent-ProposalThresholdUpdated) event. + + + +Asserts that the caller is the governance executor. + + + +Internal function to update the voting delay. + +This function does not emit an event if the new voting delay is the same as the old one. + +May emit a [VotingDelayUpdated](#GovernorSettingsComponent-VotingDelayUpdated) event. + + + +Internal function to update the voting period. + +Requirements: + +- `new_voting_period` must be greater than 0. + +This function does not emit an event if the new voting period is the same as the old one. + +May emit a [VotingPeriodUpdated](#GovernorSettingsComponent-VotingPeriodUpdated) event. + + + +Internal function to update the proposal threshold. + +This function does not emit an event if the new proposal threshold is the same as the old one. + +May emit a [ProposalThresholdUpdated](#GovernorSettingsComponent-ProposalThresholdUpdated) event. + + +#### Events [!toc] [#GovernorSettingsComponent-Events] + + +Emitted when the voting delay is updated. + + + +Emitted when the voting period is updated. + + + +Emitted when the proposal threshold is updated. + + +### `GovernorVotesComponent` [toc] [#GovernorVotesComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorVotesComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for voting weight extraction from a token with the [IVotes](#IVotes) extension. + +Extension traits implementations + +#### GovernorVotes [!toc] [#GovernorVotesComponent-GovernorVotes] + +- [`clock(self)`](#GovernorVotesComponent-clock) +- [`CLOCK_MODE(self)`](#GovernorVotesComponent-CLOCK_MODE) +- [`get_votes(self, account, timepoint, params)`](#GovernorVotesComponent-get_votes) + +Embeddable implementations + +#### VotesTokenImpl [!toc] [#GovernorVotesComponent-VotesTokenImpl] + +- [`token(self)`](#GovernorVotesComponent-token) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorVotesComponent-InternalImpl] + +- [`initializer(self, votes_token)`](#GovernorVotesComponent-initializer) + +#### Extension traits functions [!toc] [#GovernorVotesComponent-ExtensionTraitsFunctions] + + +Returns the current timepoint determined by the governor's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the governor is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + + +Returns the voting power of `account` at a specific `timepoint` using the votes token. + + +#### Embeddable functions [!toc] [#GovernorVotesComponent-EmbeddableFunctions] + + +Returns the votes token that voting power is sourced from. + + +#### Internal functions [!toc] [#GovernorVotesComponent-InternalFunctions] + + +Initializes the component by setting the votes token. + +Requirements: + +- `votes_token` must not be zero. + + +### `GovernorVotesQuorumFractionComponent` [toc] [#GovernorVotesQuorumFractionComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) for voting weight extraction from a token with the [IVotes](#IVotes) extension and a quorum expressed as a fraction of the total supply. + +Extension traits implementations + +#### GovernorQuorum [!toc] [#GovernorVotesQuorumFractionComponent-GovernorQuorum] + +- [`quorum(self, timepoint)`](#GovernorVotesQuorumFractionComponent-quorum) + +#### GovernorVotes [!toc] [#GovernorVotesQuorumFractionComponent-GovernorVotes] + +- [`clock(self)`](#GovernorVotesQuorumFractionComponent-clock) +- [`CLOCK_MODE(self)`](#GovernorVotesQuorumFractionComponent-CLOCK_MODE) +- [`get_votes(self, account, timepoint, params)`](#GovernorVotesQuorumFractionComponent-get_votes) + +Embeddable implementations + +#### QuorumFractionImpl [!toc] [#GovernorVotesQuorumFractionComponent-QuorumFractionImpl] + +- [`token(self)`](#GovernorVotesQuorumFractionComponent-token) +- [`current_quorum_numerator(self)`](#GovernorVotesQuorumFractionComponent-current_quorum_numerator) +- [`quorum_numerator(self, timepoint)`](#GovernorVotesQuorumFractionComponent-quorum_numerator) +- [`quorum_denominator(self)`](#GovernorVotesQuorumFractionComponent-quorum_denominator) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorVotesQuorumFractionComponent-InternalImpl] + +- [`initializer(self, votes_token, quorum_numerator)`](#GovernorVotesQuorumFractionComponent-initializer) +- [`update_quorum_numerator(self, new_quorum_numerator)`](#GovernorVotesQuorumFractionComponent-update_quorum_numerator) + +Events + +- [`QuorumNumeratorUpdated(old_quorum_numerator, new_quorum_numerator)`](#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated) + +#### Extension traits functions [!toc] [#GovernorVotesQuorumFractionComponent-ExtensionTraitsFunctions] + + +It is computed as a percentage of the votes token total supply at a given `timepoint` in the past. + + + +Returns the current timepoint determined by the governor's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the governor is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + + +Returns the voting power of `account` at a specific `timepoint` using the votes token. + + +#### Embeddable functions [!toc] [#GovernorVotesQuorumFractionComponent-EmbeddableFunctions] + + +Returns the address of the votes token used for voting power extraction. + + + +Returns the current quorum numerator value. + + + +Returns the quorum numerator value at a specific `timepoint` in the past. + + + +Returns the quorum denominator value. + + +#### Internal functions [!toc] [#GovernorVotesQuorumFractionComponent-InternalFunctions] + + +Initializes the component by setting the votes token and the initial quorum numerator value. + +Requirements: + +- `votes_token` must not be zero. +- `quorum_numerator` must be less than `quorum_denominator`. + +Emits a [QuorumNumeratorUpdated](#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated) event. + + + +Updates the quorum numerator. + +This function does not emit an event if the new quorum numerator is the same as the old one. + +Requirements: + +- `new_quorum_numerator` must be less than `quorum_denominator`. + +May emit a [QuorumNumeratorUpdated](#GovernorVotesQuorumFractionComponent-QuorumNumeratorUpdated) event. + + +#### Events [!toc] [#GovernorVotesQuorumFractionComponent-Events] + + +Emitted when the quorum numerator is updated. + + +### `GovernorTimelockExecutionComponent` [toc] [#GovernorTimelockExecutionComponent] + + + +```rust +use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent; +``` + +Extension of [GovernorComponent](#GovernorComponent) that binds the execution process to an instance of a contract implementing [TimelockControllerComponent](#TimelockControllerComponent). This adds a delay, enforced by the timelock to all successful proposals (in addition to the voting duration). + +The Governor needs the [PROPOSER, EXECUTOR, and CANCELLER roles](../governance/timelock#roles) to work properly. + +Using this model means the proposal will be operated by the timelock and not by the governor. Thus, the assets and permissions must be attached to the timelock. Any asset sent to the governor will be inaccessible from a proposal, unless executed via `Governor::relay`. + +Setting up the timelock to have additional proposers or cancellers besides the governor is very risky, as it grants them the ability to: 1) execute operations as the timelock, and thus possibly performing operations or accessing funds that are expected to only be accessible through a vote, and 2) block governance proposals that have been approved by the voters, effectively executing a Denial of Service attack. + +Extension traits implementations + +#### GovernorExecution [!toc] [#GovernorTimelockExecutionComponent-GovernorExecution] + +- [`state(self, proposal_id)`](#GovernorTimelockExecutionComponent-state) +- [`executor(self)`](#GovernorTimelockExecutionComponent-executor) +- [`execute_operations(self, proposal_id, calls, description_hash)`](#GovernorTimelockExecutionComponent-execute_operations) +- [`queue_operations(self, proposal_id, calls, description_hash)`](#GovernorTimelockExecutionComponent-queue_operations) +- [`proposal_needs_queuing(self, proposal_id)`](#GovernorTimelockExecutionComponent-proposal_needs_queuing) +- [`cancel_operations(self, proposal_id, description_hash)`](#GovernorTimelockExecutionComponent-cancel_operations) + +Embeddable implementations + +#### TimelockedImpl [!toc] [#GovernorTimelockExecutionComponent-TimelockedImpl] + +- [`timelock(self)`](#GovernorTimelockExecutionComponent-timelock) +- [`get_timelock_id(self, proposal_id)`](#GovernorTimelockExecutionComponent-get_timelock_id) +- [`update_timelock(self, new_timelock)`](#GovernorTimelockExecutionComponent-update_timelock) + +Internal implementations + +#### InternalImpl [!toc] [#GovernorTimelockExecutionComponent-InternalImpl] + +- [`initializer(self, timelock_controller)`](#GovernorTimelockExecutionComponent-initializer) +- [`assert_only_governance(self)`](#GovernorTimelockExecutionComponent-assert_only_governance) +- [`timelock_salt(self, description_hash)`](#GovernorTimelockExecutionComponent-timelock_salt) +- [`get_timelock_dispatcher(self)`](#GovernorTimelockExecutionComponent-get_timelock_dispatcher) +- [`_update_timelock(self, new_timelock)`](#GovernorTimelockExecutionComponent-_update_timelock) + +Events + +- [`TimelockUpdated(old_timelock, new_timelock)`](#GovernorTimelockExecutionComponent-TimelockUpdated) + +#### Extension traits functions [!toc] [#GovernorTimelockExecutionComponent-ExtensionTraitsFunctions] + + +Returns the state of a proposal given its id. + +Requirements: + +- The proposal must exist. + + + +Returns the executor address. + +In this module, the executor is the timelock controller. + + + +Runs the already queued proposal through the timelock. + + + +Queue a proposal to the timelock. + +Returns the eta for the execution of the queued proposal. + + + +In this implementation, it always returns true. + + + +Cancels the timelocked proposal if it has already been queued. + + +#### Embeddable functions [!toc] [#GovernorTimelockExecutionComponent-EmbeddableFunctions] + + +Returns the timelock controller address. + + + +Returns the timelock proposal id for a given proposal id. + + + +Updates the associated timelock. + +Requirements: + +- The caller must be the governance. + +Emits a [TimelockUpdated](#GovernorTimelockExecutionComponent-TimelockUpdated) event. + + +#### Internal functions [!toc] [#GovernorTimelockExecutionComponent-InternalFunctions] + + +Initializes the timelock controller. + +Requirements: + +- The timelock must not be the zero address. + + + +Ensures the caller is the executor (the timelock controller in this case). + + + +Computes the `TimelockController` operation salt as the XOR of the governor address and `description_hash`. + +It is computed with the governor address itself to avoid collisions across governor instances using the same timelock. + + + +Returns a dispatcher for interacting with the timelock controller. + + + +Internal function to update the timelock controller address. + +Emits a [TimelockUpdated](#GovernorTimelockExecutionComponent-TimelockUpdated) event. + + +#### Events [!toc] [#GovernorTimelockExecutionComponent-Events] + + +Emitted when the timelock controller is updated. + + +## [](#multisig)Multisig + +A Multisig module enhances security and decentralization by requiring multiple signers to approve and execute transactions. Features include configurable quorum, signer management, and self-administration, ensuring collective decision-making and transparency for critical operations. + +### `MultisigComponent` [toc] [#MultisigComponent] + + + +```rust +use openzeppelin_governance::multisig::MultisigComponent; +``` + +Component that implements [IMultisig](#IMultisig) and provides functionality for multisignature wallets, including transaction management, quorum handling, and signer operations. + +Embeddable Implementations + +#### MultisigImpl [!toc] [#MultisigComponent-MultisigImpl] + +- [`get_quorum(self)`](#MultisigComponent-get_quorum) +- [`is_signer(self, signer)`](#MultisigComponent-is_signer) +- [`get_signers(self)`](#MultisigComponent-get_signers) +- [`is_confirmed(self, id)`](#MultisigComponent-is_confirmed) +- [`is_confirmed_by(self, id, signer)`](#MultisigComponent-is_confirmed_by) +- [`is_executed(self, id)`](#MultisigComponent-is_executed) +- [`get_submitted_block(self, id)`](#MultisigComponent-get_submitted_block) +- [`get_transaction_state(self, id)`](#MultisigComponent-get_transaction_state) +- [`get_transaction_confirmations(self, id)`](#MultisigComponent-get_transaction_confirmations) +- [`hash_transaction(self, to, selector, calldata, salt)`](#MultisigComponent-hash_transaction) +- [`hash_transaction_batch(self, calls, salt)`](#MultisigComponent-hash_transaction_batch) +- [`add_signers(ref self, new_quorum, signers_to_add)`](#MultisigComponent-add_signers) +- [`remove_signers(ref self, new_quorum, signers_to_remove)`](#MultisigComponent-remove_signers) +- [`replace_signer(ref self, signer_to_remove, signer_to_add)`](#MultisigComponent-replace_signer) +- [`change_quorum(ref self, new_quorum)`](#MultisigComponent-change_quorum) +- [`submit_transaction(ref self, to, selector, calldata, salt)`](#MultisigComponent-submit_transaction) +- [`submit_transaction_batch(ref self, calls, salt)`](#MultisigComponent-submit_transaction_batch) +- [`confirm_transaction(ref self, id)`](#MultisigComponent-confirm_transaction) +- [`revoke_confirmation(ref self, id)`](#MultisigComponent-revoke_confirmation) +- [`execute_transaction(ref self, to, selector, calldata, salt)`](#MultisigComponent-execute_transaction) +- [`execute_transaction_batch(ref self, calls, salt)`](#MultisigComponent-execute_transaction_batch) + +Internal Implementations + +#### InternalImpl [!toc] [#MultisigComponent-InternalImpl] + +- [`initializer(ref self, quorum, signers)`](#MultisigComponent-initializer) +- [`resolve_tx_state(self, id)`](#MultisigComponent-resolve_tx_state) +- [`assert_one_of_signers(self, caller)`](#MultisigComponent-assert_one_of_signers) +- [`assert_tx_exists(self, id)`](#MultisigComponent-assert_tx_exists) +- [`assert_only_self(self)`](#MultisigComponent-assert_only_self) +- [`_add_signers(ref self, new_quorum, signers_to_add)`](#MultisigComponent-_add_signers) +- [`_remove_signers(ref self, new_quorum, signers_to_remove)`](#MultisigComponent-_remove_signers) +- [`_replace_signer(ref self, signer_to_remove, signer_to_add)`](#MultisigComponent-_replace_signer) +- [`_change_quorum(ref self, new_quorum)`](#MultisigComponent-_change_quorum) + +Events + +- [`SignerAdded(signer)`](#MultisigComponent-SignerAdded) +- [`SignerRemoved(signer)`](#MultisigComponent-SignerRemoved) +- [`QuorumUpdated(old_quorum, new_quorum)`](#MultisigComponent-QuorumUpdated) +- [`TransactionSubmitted(id, signer)`](#MultisigComponent-TransactionSubmitted) +- [`TransactionConfirmed(id, signer)`](#MultisigComponent-TransactionConfirmed) +- [`ConfirmationRevoked(id, signer)`](#MultisigComponent-ConfirmationRevoked) +- [`TransactionExecuted(id)`](#MultisigComponent-TransactionExecuted) +- [`CallSalt(id, salt)`](#MultisigComponent-CallSalt) + +#### Embeddable functions [!toc] [#MultisigComponent-EmbeddableFunctions] + + +Returns the current quorum value. + + + +Checks if a given `signer` is registered. + + + +Returns a list of all current signers. + + + +Returns whether the transaction with the given `id` has been confirmed. A confirmed transaction has received the required number of confirmations (quorum). + + + +Returns whether the transaction with the given `id` has been confirmed by the specified `signer`. + + + +Returns whether the transaction with the given `id` has been executed. + + + +Returns the block number when the transaction with the given `id` was submitted. + + + +Returns the current state of the transaction with the given `id`. + +The possible states are: + +- `NotFound`: the transaction does not exist. +- `Pending`: the transaction exists but hasn't reached the required confirmations. +- `Confirmed`: the transaction has reached the required confirmations but hasn't been executed. +- `Executed`: the transaction has been executed. + + + +Returns the number of confirmations from registered signers for the transaction with the specified `id`. + + + +Returns the computed identifier of a transaction containing a single call. + + + +Returns the computed identifier of a transaction containing a batch of calls. + + + +Adds new signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after addition. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for each signer added. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Removes signers and updates the quorum. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be less than or equal to the total number of signers after removal. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for each signer removed. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Replaces an existing signer with a new signer. + +Requirements: + +- The caller must be the contract itself. +- `signer_to_remove` must be an existing signer. +- `signer_to_add` must not be an existing signer. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for the removed signer. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for the new signer. + + + +Updates the quorum value to `new_quorum`. + +Requirements: + +- The caller must be the contract itself. +- `new_quorum` must be non-zero. +- `new_quorum` must be less than or equal to the total number of signers. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Submits a new transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#MultisigComponent-TransactionSubmitted) event. + +Emits a [CallSalt](#MultisigComponent-CallSalt) event if `salt` is not zero. + + + +Submits a new batch transaction for confirmation. + +Requirements: + +- The caller must be a registered signer. +- The transaction must not have been submitted before. + +Emits a [TransactionSubmitted](#MultisigComponent-TransactionSubmitted) event. + +Emits a [CallSalt](#MultisigComponent-CallSalt) event if `salt` is not zero. + + + +Confirms a transaction with the given `id`. + +Requirements: + +- The caller must be a registered signer. +- The transaction must exist and not be executed. +- The caller must not have already confirmed the transaction. + +Emits a [TransactionConfirmed](#MultisigComponent-TransactionConfirmed) event. + + + +Revokes a previous confirmation for a transaction with the given `id`. + +Requirements: + +- The transaction must exist and not be executed. +- The caller must have previously confirmed the transaction. + +Emits a [ConfirmationRevoked](#MultisigComponent-ConfirmationRevoked) event. + + + +Executes a confirmed transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#MultisigComponent-TransactionExecuted) event. + + + +Executes a confirmed batch transaction. + +Requirements: + +- The caller must be a registered signer. +- The transaction must be confirmed and not yet executed. + +Emits a [TransactionExecuted](#MultisigComponent-TransactionExecuted) event. + + +#### Internal functions [!toc] [#MultisigComponent-InternalFunctions] + + +Initializes the Multisig component with the initial `quorum` and `signers`. This function must be called during contract initialization to set up the initial state. + +Requirements: + +- `quorum` must be non-zero and less than or equal to the number of `signers`. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for each signer added. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event. + + + +Resolves and returns the current state of the transaction with the given `id`. + +The possible states are: + +- `NotFound`: the transaction does not exist. +- `Pending`: the transaction exists but hasn't reached the required confirmations. +- `Confirmed`: the transaction has reached the required confirmations but hasn't been executed. +- `Executed`: the transaction has been executed. + + + +Asserts that the `caller` is one of the registered signers. + +Requirements: + +- The `caller` must be a registered signer. + + + +Asserts that a transaction with the given `id` exists. + +Requirements: + +- The transaction with the given `id` must have been submitted. + + + +Asserts that the caller is the contract itself. + +Requirements: + +- The caller must be the contract's own address. + + + +Adds new signers and updates the quorum. + +Requirements: + +- Each signer address must be non-zero. +- `new_quorum` must be non-zero and less than or equal to the total number of signers after addition. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for each new signer added. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Removes existing signers and updates the quorum. + +Requirements: + +- `new_quorum` must be non-zero and less than or equal to the total number of signers after removal. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for each signer removed. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + + +Replaces an existing signer with a new signer. + +Requirements: + +- `signer_to_remove` must be an existing signer. +- `signer_to_add` must not be an existing signer. +- `signer_to_add` must be a non-zero address. + +Emits a [SignerRemoved](#MultisigComponent-SignerRemoved) event for the removed signer. + +Emits a [SignerAdded](#MultisigComponent-SignerAdded) event for the new signer. + + + +Updates the quorum value to `new_quorum` if it differs from the current quorum. + +Requirements: + +- `new_quorum` must be non-zero. +- `new_quorum` must be less than or equal to the total number of signers. + +Emits a [QuorumUpdated](#MultisigComponent-QuorumUpdated) event if the quorum changes. + + +#### Events [!toc] [#MultisigComponent-Events] + + +Emitted when a new `signer` is added. + + + +Emitted when a `signer` is removed. + + + +Emitted when the `quorum` value is updated. + + + +Emitted when a new transaction is submitted by a `signer`. + + + +Emitted when a transaction is confirmed by a `signer`. + + + +Emitted when a `signer` revokes his confirmation. + + + +Emitted when a transaction is executed. + + + +Emitted when a new transaction is submitted with non-zero salt. + + +## [](#timelock)Timelock + +In a governance system, `TimelockControllerComponent` is in charge of introducing a delay between a proposal and its execution. + +### `TimelockControllerComponent` [toc] [#TimelockControllerComponent] + + + +```rust +use openzeppelin_governance::timelock::TimelockControllerComponent; +``` + +Component that implements [ITimelock](#ITimelock) and enables the implementing contract to act as a timelock controller. + +[Embeddable Mixin Implementations](../components#mixins) + +#### TimelockMixinImpl [!toc] [#TimelockControllerComponent-TimelockMixinImpl] + +- [`TimelockImpl`](#TimelockControllerComponent-Embeddable-Impls-TimelockImpl) +- [`SRC5Impl`](./introspection#SRC5Component-Embeddable-Impls) +- [`AccessControlImpl`](./access#AccessControlComponent-Embeddable-Impls) +- [`AccessControlCamelImpl`](./access#AccessControlComponent-Embeddable-Impls) + +Embeddable Implementations + +#### TimelockImpl [!toc] [#TimelockControllerComponent-TimelockImpl] + +- [`is_operation(self, id)`](#TimelockControllerComponent-is_operation) +- [`is_operation_pending(self, id)`](#TimelockControllerComponent-is_operation_pending) +- [`is_operation_ready(self, id)`](#TimelockControllerComponent-is_operation_ready) +- [`is_operation_done(self, id)`](#TimelockControllerComponent-is_operation_done) +- [`get_timestamp(self, id)`](#TimelockControllerComponent-get_timestamp) +- [`get_operation_state(self, id)`](#TimelockControllerComponent-get_operation_state) +- [`get_min_delay(self)`](#TimelockControllerComponent-get_min_delay) +- [`hash_operation(self, call, predecessor, salt)`](#TimelockControllerComponent-hash_operation) +- [`hash_operation_batch(self, calls, predecessor, salt)`](#TimelockControllerComponent-hash_operation_batch) +- [`schedule(self, call, predecessor, salt, delay)`](#TimelockControllerComponent-schedule) +- [`schedule_batch(self, calls, predecessor, salt, delay)`](#TimelockControllerComponent-schedule_batch) +- [`cancel(self, id)`](#TimelockControllerComponent-cancel) +- [`execute(self, call, predecessor, salt)`](#TimelockControllerComponent-execute) +- [`execute_batch(self, calls, predecessor, salt)`](#TimelockControllerComponent-execute_batch) +- [`update_delay(self, new_delay)`](#TimelockControllerComponent-update_delay) + +#### SRC5Impl [!toc] [#TimelockControllerComponent-SRC5Impl] + +- [`supports_interface(self, interface_id: felt252)`](./introspection#ISRC5-supports_interface) + +#### AccessControlImpl [!toc] [#TimelockControllerComponent-AccessControlImpl] + +- [`has_role(self, role, account)`](./access#IAccessControl-has_role) +- [`get_role_admin(self, role)`](./access#IAccessControl-get_role_admin) +- [`grant_role(self, role, account)`](./access#IAccessControl-grant_role) +- [`revoke_role(self, role, account)`](./access#IAccessControl-revoke_role) +- [`renounce_role(self, role, account)`](./access#IAccessControl-renounce_role) + +#### AccessControlCamelImpl [!toc] [#TimelockControllerComponent-AccessControlCamelImpl] + +- [`hasRole(self, role, account)`](./access#IAccessControl-hasRole) +- [`getRoleAdmin(self, role)`](./access#IAccessControl-getRoleAdmin) +- [`grantRole(self, role, account)`](./access#IAccessControl-grantRole) +- [`revokeRole(self, role, account)`](./access#IAccessControl-revokeRole) +- [`renounceRole(self, role, account)`](./access#IAccessControl-renounceRole) + +Internal Implementations + +#### InternalImpl [!toc] [#TimelockControllerComponent-InternalImpl] + +- [`initializer(self, min_delay, proposers, executors, admin)`](#TimelockControllerComponent-initializer) +- [`assert_only_role(self, role)`](#TimelockControllerComponent-assert_only_role) +- [`assert_only_role_or_open_role(self, role)`](#TimelockControllerComponent-assert_only_role_or_open_role) +- [`assert_only_self(self)`](#TimelockControllerComponent-assert_only_self) +- [`_before_call(self, id, predecessor)`](#TimelockControllerComponent-_before_call) +- [`_after_call(self, id)`](#TimelockControllerComponent-_after_call) +- [`_schedule(self, id, delay)`](#TimelockControllerComponent-_schedule) +- [`_execute(self, call)`](#TimelockControllerComponent-_execute) + +Events + +- [`CallScheduled(id, index, call, predecessor, delay)`](#TimelockControllerComponent-CallScheduled) +- [`CallExecuted(id, index, call)`](#TimelockControllerComponent-CallExecuted) +- [`CallSalt(id, salt)`](#TimelockControllerComponent-CallSalt) +- [`CallCancelled(id)`](#TimelockControllerComponent-CallCancelled) +- [`MinDelayChanged(old_duration, new_duration)`](#TimelockControllerComponent-MinDelayChanged) + +#### Embeddable functions [!toc] [#TimelockControllerComponent-EmbeddableFunctions] + + +Returns whether `id` corresponds to a registered operation. This includes the OperationStates: `Waiting`, `Ready`, and `Done`. + + + +Returns whether the `id` OperationState is pending or not. Note that a pending operation may be either `Waiting` or `Ready`. + + + +Returns whether the `id` OperationState is `Ready` or not. + + + +Returns whether the `id` OperationState is `Done` or not. + + + +Returns the timestamp at which `id` becomes `Ready`. + +`0` means the OperationState is `Unset` and `1` means the OperationState is `Done`. + + + +Returns the current state of the operation with the given `id`. + +The possible states are: + +- `Unset`: the operation has not been scheduled or has been canceled. +- `Waiting`: the operation has been scheduled and is pending the scheduled delay. +- `Ready`: the timer has expired, and the operation is eligible for execution. +- `Done`: the operation has been executed. + + + +Returns the minimum delay in seconds for an operation to become valid. This value can be changed by executing an operation that calls `update_delay`. + + + +Returns the identifier of an operation containing a single transaction. + + + +Returns the identifier of an operation containing a batch of transactions. + + + +Schedule an operation containing a single transaction. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. +- The proposal must not already exist. +- `delay` must be greater than or equal to the min delay. + +Emits [CallScheduled](#TimelockControllerComponent-CallScheduled) event. Emits [CallSalt](#TimelockControllerComponent-CallSalt) event if `salt` is not zero. + + + +Schedule an operation containing a batch of transactions. + +Requirements: + +- The caller must have the `PROPOSER_ROLE` role. +- The proposal must not already exist. +- `delay` must be greater than or equal to the min delay. + +Emits one [CallScheduled](#TimelockControllerComponent-CallScheduled) event for each transaction in the batch. Emits [CallSalt](#TimelockControllerComponent-CallSalt) event if `salt` is not zero. + + + +Cancels an operation. A canceled operation returns to `Unset` OperationState. + +Requirements: + +- The caller must have the `CANCELLER_ROLE` role. +- `id` must be a pending operation. + +Emits a [CallCancelled](#TimelockControllerComponent-CallCancelled) event. + + + +Execute a (Ready) operation containing a single Call. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#TimelockControllerComponent-CallExecuted) event. + +This function can reenter, but it doesn't pose a risk because [`_after_call(self: @ContractState, id: felt252)` internal](#TimelockControllerComponent-_after_call) checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Execute a (Ready) operation containing a batch of Calls. + +Requirements: + +- Caller must have `EXECUTOR_ROLE`. +- `id` must be in Ready OperationState. +- `predecessor` must either be `0` or in Done OperationState. + +Emits a [CallExecuted](#TimelockControllerComponent-CallExecuted) event for each Call. + +This function can reenter, but it doesn't pose a risk because `_after_call` checks that the proposal is pending, thus any modifications to the operation during reentrancy should be caught. + + + +Changes the minimum timelock duration for future operations. + +Requirements: + +- The caller must be the timelock itself. This can only be achieved by scheduling and later executing an operation where the timelock is the target and the data is the serialized call to this function. + +Emits a [MinDelayChanged](#TimelockControllerComponent-MinDelayChanged) event. + + +#### Internal functions [!toc] [#TimelockControllerComponent-InternalFunctions] + + +Initializes the contract by registering support for SRC5 and AccessControl. + +This function also configures the contract with the following parameters: + +- `min_delay`: initial minimum delay in seconds for operations. +- `proposers`: accounts to be granted proposer and canceller roles. +- `executors`: accounts to be granted executor role. +- `admin`: optional account to be granted admin role; disable with zero address. + +The optional admin can aid with initial configuration of roles after deployment without being subject to delay, but this role should be subsequently renounced in favor of administration through timelocked proposals. + +Emits two [IAccessControl::RoleGranted](./access#IAccessControl-RoleGranted) events for each account in `proposers` with `PROPOSER_ROLE` and `CANCELLER_ROLE` roles. + +Emits a [IAccessControl::RoleGranted](./access#IAccessControl-RoleGranted) event for each account in `executors` with `EXECUTOR_ROLE` role. + +May emit a [IAccessControl::RoleGranted](./access#IAccessControl-RoleGranted) event for `admin` with `DEFAULT_ADMIN_ROLE` role (if `admin` is not zero). + +Emits [MinDelayChanged](#TimelockControllerComponent-MinDelayChanged) event. + + + +Validates that the caller has the given `role`. Otherwise it panics. + + + +Validates that the caller has the given `role`. If `role` is granted to the zero address, then this is considered an open role which allows anyone to be the caller. + + + +Validates that the caller is the timelock contract itself. Otherwise it panics. + + + +Private function that checks before execution of an operation's calls. + +Requirements: + +- `id` must be in the `Ready` OperationState. +- `predecessor` must either be zero or be in the `Done` OperationState. + + + +Private function that checks after execution of an operation's calls and sets the OperationState of `id` to `Done`. + +Requirements: + +- `id` must be in the Ready OperationState. + + + +Private function that schedules an operation that is to become valid after a given `delay`. + + + +Private function that executes an operation's calls. + + +#### Events [!toc] [#TimelockControllerComponent-Events] + + +Emitted when `call` is scheduled as part of operation `id`. + + + +Emitted when `call` is performed as part of operation `id`. + + + +Emitted when a new proposal is scheduled with non-zero salt. + + + +Emitted when operation `id` is cancelled. + + + +Emitted when the minimum delay for future operations is modified. + + +## [](#votes)Votes + +The `VotesComponent` provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. + +### `VotesComponent` [toc] [#VotesComponent] + + + +```rust +use openzeppelin_governance::votes::VotesComponent; +``` + +Component that implements the [IVotes](#IVotes) interface and provides a flexible system for tracking and delegating voting power. + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + +When using this module, your contract must implement the [VotingUnitsTrait](#VotingUnitsTrait). For convenience, this is done automatically for `ERC20` and `ERC721` tokens. + +Voting Units Trait Implementations + +#### ERC20VotesImpl [!toc] [#VotesComponent-ERC20VotesImpl] + +- [`get_voting_units(self, account)`](#VotesComponent-ERC20VotesImpl-get_voting_units) + +#### ERC721VotesImpl [!toc] [#VotesComponent-ERC721VotesImpl] + +- [`get_voting_units(self, account)`](#VotesComponent-ERC721VotesImpl-get_voting_units) + +Embeddable Implementations + +#### VotesImpl [!toc] [#VotesComponent-VotesImpl] + +- [`get_votes(self, account)`](#VotesComponent-get_votes) +- [`get_past_votes(self, account, timepoint)`](#VotesComponent-get_past_votes) +- [`get_past_total_supply(self, timepoint)`](#VotesComponent-get_past_total_supply) +- [`delegates(self, account)`](#VotesComponent-delegates) +- [`delegate(self, delegatee)`](#VotesComponent-delegate) +- [`delegate_by_sig(self, delegator, delegatee, nonce, expiry, signature)`](#VotesComponent-delegate_by_sig) +- [`clock(self)`](#VotesComponent-clock) +- [`CLOCK_MODE(self)`](#VotesComponent-CLOCK_MODE) + +Internal implementations + +#### InternalImpl [!toc] [#VotesComponent-InternalImpl] + +- [`get_total_supply(self)`](#VotesComponent-get_total_supply) +- [`move_delegate_votes(self, from, to, amount)`](#VotesComponent-move_delegate_votes) +- [`transfer_voting_units(self, from, to, amount)`](#VotesComponent-transfer_voting_units) +- [`num_checkpoints(self, account)`](#VotesComponent-num_checkpoints) +- [`checkpoints(self, account, pos)`](#VotesComponent-checkpoints) +- [`_delegate(self, account, delegatee)`](#VotesComponent-_delegate) + +Events + +- [`DelegateChanged(delegator, from_delegate, to_delegate)`](#VotesComponent-DelegateChanged) +- [`DelegateVotesChanged(delegate, previous_votes, new_votes)`](#VotesComponent-DelegateVotesChanged) + + +Returns the number of voting units for a given account. + +This implementation is specific to ERC20 tokens, where the balance of tokens directly represents the number of voting units. + +This implementation will work out of the box if the ERC20 component is implemented in the final contract. + +This implementation assumes tokens map to voting units 1:1. Any deviation from this formula when transferring voting units (e.g. by using hooks) may compromise the internal vote accounting. + + + +Returns the number of voting units for a given account. + +This implementation is specific to ERC721 tokens, where each token represents one voting unit. The function returns the balance of ERC721 tokens for the specified account. + +This implementation will work out of the box if the ERC721 component is implemented in the final contract. + +This implementation assumes tokens map to voting units 1:1. Any deviation from this formula when transferring voting units (e.g. by using hooks) may compromise the internal vote accounting. + + +#### Embeddable functions [!toc] [#VotesComponent-EmbeddableFunctions] + + +Returns the current amount of votes that `account` has. + + + +Returns the amount of votes that `account` had at a specific moment in the past. + +Requirements: + +- `timepoint` must be in the past. + + + +Returns the total supply of votes available at a specific moment in the past. + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. Votes that have not been delegated are still part of total supply, even though they would not participate in a vote. + +Requirements: + +- `timepoint` must be in the past. + + + +Returns the delegate that `account` has chosen. + + + +Delegates votes from the sender to `delegatee`. + +Emits a [DelegateChanged](#VotesComponent-DelegateChanged) event. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Delegates votes from `delegator` to `delegatee` through a [SNIP-12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) message signature validation. + +Requirements: + +- `expiry` must not be in the past. +- `nonce` must match the account's current nonce. +- `delegator` must implement `SRC6::is_valid_signature`. +- `signature` should be valid for the message hash. + +Emits a [DelegateChanged](#VotesComponent-DelegateChanged) event. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Returns the current timepoint determined by the contract's operational mode, intended for use in time-sensitive logic. See [ERC-6372#clock](https://eips.ethereum.org/EIPS/eip-6372#clock). + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the contract is operating in. See [ERC-6372#CLOCK\_MODE](https://eips.ethereum.org/EIPS/eip-6372#clock_mode). + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + +#### Internal functions [!toc] [#VotesComponent-InternalFunctions] + + +Returns the current total supply of votes. + + + +Moves delegated votes from one delegate to another. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Transfers, mints, or burns voting units. + +To register a mint, `from` should be zero. To register a burn, `to` should be zero. Total supply of voting units will be adjusted with mints and burns. + +If voting units are based on an underlying transferable asset (like a token), you must call this function every time the asset is transferred to keep the internal voting power accounting in sync. For ERC20 and ERC721 tokens, this is typically handled using hooks. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + + +Returns the number of checkpoints for `account`. + + + +Returns the `pos`-th checkpoint for `account`. + + + +Delegates all of `account`'s voting units to `delegatee`. + +Emits a [DelegateChanged](#VotesComponent-DelegateChanged) event. + +May emit one or two [DelegateVotesChanged](#VotesComponent-DelegateVotesChanged) events. + + +#### Events [!toc] [#VotesComponent-Events] + + +Emitted when an account changes their delegate. + + + +Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. + + +### `VotingUnitsTrait` [toc] [#VotingUnitsTrait] + + + +```rust +pub trait VotingUnitsTrait { + fn get_voting_units(self: @TState, account: ContractAddress) -> u256; +} +``` + +A trait that must be implemented when integrating [VotesComponent](#VotesComponent) into a contract. It offers a mechanism to retrieve the number of voting units for a given account at the current time. + +Functions + +- [`get_voting_units(self, account)`](#VotingUnitsTrait-get_voting_units) + +#### Functions [!toc] [#VotingUnitsTrait-Functions] + + +Returns the number of voting units for a given account. For ERC20, this is typically the token balance. For ERC721, this is typically the number of tokens owned. + +While any formula can be used as a measure of voting units, the internal vote accounting of the contract may be compromised if voting units are transferred in any external flow by following a different formula. +For example, when implementing the hook for ERC20, the number of voting units transferred should match the formula given by the `get_voting_units` implementation. + diff --git a/docs/content/contracts-cairo/alpha/api/introspection.mdx b/docs/content/contracts-cairo/alpha/api/introspection.mdx new file mode 100644 index 00000000..32a0450c --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/introspection.mdx @@ -0,0 +1,105 @@ +--- +title: Introspection +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate handles [type introspection](https://en.wikipedia.org/wiki/Type_introspection) of contracts. In other words, it examines which functions can be called on a given contract. This is referred to as the contract's interface. + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### `ISRC5` [toc] [#ISRC5] + + + +```rust +use openzeppelin_interfaces::introspection::ISRC5; +``` + +Interface of the SRC5 Introspection Standard as defined in [SNIP-5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md). + +[SRC5 ID](#ISRC5) + +```text +0x3f918d17e5ee77373b56385708f855659a07f75997f365cf87748628532a055 +``` + +Functions + +- [`supports_interface(interface_id)`](#ISRC5-supports_interface) + +#### Functions [!toc] [#ISRC5-Functions] + + +Checks whether the contract implements the given interface. + +Check [Computing the Interface ID](../introspection#computing-the-interface-id) for more information on how to compute this ID. + + +## [](#core)Core + +### `SRC5Component` [toc] [#SRC5Component] + + + +```rust +use openzeppelin_introspection::src5::SRC5Component; +``` + +SRC5 component extending [`ISRC5`](#ISRC5). + +Embeddable Implementations + +#### SRC5Impl [!toc] [#SRC5Component-SRC5Impl] + +- [`supports_interface(self, interface_id)`](#SRC5Component-supports_interface) + +Internal Implementations + +#### InternalImpl [!toc] [#SRC5Component-InternalImpl] + +- [`register_interface(self, interface_id)`](#SRC5Component-register_interface) +- [`deregister_interface(self, interface_id)`](#SRC5Component-deregister_interface) + +#### Embeddable functions [!toc] [#SRC5Component-EmbeddableFunctions] + + +See [`ISRC5::supports_interface`](#ISRC5-supports_interface). + + +#### Internal functions [!toc] [#SRC5Component-InternalFunctions] + + +Registers support for the given `interface_id`. + + + +Deregisters support for the given `interface_id`. + diff --git a/docs/content/contracts-cairo/alpha/api/merkle-tree.mdx b/docs/content/contracts-cairo/alpha/api/merkle-tree.mdx new file mode 100644 index 00000000..5772e073 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/merkle-tree.mdx @@ -0,0 +1,185 @@ +--- +title: Merkle Tree +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides a set of utilities for verifying Merkle Tree proofs on-chain. The tree and the proofs can be generated using this [JavaScript library](https://github.com/ericnordelo/strk-merkle-tree). + +This module provides: + +- `verify` - can prove that some value is part of a Merkle tree. +- `verify_multi_proof` - can prove multiple values are part of a Merkle tree. + +`openzeppelin_merkle_tree` doesn’t have dependencies outside of `corelib`, and can be used in projects that are not Starknet-related. + +To use it as a standalone package, you can add it in your `Scarb.toml` as follows: + +`openzeppelin_merkle_tree = "3.0.0-alpha.1"` + +## [](#modules)Modules + +### [](#merkle_proof)`merkle_proof` [toc] [#merkle_proof] + + + +```rust +use openzeppelin_merkle_tree::merkle_proof; +``` + +These functions deal with verification of Merkle Tree proofs. + +The tree and the proofs can be generated using this [JavaScript library](https://github.com/ericnordelo/strk-merkle-tree). You will find a quickstart guide in the readme. + +You should avoid using leaf values that are two felt252 values long prior to hashing, or use a hash function other than the one used to hash internal nodes for hashing leaves. This is because the concatenation of a sorted pair of internal nodes in the Merkle tree could be reinterpreted as a leaf value. The JavaScript library generates Merkle trees that are safe against this attack out of the box. + +Functions + +- [`verify(proof, root, leaf)`](#merkle_proof-verify) +- [`verify_pedersen(proof, root, leaf)`](#merkle_proof-verify_pedersen) +- [`verify_poseidon(proof, root, leaf)`](#merkle_proof-verify_poseidon) +- [`process_proof(proof, leaf)`](#merkle_proof-process_proof) +- [`verify_multi_proof(proof, proof_flags, root, leaves)`](#merkle_proof-verify_multi_proof) +- [`process_multi_proof(proof, proof_flags, leaf)`](#merkle_proof-process_multi_proof) + +#### [](#merkle_proof-Functions)Functions [!toc] + + +Returns true if a `leaf` can be proved to be a part of a Merkle tree defined by `root`. + +For this, a `proof` must be provided, containing sibling hashes on the branch from the leaf to the root of the tree. + +Each pair of leaves and each pair of pre-images are assumed to be sorted. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + +`verify_pedersen` and `verify_poseidon` already include the corresponding `Hasher` implementations. + + + +Version of `verify` using Pedersen as the hashing function. + + + +Version of `verify` using Poseidon as the hashing function. + + + +Returns the rebuilt hash obtained by traversing a Merkle tree up from `leaf` using `proof`. + +A `proof` is valid if and only if the rebuilt hash matches the root of the tree. + +When processing the proof, the pairs of leaves & pre-images are assumed to be sorted. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + + + +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by `root`, according to `proof` and `proof_flags` as described in `process_multi_proof`. + +The `leaves` must be validated independently. + +Not all Merkle trees admit multiproofs. See `process_multi_proof` for details. + +Consider the case where `root == proof.at(0) && leaves.len() == 0` as it will return `true`. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + + + +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. + +The reconstruction proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another leaf/inner node or a proof sibling node, depending on whether each `proof_flags` item is true or false respectively. + +Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: + +1. The tree is complete (but not necessarily perfect). +2. The leaves to be proven are in the opposite order than they are in the tree. (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + +The *empty set* (i.e. the case where `proof.len() == 1 && leaves.len() == 0`) is considered a no-op, and therefore a valid multiproof (i.e. it returns `proof.at(0)`). Consider disallowing this case if you're not validating the leaves elsewhere. + +This function expects a `CommutativeHasher` implementation. See [hashes::CommutativeHasher](#hashes-CommutativeHasher) for more information. + + +### [](#hashes)`hashes` [toc] [#hashes] + + + +```rust +use openzeppelin_merkle_tree::hashes; +``` + +Module providing the trait and default implementations for the commutative hash functions used in [`merkle_proof`](#merkle_proof). + +The `PedersenCHasher` implementation matches the default node hashing function used in the [JavaScript library](https://github.com/ericnordelo/strk-merkle-tree). + +Traits + +- [`CommutativeHasher`](#hashes-CommutativeHasher) + +Impls + +- [`PedersenCHasher`](#hashes-PedersenCHasher) +- [`PoseidonCHasher`](#hashes-PoseidonCHasher) + +#### [](#hashes-Traits)Traits [!toc] + + +Declares a commutative hash function with the following signature: + +`commutative_hash(a: felt252, b: felt252) → felt252;` + +which computes a commutative hash of a sorted pair of felt252 values. + +This is usually implemented as an extension of a non-commutative hash function, like Pedersen or Poseidon, returning the hash of the concatenation of the two values by first sorting them. + +Frequently used when working with merkle proofs. + +The `commutative_hash` function MUST follow the invariant that `commutative_hash(a, b) == commutative_hash(b, a)`. + + +#### [](#hashes-Impls)Impls [!toc] + + +Implementation of the `CommutativeHasher` trait which computes the Pedersen hash of chaining the two input values with the len (2), sorting the pair first. + + + +Implementation of the `CommutativeHasher` trait which computes the Poseidon hash of the concatenation of two values, sorting the pair first. + diff --git a/docs/content/contracts-cairo/alpha/api/security.mdx b/docs/content/contracts-cairo/alpha/api/security.mdx new file mode 100644 index 00000000..12e3a01f --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/security.mdx @@ -0,0 +1,202 @@ +--- +title: Security +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides components to handle common security-related tasks. + +## [](#initializable)Initializable + +### [](#InitializableComponent)`InitializableComponent` [toc] [#InitializableComponent] + + + +```rust +use openzeppelin_security::InitializableComponent; +``` + +Component enabling one-time initialization for contracts. + +Embeddable Implementations + +InitializableImpl + +- [`is_initialized(self)`](#InitializableComponent-is_initialized) + +Internal Implementations + +InternalImpl + +- [`initialize(self)`](#InitializableComponent-initialize) + +#### [](#InitializableComponent-Embeddable-Functions)Embeddable functions [!toc] + + +Returns whether the contract has been initialized. + + +#### [](#InitializableComponent-Internal-Functions)Internal functions [!toc] + + +Initializes the contract. Can only be called once. + +Requirements: + +- the contract must not have been initialized before. + + +## [](#pausable)Pausable + +### [](#PausableComponent)`PausableComponent` [toc] [#PausableComponent] + + + +```rust +use openzeppelin_security::PausableComponent; +``` + +Component to implement an emergency stop mechanism. + +Embeddable Implementations + +PausableImpl + +- [`is_paused(self)`](#PausableComponent-is_paused) + +Internal Implementations + +InternalImpl + +- [`assert_not_paused(self)`](#PausableComponent-assert_not_paused) +- [`assert_paused(self)`](#PausableComponent-assert_paused) +- [`pause(self)`](#PausableComponent-pause) +- [`unpause(self)`](#PausableComponent-unpause) + +Events + +- [`Paused(account)`](#PausableComponent-Paused) +- [`Unpaused(account)`](#PausableComponent-Unpaused) + +#### [](#PausableComponent-Embeddable-Functions)Embeddable functions [!toc] + + +Returns whether the contract is currently paused. + + +#### [](#PausableComponent-Internal-Functions)Internal functions [!toc] + + +Panics if the contract is paused. + + + +Panics if the contract is not paused. + + + +Pauses the contract. + +Requirements: + +- the contract must not be paused. + +Emits a [Paused](#PausableComponent-Paused) event. + + + +Unpauses the contract. + +Requirements: + +- the contract must be paused. + +Emits an [Unpaused](#PausableComponent-Unpaused) event. + + +#### [](#PausableComponent-Events)Events [!toc] + + +Emitted when the contract is paused by `account`. + + + +Emitted when the contract is unpaused by `account`. + + +## [](#reentrancyguard)ReentrancyGuard + +### [](#ReentrancyGuardComponent)`ReentrancyGuardComponent` [toc] [#ReentrancyGuardComponent] + + + +```rust +use openzeppelin_security::ReentrancyGuardComponent; +``` + +Component to help prevent reentrant calls. + +Internal Implementations + +InternalImpl + +- [`start(self)`](#ReentrancyGuardComponent-start) +- [`end(self)`](#ReentrancyGuardComponent-end) + +#### [](#ReentrancyGuardComponent-Internal-Functions)Internal functions [!toc] + + +Prevents a contract's function from calling itself or another protected function, directly or indirectly. + +Requirements: + +- the guard must not be currently enabled. + + + +Removes the reentrant guard. + diff --git a/docs/content/contracts-cairo/alpha/api/testing.mdx b/docs/content/contracts-cairo/alpha/api/testing.mdx new file mode 100644 index 00000000..7a9b3cc6 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/testing.mdx @@ -0,0 +1,10 @@ +--- +title: Testing +--- + + +The `openzeppelin_testing` package version is now decoupled from the `cairo-contracts` version. + + +You can find the documentation for the `openzeppelin_testing` package in the README for the corresponding version in the +[scarb registry](https://scarbs.xyz/packages/openzeppelin_testing). diff --git a/docs/content/contracts-cairo/alpha/api/token_common.mdx b/docs/content/contracts-cairo/alpha/api/token_common.mdx new file mode 100644 index 00000000..de34561d --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/token_common.mdx @@ -0,0 +1,539 @@ +--- +title: Common (Token) +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This module provides extensions and utilities that are common to multiple token standards. + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### [](#IERC2981)`IERC2981` [toc] [#IERC2981] + + + +```rust +use openzeppelin_interfaces::erc2981::IERC2981; +``` + +[SRC5 ID](./introspection#ISRC5) + +```text +0x2d3414e45a8700c29f119a54b9f11dca0e29e06ddcb214018fc37340e165ed6 +``` + +Interface of the ERC2981 standard as defined in [EIP-2981](https://eips.ethereum.org/EIPS/eip-2981). + +Functions + +- [`royalty_info(token_id, sale_price)`](#IERC2981-royalty_info) + +#### [](#IERC2981-Functions)Functions [!toc] + + +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of exchange. The royalty amount is denominated and must be paid in that same unit of exchange. + + +### [](#IERC2981Info)`IERC2981Info` [toc] [#IERC2981Info] + + + +```rust +use openzeppelin_interfaces::erc2981::IERC2981Info; +``` + +Interface providing external read functions for discovering the state of ERC2981 component. + +Functions + +- [`default_royalty()`](#IERC2981Info-default_royalty) +- [`token_royalty(token_id)`](#IERC2981Info-token_royalty) + +#### [](#IERC2981Info-Functions)Functions [!toc] + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Returns the royalty information specific to a token. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + +### [](#IERC2981Admin)`IERC2981Admin` [toc] [#IERC2981Admin] + + + +```rust +use openzeppelin_interfaces::erc2981::IERC2981Admin; +``` + +Interface providing external admin functions for managing the settings of ERC2981 component. + +Functions + +- [`set_default_royalty(receiver, fee_numerator)`](#IERC2981Admin-set_default_royalty) +- [`delete_default_royalty()`](#IERC2981Admin-delete_default_royalty) +- [`set_token_royalty(token_id, receiver, fee_numerator)`](#IERC2981Admin-set_token_royalty) +- [`reset_token_royalty(token_id)`](#IERC2981Admin-reset_token_royalty) + +#### [](#IERC2981Admin-Functions)Functions [!toc] + + +Sets the royalty information that all ids in this contract will default to. + + + +Sets the default royalty percentage and receiver to zero. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + + + +Resets royalty information for the token id back to unset. + + +## [](#erc2981)ERC2981 + +### [](#ERC2981Component)`ERC2981Component` [toc] [#ERC2981Component] + + + +```rust +use openzeppelin_token::common::erc2981::ERC2981Component; +``` + +ERC2981 component extending [IERC2981](#IERC2981). + +[Immutable Component Config](../components#immutable-config) + +constants + +- [`FEE_DENOMINATOR`](#ERC2981Component-IC-FEE_DENOMINATOR) + +functions + +- [`validate()`](#ERC2981Component-IC-validate) + +Embeddable Implementations + +ERC2981Impl + +- [`royalty_info(self, token_id, sale_price)`](#ERC2981Component-royalty_info) + +ERC2981InfoImpl + +- [`default_royalty(self)`](#ERC2981InfoImpl-default_royalty) +- [`token_royalty(self, token_id)`](#ERC2981InfoImpl-token_royalty) + +ERC2981AdminOwnableImpl + +- [`set_default_royalty(self, receiver, fee_numerator)`](#ERC2981AdminOwnableImpl-set_default_royalty) +- [`delete_default_royalty(self)`](#ERC2981AdminOwnableImpl-delete_default_royalty) +- [`set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981AdminOwnableImpl-set_token_royalty) +- [`reset_token_royalty(self, token_id)`](#ERC2981AdminOwnableImpl-reset_token_royalty) + +ERC2981AdminAccessControlImpl + +- [`set_default_royalty(self, receiver, fee_numerator)`](#ERC2981AdminAccessControlImpl-set_default_royalty) +- [`delete_default_royalty(self)`](#ERC2981AdminAccessControlImpl-delete_default_royalty) +- [`set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981AdminAccessControlImpl-set_token_royalty) +- [`reset_token_royalty(self, token_id)`](#ERC2981AdminAccessControlImpl-reset_token_royalty) + +ERC2981AdminAccessControlDefaultAdminRulesImpl + +- [`set_default_royalty(self, receiver, fee_numerator)`](#ERC2981AdminAccessControlDefaultAdminRulesImpl-set_default_royalty) +- [`delete_default_royalty(self)`](#ERC2981AdminAccessControlDefaultAdminRulesImpl-delete_default_royalty) +- [`set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981AdminAccessControlDefaultAdminRulesImpl-set_token_royalty) +- [`reset_token_royalty(self, token_id)`](#ERC2981AdminAccessControlDefaultAdminRulesImpl-reset_token_royalty) + +Internal implementations + +InternalImpl + +- [`initializer(self, default_receiver, default_royalty_fraction)`](#ERC2981Component-initializer) +- [`_default_royalty(self)`](#ERC2981Component-_default_royalty) +- [`_set_default_royalty(self, receiver, fee_numerator)`](#ERC2981Component-_set_default_royalty) +- [`_delete_default_royalty(self)`](#ERC2981Component-_delete_default_royalty) +- [`_token_royalty(self, token_id)`](#ERC2981Component-_token_royalty) +- [`_set_token_royalty(self, token_id, receiver, fee_numerator)`](#ERC2981Component-_set_token_royalty) +- [`_reset_token_royalty(self, token_id)`](#ERC2981Component-_reset_token_royalty) + +#### [](#ERC2981Component-Immutable-Config)Immutable Config constants [!toc] + + +The denominator with which to interpret the fee set in `_set_token_royalty` and `_set_default_royalty` as a fraction of the sale price. + + + +Validates the given implementation of the contract's configuration. + +Requirements: + +- `FEE_DENOMINATOR` must be greater than 0. + +This function is called by the contract's initializer. + + +#### [](#ERC2981Component-Embeddable-functions)Embeddable functions [!toc] + + +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The amount of royalty payment. + + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Returns the royalty information specific to a token. If no specific royalty information is set for the token, the default is returned. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + +#### [](#ERC2981Component-ERC2981AdminOwnableImpl)ERC2981AdminOwnableImpl [!toc] + +Provides admin functions for managing royalty settings that are restricted to be called only by the contract's owner. Requires the contract to implement [OwnableComponent](./access#OwnableComponent). + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- The caller is the contract owner. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + +Requirements: + +- The caller is the contract owner. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- The caller is the contract owner. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + +Requirements: + +- The caller is the contract owner. + + +#### [](#ERC2981Component-ERC2981AdminAccessControlImpl)ERC2981AdminAccessControlImpl [!toc] + +Provides admin functions for managing royalty settings that require `ROYALTY_ADMIN_ROLE` to be granted to the caller. Requires the contract to implement [AccessControlComponent](./access#AccessControlComponent). + + +Role for the admin responsible for managing royalty settings. + + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. + + +#### [](#ERC2981Component-ERC2981AdminAccessControlDefaultAdminRulesImpl)ERC2981AdminAccessControlDefaultAdminRulesImpl [!toc] + +An alternative implementation of [IERC2981Admin](#IERC2981Admin). Provides admin functions for managing royalty settings +that require `ROYALTY_ADMIN_ROLE` to be granted to the caller. Requires the contract to implement [AccessControlDefaultAdminRulesComponent](./access#AccessControlDefaultAdminRulesComponent). + + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + +Requirements: + +- The caller must have `ROYALTY_ADMIN_ROLE` role. + + +#### [](#ERC2981Component-Internal-functions)Internal functions [!toc] + + +Initializes the contract by setting the default royalty and registering the supported interface. + +Requirements: + +- `default_receiver` cannot be the zero address. +- `default_royalty_fraction` cannot be greater than the fee denominator. +- The fee denominator must be greater than 0. + +The fee denominator is set by the contract using the [Immutable Component Config](../components#immutable-config). + + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Sets the default royalty percentage and receiver to zero. + + + +Returns the royalty information that all ids in this contract will default to. + +The returned tuple contains: + +- `t.0`: The receiver of the royalty payment. +- `t.1`: The numerator of the royalty fraction. +- `t.2`: The denominator of the royalty fraction. + + + +Sets the royalty information for a specific token id that takes precedence over the global default. + +Requirements: + +- `receiver` cannot be the zero address. +- `fee_numerator` cannot be greater than the fee denominator. + + + +Resets royalty information for the token id back to unset. + diff --git a/docs/content/contracts-cairo/alpha/api/udc.mdx b/docs/content/contracts-cairo/alpha/api/udc.mdx new file mode 100644 index 00000000..742fd907 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/udc.mdx @@ -0,0 +1,90 @@ +--- +title: Universal Deployer +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +Reference of the Universal Deployer Contract (UDC) interface and preset. + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### [](#IUniversalDeployer)`IUniversalDeployer` [toc] [#IUniversalDeployer] + + + +```rust +use openzeppelin_interfaces::deployments::IUniversalDeployer; +``` + +Functions + +- [`deploy_contract(class_hash, salt, not_from_zero, calldata)`](#IUniversalDeployer-deploy_contract) + +Events + +- [`ContractDeployed(address, deployer, not_from_zero, class_hash, calldata, salt)`](#IUniversalDeployer-ContractDeployed) + +#### [](#IUniversalDeployer-Functions)Functions [!toc] + + +Deploys a contract through the Universal Deployer Contract. + + +#### [](#IUniversalDeployer-Events)Events [!toc] + + +Emitted when `deployer` deploys a contract through the Universal Deployer Contract. + + +## [](#presets)Presets + +### [](#UniversalDeployer)`UniversalDeployer` [toc] [#UniversalDeployer] + + + +```rust +use openzeppelin_presets::UniversalDeployer; +``` + +The standard Universal Deployer Contract. + +[Sierra class hash](../presets) + +```text +{{UniversalDeployerClassHash}} +``` + +Embedded Implementations + +UniversalDeployerImpl + +- [`deploy_contract(self, address, deployer, not_from_zero, class_hash, calldata, salt)`](#UniversalDeployer-deploy_contract) + +#### [](#UniversalDeployer-External-functions)External functions [!toc] + + +Deploys a contract through the Universal Deployer Contract. + +When `not_from_zero` is `true`, `salt` is hashed with the caller address and the modified salt is passed to the inner `deploy_syscall`. This type of deployment is [origin-dependent](../udc#origin-dependent). + +When `not_from_zero` is `false`, the deployment type is [origin-independent](../udc#origin-independent). + +Emits an [ContractDeployed](#IUniversalDeployer-ContractDeployed) event. + diff --git a/docs/content/contracts-cairo/alpha/api/upgrades.mdx b/docs/content/contracts-cairo/alpha/api/upgrades.mdx new file mode 100644 index 00000000..b7013565 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/upgrades.mdx @@ -0,0 +1,133 @@ +--- +title: Upgrades +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides interfaces and utilities related to upgradeability. + +## [](#interfaces)Interfaces + + +Starting from version `3.x.x`, the interfaces are no longer part of the `openzeppelin_access` package. The references +documented here are contained in the `openzeppelin_interfaces` package version `{{openzeppelin_interfaces_version}}`. + + +### [](#IUpgradeable)`IUpgradeable` [toc] [#IUpgradeable] + + + +```rust +use openzeppelin_interfaces::upgrades::IUpgradeable; +``` + +Interface of an upgradeable contract. + +Functions + +- [`upgrade(new_class_hash)`](#IUpgradeable-upgrade) + +#### [](#IUpgradeable-Functions)Functions [!toc] + + +Upgrades the contract code by updating its [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/). + +This function is usually protected by an [Access Control](../access) mechanism. + + +### [](#IUpgradeAndCall)`IUpgradeAndCall` [toc] [#IUpgradeAndCall] + + + +```rust +use openzeppelin_interfaces::upgrades::IUpgradeAndCall; +``` + +Interface for an upgradeable contract that couples an upgrade with a function call in the upgraded context. + +Functions + +- [`upgrade_and_call(new_class_hash, selector, calldata)`](#IUpgradeAndCall-upgrade_and_call) + +#### [](#IUpgradeAndCall-Functions)Functions [!toc] + + +Upgrades the contract code by updating its [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/) and calls `selector` with the upgraded context. + +This function is usually protected by an [Access Control](../access) mechanism. + + +## [](#core)Core + +### [](#UpgradeableComponent)`UpgradeableComponent` [toc] [#UpgradeableComponent] + + + +```rust +use openzeppelin_upgrades::upgradeable::UpgradeableComponent; +``` + +Upgradeable component. + +Internal Implementations + +InternalImpl + +- [`upgrade(self, new_class_hash)`](#UpgradeableComponent-upgrade) +- [`upgrade_and_call(self, new_class_hash, selector, calldata)`](#UpgradeableComponent-upgrade_and_call) + +Events + +- [`Upgraded(class_hash)`](#UpgradeableComponent-Upgraded) + +#### [](#UpgradeableComponent-Internal-Functions)Internal Functions [!toc] + + +Upgrades the contract by updating the contract [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/). + +Requirements: + +- `new_class_hash` must be different from zero. + +Emits an [Upgraded](#UpgradeableComponent-Upgraded) event. + + + +Replaces the contract's class hash with `new_class_hash` and then calls `selector` from the upgraded context. This function returns the unwrapped `call_contract_syscall` return value(s), if available, of the `selector` call. + +Requirements: + +- `new_class_hash` must be different from zero. + +The function call comes from the upgraded contract itself and not the account. + +A similar behavior to `upgrade_and_call` can also be achieved with a list of calls from an account since the [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) account standard supports multicall. An account can execute a list of calls with [upgrade](#IUpgradeable-upgrade) being the first element in the list and the extra function call as the second. With this approach, the calls will execute from the account's context and can't be front-ran. + +Emits an [Upgraded](#UpgradeableComponent-Upgraded) event. + + +#### [](#UpgradeableComponent-Events)Events [!toc] + + +Emitted when the [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/) is upgraded. + diff --git a/docs/content/contracts-cairo/alpha/api/utilities.mdx b/docs/content/contracts-cairo/alpha/api/utilities.mdx new file mode 100644 index 00000000..2b7b13da --- /dev/null +++ b/docs/content/contracts-cairo/alpha/api/utilities.mdx @@ -0,0 +1,417 @@ +--- +title: Utilities +--- + +import { UMBRELLA_VERSION } from "../utils/constants.js"; + +This crate provides miscellaneous components and libraries containing utility functions to handle common tasks. + +## Core + +### `utils` [toc] [#utils] + + + +```rust +use openzeppelin_utils; +``` + +Module containing core utilities of the library. + +Members + +Inner modules + +- [`cryptography`](#utils-cryptography) +- [`deployments`](#utils-deployments) +- [`math`](#utils-math) +- [`contract_clock`](#utils-contract_clock) +- [`serde`](#utils-serde) + +#### Inner modules [!toc] [#utils-Inner-Modules] + + +See [`openzeppelin_utils::cryptography`](#cryptography). + + + +See [`openzeppelin_utils::deployments`](#deployments). + + + +See [`openzeppelin_utils::math`](#math). + + + +See [`openzeppelin_utils::contract_clock`](#contract_clock). + + + +See [`openzeppelin_utils::serde`](#serde). + + +### `cryptography` [toc] [#cryptography] + + + +```rust +use openzeppelin_utils::cryptography; +``` + +Module containing utilities related to cryptography. + +Members + +Inner modules + +- [`nonces`](#cryptography-nonces) +- [`snip12`](#cryptography-snip12) + +#### Inner modules [!toc] [#cryptography-Inner-Modules] + + +See [`openzeppelin_utils::cryptography::nonces::NoncesComponent`](#NoncesComponent). + + + +See [`openzeppelin_utils::cryptography::snip12`](#snip12). + + +### `deployments` [toc] [#deployments] + + + +```rust +use openzeppelin_utils::deployments; +``` + +Module containing utility functions for calculating contract addresses through [deploy\_syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy) and the [Universal Deployer Contract](../udc) (UDC). + +Members + +Structs + +- [`DeployerInfo(caller_address, udc_address)`](#deployments-DeployerInfo) + +Functions + +- [`calculate_contract_address_from_deploy_syscall(salt, class_hash, constructor_calldata, deployer_address)`](#deployments-calculate_contract_address_from_deploy_syscall) +- [`compute_hash_on_elements(data)`](#deployments-compute_hash_on_elements) +- [`calculate_contract_address_from_udc(salt, class_hash, constructor_calldata, deployer_info)`](#deployments-calculate_contract_address_from_udc) + +#### Structs [!toc] [#deployments-Structs] + + +Struct containing arguments necessary in [utils::calculate\_contract\_address\_from\_udc](#deployments-calculate_contract_address_from_udc) for origin-dependent deployment calculations. + + +#### Functions [!toc] [#deployments-Functions] + + +Returns the contract address when passing the given arguments to [deploy\_syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy). + + + +Creates a Pedersen hash chain with the elements of `data` and returns the finalized hash. + + + +Returns the calculated contract address for UDC deployments. + +Origin-independent deployments (deployed from zero) should pass `Option::None` as `deployer_info`. + +Origin-dependent deployments hash `salt` with `caller_address` (member of [DeployerInfo](#deployments-DeployerInfo)) and pass the hashed salt to the inner [deploy\_syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy) as the `contract_address_salt` argument. + + +### `execution` [toc] [#execution] + + + +```rust +use openzeppelin_utils::execution; +``` + +Module containing utilities related to execution. + +#### Functions [!toc] [#execution-Functions] + + +Executes a list of calls and returns an array containing the return values from each call. + + + +Executes a single call and returns its return value. + + + +Validates a signature using SRC6 `is_valid_signature` and asserts it's valid. Checks both 'VALID' (`starknet::VALIDATED`) and true (1) for backwards compatibility. Reverts with `invalid_signature_error` if signature is invalid. + + +### `math` [toc] [#math] + + + +```rust +use openzeppelin_utils::math; +``` + +Module containing math utilities. + +Members + +Functions + +- [`average(a, b)`](#math-average) + +#### Functions [!toc] [#math-Functions] + + +Returns the average of two unsigned integers. The result is rounded down. + +`T` is a generic value matching different numeric implementations. + + +### `contract_clock` [toc] [#contract_clock] + + +```rust +use openzeppelin_utils::contract_clock; +``` + +Module providing a trait for the [EIP-6372](https://eips.ethereum.org/EIPS/eip-6372) standard along with default clock implementations based on either block number or block timestamp. + +Traits + +- [`ERC6372Clock`](#ERC6372Clock) + +Implementations + +- [`ERC6372BlockNumberClock`](#contract_clock-ERC6372BlockNumberClock) +- [`ERC6372TimestampClock`](#contract_clock-ERC6372TimestampClock) + +#### `ERC6372Clock` [toc] [#ERC6372Clock] + + +```rust +use openzeppelin_utils::contract_clock::ERC6372Clock; +``` + +A trait for the [EIP-6372](https://eips.ethereum.org/EIPS/eip-6372) standard that allows flexible internal clock implementation — based on block timestamp, block number, or a custom logic. + +Functions + +- [`clock()`](#ERC6372Clock-clock) +- [`CLOCK_MODE()`](#ERC6372Clock-CLOCK_MODE) + +#### Functions [!toc] [#ERC6372Clock-Functions] + + +Returns the current timepoint determined by the contract's operational mode, intended for use in time-sensitive logic. + +Requirements: + +- This function MUST always be non-decreasing. + + + +Returns a description of the clock the contract is operating in. + +Requirements: + +- The output MUST be formatted like a URL query string, decodable in standard JavaScript. + + +#### Implementations [!toc] [#contract_clock-Impls] + + +Implementation of the `ERC6372Clock` trait that uses the block number as its clock reference. + + + +Implementation of the `ERC6372Clock` trait that uses the block timestamp as its clock reference. + + +### `serde` [toc] [#serde] + + + +```rust +use openzeppelin_utils::serde; +``` + +Module containing utilities related to serialization and deserialization of Cairo data structures. + +Members + +Traits + +- [`SerializedAppend`](#serde-SerializedAppend) + +#### Traits [!toc] [#serde-Traits] + + +Importing this trait allows the ability to append a serialized representation of a Cairo data structure already implementing the `Serde` trait to a `felt252` buffer. + +Usage example: + +```rust +use openzeppelin_utils::serde::SerializedAppend; +use starknet::ContractAddress; + +fn to_calldata(recipient: ContractAddress, amount: u256) -> Array { + let mut calldata = array![]; + calldata.append_serde(recipient); + calldata.append_serde(amount); + calldata +} +``` + +Note that the `append_serde` method is automatically available for arrays of felts, and it accepts any data structure that implements the `Serde` trait. + + +## Cryptography [#cryptography-toc] + +### `NoncesComponent` [toc] [#NoncesComponent] + + + +```rust +use openzeppelin_utils::cryptography::nonces::NoncesComponent; +``` + +This component provides a simple mechanism for handling incremental nonces for a set of addresses. It is commonly used to prevent replay attacks when contracts accept signatures as input. + +Embeddable Implementations + +NoncesImpl + +- [`nonces(self, owner)`](#NoncesComponent-nonces) + +Internal Implementations + +InternalImpl + +- [`use_nonce(self, owner)`](#NoncesComponent-use_nonce) +- [`use_checked_nonce(self, owner, nonce)`](#NoncesComponent-use_checked_nonce) + +#### Embeddable functions [!toc] [#NoncesComponent-Embeddable-Functions] + + +Returns the next unused nonce for an `owner`. + + +#### Internal functions [!toc] [#NoncesComponent-Internal-Functions] + + +Consumes a nonce, returns the current value, and increments nonce. + +For each account, the nonce has an initial value of 0, can only be incremented by one, and cannot be decremented or reset. This guarantees that the nonce never overflows. + + + +Same as `use_nonce` but checking that `nonce` is the next valid one for `owner`. + + +### `snip12` [toc] [#snip12] + + + +```rust +use openzeppelin_utils::snip12; +``` + +Supports on-chain generation of message hashes compliant with [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md). + +For a full walkthrough on how to use this module, see the [SNIP12 and Typed Messages](../guides/snip12) guide. diff --git a/docs/content/contracts-cairo/alpha/backwards-compatibility.mdx b/docs/content/contracts-cairo/alpha/backwards-compatibility.mdx new file mode 100644 index 00000000..3c63bfcd --- /dev/null +++ b/docs/content/contracts-cairo/alpha/backwards-compatibility.mdx @@ -0,0 +1,35 @@ +--- +title: Backwards Compatibility +--- + +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. + +Bear in mind that while releasing versions, we treat minors as majors and patches as minors, in accordance with semantic versioning. This means that `v2.1.0` could be adding features to `v2.0.0`, while `v3.0.0` would be considered a breaking release. + +## 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. + +### 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. + +In the case of breaking "upgrade compatibility", an entry to the changelog will be added listing those breaking changes. + +## Storage layout + +Patch updates will always preserve storage layout compatibility, and after `1.0.0` minors will too. 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. + +## Cairo version + +The minimum Cairo version required to compile the contracts will remain unchanged for patch updates, but it may change for minors. diff --git a/docs/content/contracts-cairo/alpha/components.mdx b/docs/content/contracts-cairo/alpha/components.mdx new file mode 100644 index 00000000..d78deab7 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/components.mdx @@ -0,0 +1,667 @@ +--- +title: Components +--- + +The following documentation provides reasoning and examples on how to use Contracts for Cairo components. + +Starknet components are separate modules that contain storage, events, and implementations that can be integrated into a contract. +Components themselves cannot be declared or deployed. +Another way to think of components is that they are abstract modules that must be instantiated. + +[shamans_post]: https://community.starknet.io/t/cairo-components/101136#components-1 +[cairo_book]: https://book.cairo-lang.org/ch103-02-00-composability-and-components.html + + +For more information on the construction and design of Starknet components, see the [Starknet Shamans post][shamans_post] and the [Cairo book][cairo_book]. + + +## Building a contract + +### Setup + +The contract should first import the component and declare it with the `component!` macro: + +```rust +#[starknet::contract] +mod MyContract { + // Import the component + use openzeppelin_security::InitializableComponent; + + // Declare the component + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); +} +``` + +The `path` argument should be the imported component itself (in this case, [InitializableComponent](./security#initializable)). +The `storage` and `event` arguments are the variable names that will be set in the `Storage` struct and `Event` enum, respectively. +Note that even if the component doesn’t define any events, the compiler will still create an empty event enum inside the component module. + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + initializable: InitializableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + InitializableEvent: InitializableComponent::Event + } +} +``` + +The `#[substorage(v0)]` attribute must be included for each component in the `Storage` trait. +This allows the contract to have indirect access to the component’s storage. +See [Accessing component storage](#accessing-component-storage) for more on this. + +The `#[flat]` attribute for events in the `Event` enum, however, is not required. +For component events, the first key in the event log is the component ID. +Flattening the component event removes it, leaving the event ID as the first key. + +### Implementations + +Components come with granular implementations of different interfaces. +This allows contracts to integrate only the implementations that they’ll use and avoid unnecessary bloat. +Integrating an implementation looks like this: + +```rust +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + (...) + + // Gives the contract access to the implementation methods + impl InitializableImpl = + InitializableComponent::InitializableImpl; +} +``` + +Defining an `impl` gives the contract access to the methods within the implementation from the component. +For example, `is_initialized` is defined in the `InitializableImpl`. +A function on the contract level can expose it like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + (...) + + impl InitializableImpl = + InitializableComponent::InitializableImpl; + + #[external(v0)] + fn is_initialized(ref self: ContractState) -> bool { + self.initializable.is_initialized() + } +} +``` + +While there’s nothing wrong with manually exposing methods like in the previous example, this process can be tedious for implementations with many methods. +Fortunately, a contract can embed implementations which will expose all of the methods of the implementation. +To embed an implementation, add the `#[abi(embed_v0)]` attribute above the `impl`: + +```rust +#[starknet::contract] +mod MyContract { + (...) + + // This attribute exposes the methods of the `impl` + #[abi(embed_v0)] + impl InitializableImpl = + InitializableComponent::InitializableImpl; +} +``` + +`InitializableImpl` defines the `is_initialized` method in the component. +By adding the embed attribute, `is_initialized` becomes a contract entrypoint for `MyContract`. + + +Embeddable implementations, when available in this library’s components, are segregated from the internal component implementation which makes it easier to safely expose. +Components also separate granular implementations from [mixin](#mixins) implementations. +The API documentation design reflects these groupings. +See [ERC20Component](/contracts-cairo/alpha/api/erc20#erc20component) as an example which includes: + +* **Embeddable Mixin Implementation** +* **Embeddable Implementations** +* **Internal Implementations** +* **Events** + + + +### Mixins + +Mixins are impls made of a combination of smaller, more specific impls. +While separating components into granular implementations offers flexibility, +integrating components with many implementations can appear crowded especially if the contract uses all of them. +Mixins simplify this by allowing contracts to embed groups of implementations with a single directive. + +Compare the following code blocks to see the benefit of using a mixin when creating an account contract. + +#### Account without mixin + +```rust +component!(path: AccountComponent, storage: account, event: AccountEvent); +component!(path: SRC5Component, storage: src5, event: SRC5Event); + +#[abi(embed_v0)] +impl SRC6Impl = AccountComponent::SRC6Impl; +#[abi(embed_v0)] +impl DeclarerImpl = AccountComponent::DeclarerImpl; +#[abi(embed_v0)] +impl DeployableImpl = AccountComponent::DeployableImpl; +#[abi(embed_v0)] +impl PublicKeyImpl = AccountComponent::PublicKeyImpl; +#[abi(embed_v0)] +impl SRC6CamelOnlyImpl = AccountComponent::SRC6CamelOnlyImpl; +#[abi(embed_v0)] +impl PublicKeyCamelImpl = AccountComponent::PublicKeyCamelImpl; +impl AccountInternalImpl = AccountComponent::InternalImpl; + +#[abi(embed_v0)] +impl SRC5Impl = SRC5Component::SRC5Impl; +``` + +#### Account with mixin + +```rust +component!(path: AccountComponent, storage: account, event: AccountEvent); +component!(path: SRC5Component, storage: src5, event: SRC5Event); + +#[abi(embed_v0)] +impl AccountMixinImpl = AccountComponent::AccountMixinImpl; +impl AccountInternalImpl = AccountComponent::InternalImpl; +``` + +The rest of the setup for the contract, however, does not change. +This means that component dependencies must still be included in the `Storage` struct and `Event` enum. +Here’s a full example of an account contract that embeds the `AccountMixinImpl`: + +```rust +#[starknet::contract] +mod Account { + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // This embeds all of the methods from the many AccountComponent implementations + // and also includes `supports_interface` from `SRC5Impl` + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } +} +``` + +### Initializers + + +Failing to use a component’s `initializer` can result in irreparable contract deployments. + +Always read the API Reference documentation for each integrated component. + + + +Some components require some sort of setup upon construction. +Usually, this would be a job for a constructor; however, components themselves cannot implement constructors. +Components instead offer ``initializer``s within their `InternalImpl` to call from the contract’s constructor. +Let’s look at how a contract would integrate [OwnableComponent](/contracts-cairo/alpha/api/access#OwnableComponent): + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_access::ownable::OwnableComponent; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + + // Instantiate `InternalImpl` to give the contract access to the `initializer` + impl InternalImpl = OwnableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + // Invoke ownable's `initializer` + self.ownable.initializer(owner); + } +} +``` + +### Immutable Config + +While initializers help set up the component’s initial state, some require configuration that may be defined +as constants, saving gas by avoiding the necessity of reading from storage each time the variable needs to be used. The +Immutable Component Config pattern helps with this matter by allowing the implementing contract to define a set of +constants declared in the component, customizing its functionality. + + +The Immutable Component Config standard is defined in the SRC-107. + + +Here’s an example of how to use the Immutable Component Config pattern with the [ERC2981Component](/contracts-cairo/alpha/api/token_common#erc2981component): + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::common::erc2981::ERC2981Component; + use starknet::contract_address_const; + + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // Instantiate `InternalImpl` to give the contract access to the `initializer` + impl InternalImpl = ERC2981Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc2981: ERC2981Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC2981Event: ERC2981Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + // Define the immutable config + pub impl ERC2981ImmutableConfig of ERC2981Component::ImmutableConfig { + const FEE_DENOMINATOR: u128 = 10_000; + } + + #[constructor] + fn constructor(ref self: ContractState) { + let default_receiver = contract_address_const::<'RECEIVER'>(); + let default_royalty_fraction = 1000; + // Invoke erc2981's `initializer` + self.erc2981.initializer(default_receiver, default_royalty_fraction); + } +} +``` + +#### Default config + +Sometimes, components implementing the Immutable Component Config pattern provide a default configuration that can be +directly used without implementing the `ImmutableConfig` trait locally. When provided, this implementation will be named +`DefaultConfig` and will be available in the same module containing the component, as a sibling. + +In the following example, the `DefaultConfig` trait is used to define the `FEE_DENOMINATOR` config constant. + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_introspection::src5::SRC5Component; + // Bring the DefaultConfig trait into scope + use openzeppelin_token::common::erc2981::{ERC2981Component, DefaultConfig}; + use starknet::contract_address_const; + + component!(path: ERC2981Component, storage: erc2981, event: ERC2981Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + // Instantiate `InternalImpl` to give the contract access to the `initializer` + impl InternalImpl = ERC2981Component::InternalImpl; + + #[storage] + struct Storage { + (...) + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + (...) + } + + #[constructor] + fn constructor(ref self: ContractState) { + let default_receiver = contract_address_const::<'RECEIVER'>(); + let default_royalty_fraction = 1000; + // Invoke erc2981's `initializer` + self.erc2981.initializer(default_receiver, default_royalty_fraction); + } +} +``` + +#### `validate` function + +The `ImmutableConfig` trait may also include a `validate` function with a default implementation, which +asserts that the configuration is correct, and must not be overridden by the implementing contract. For more information +on how to use this function, refer to the [validate section of the SRC-107](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-107.md#validate-function). + +### Dependencies + +Some components include dependencies of other components. +Contracts that integrate components with dependencies must also include the component dependency. +For instance, [AccessControlComponent](/contracts-cairo/alpha/api/access#accesscontrolcomponent) depends on [SRC5Component](/contracts-cairo/alpha/api/introspection#src5component). +Creating a contract with `AccessControlComponent` should look like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: AccessControlComponent, storage: accesscontrol, event: AccessControlEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // AccessControl + #[abi(embed_v0)] + impl AccessControlImpl = + AccessControlComponent::AccessControlImpl; + #[abi(embed_v0)] + impl AccessControlCamelImpl = + AccessControlComponent::AccessControlCamelImpl; + impl AccessControlInternalImpl = AccessControlComponent::InternalImpl; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + accesscontrol: AccessControlComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + (...) +} +``` + +## Customization + + +Customizing implementations and accessing component storage can potentially corrupt the state, bypass security checks, and undermine the component logic. + +**Exercise extreme caution**. See [Security](#security). + + + +### Hooks + +Hooks are entrypoints to the business logic of a token component that are accessible at the contract level. +This allows contracts to insert additional behaviors before and/or after token transfers (including mints and burns). +Prior to hooks, extending functionality required contracts to create [custom implementations](#custom-implementations). + +All token components include a generic hooks trait that include empty default functions. +When creating a token contract, the using contract must create an implementation of the hooks trait. +Suppose an ERC20 contract wanted to include Pausable functionality on token transfers. +The following snippet leverages the `before_update` hook to include this behavior. + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_security::pausable::PausableComponent::InternalTrait; + use openzeppelin_security::pausable::PausableComponent; + use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + // Create the hooks implementation + impl ERC20HooksImpl of ERC20Component::ERC20HooksTrait { + // Occurs before token transfers + fn before_update( + ref self: ERC20Component::ComponentState, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + // Access local state from component state + let contract_state = self.get_contract(); + // Call function from integrated component + contract_state.pausable.assert_not_paused(); + } + + // Omitting the `after_update` hook because the default behavior + // is already implemented in the trait + } + + (...) +} +``` + +Notice that the `self` parameter expects a component state type. +Instead of passing the component state, the using contract’s state can be passed which simplifies the syntax. +The hook then moves the scope up with the Cairo-generated `get_contract` through the `HasComponent` trait (as illustrated with ERC20Component in this example). +From here, the hook can access the using contract’s integrated components, storage, and implementations. + +Be advised that even if a token contract does not require hooks, the hooks trait must still be implemented. +The using contract may instantiate an empty impl of the trait; +however, the Contracts for Cairo library already provides the instantiated impl to abstract this away from contracts. +The using contract just needs to bring the implementation into scope like this: + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; + use openzeppelin_token::erc20::ERC20HooksEmptyImpl; + + (...) +} +``` + + +For a more in-depth guide on hooks, see [Extending Cairo Contracts with Hooks](https://fleming-andrew.medium.com/extending-cairo-contracts-with-hooks-c3ca21d1d6b8). + + +### Custom implementations + +There are instances where a contract requires different or amended behaviors from a component implementation. +In these scenarios, a contract must create a custom implementation of the interface. +Let’s break down a pausable ERC20 contract to see what that looks like. +Here’s the setup: + +```rust +#[starknet::contract] +mod ERC20Pausable { + use openzeppelin_security::pausable::PausableComponent; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + // Import the ERC20 interfaces to create custom implementations + use openzeppelin_interfaces::erc20::{IERC20, IERC20CamelOnly}; + use starknet::ContractAddress; + + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + // `ERC20MetadataImpl` can keep the embed directive because the implementation + // will not change + #[abi(embed_v0)] + impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; + // Do not add the embed directive to these implementations because + // these will be customized + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) +} +``` + +The first thing to notice is that the contract imports the interfaces of the implementations that will be customized. +These will be used in the next code example. + +Next, the contract includes the [ERC20Component](/contracts-cairo/alpha/api/erc20#erc20component) implementations; however, `ERC20Impl` and `ERC20CamelOnlyImpl` are **not** embedded. +Instead, we want to expose our custom implementation of an interface. +The following example shows the pausable logic integrated into the ERC20 implementations: + +```rust +#[starknet::contract] +mod ERC20Pausable { + (...) + + // Custom ERC20 implementation + #[abi(embed_v0)] + impl CustomERC20Impl of IERC20 { + fn transfer( + ref self: ContractState, recipient: ContractAddress, amount: u256 + ) -> bool { + // Add the custom logic + self.pausable.assert_not_paused(); + // Add the original implementation method from `IERC20Impl` + self.erc20.transfer(recipient, amount) + } + + fn total_supply(self: @ContractState) -> u256 { + // This method's behavior does not change from the component + // implementation, but this method must still be defined. + // Simply add the original implementation method from `IERC20Impl` + self.erc20.total_supply() + } + + (...) + } + + // Custom ERC20CamelOnly implementation + #[abi(embed_v0)] + impl CustomERC20CamelOnlyImpl of IERC20CamelOnly { + fn totalSupply(self: @ContractState) -> u256 { + self.erc20.total_supply() + } + + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + self.erc20.balance_of(account) + } + + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + self.pausable.assert_not_paused(); + self.erc20.transfer_from(sender, recipient, amount) + } + } +} +``` + +Notice that in the `CustomERC20Impl`, the `transfer` method integrates `pausable.assert_not_paused` as well as `erc20.transfer` from `PausableImpl` and `ERC20Impl` respectively. +This is why the contract defined the `ERC20Impl` from the component in the previous example. + +Creating a custom implementation of an interface must define **all** methods from that interface. +This is true even if the behavior of a method does not change from the component implementation (as `total_supply` exemplifies in this example). + +### Accessing component storage + +There may be cases where the contract must read or write to an integrated component’s storage. +To do so, use the same syntax as calling an implementation method except replace the name of the method with the storage variable like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + initializable: InitializableComponent::Storage + } + + (...) + + fn write_to_comp_storage(ref self: ContractState) { + self.initializable.Initializable_initialized.write(true); + } + + fn read_from_comp_storage(self: @ContractState) -> bool { + self.initializable.Initializable_initialized.read() + } +} +``` + +## Security + +The maintainers of OpenZeppelin Contracts for Cairo are mainly concerned with the correctness and security of the code as published in the library. + +Customizing implementations and manipulating the component state may break some important assumptions and introduce vulnerabilities. +While we try to ensure the components remain secure in the face of a wide range of potential customizations, this is done in a best-effort manner. +Any and all customizations to the component logic should be carefully reviewed and checked against the source code of the component they are customizing so as to fully understand their impact and guarantee their security. diff --git a/docs/content/contracts-cairo/alpha/erc1155.mdx b/docs/content/contracts-cairo/alpha/erc1155.mdx new file mode 100644 index 00000000..45ed4c4a --- /dev/null +++ b/docs/content/contracts-cairo/alpha/erc1155.mdx @@ -0,0 +1,239 @@ +--- +title: ERC1155 +--- + +The ERC1155 multi-token standard is a specification for [fungibility-agnostic](https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens) token contracts. +The ERC1155 library implements an approximation of [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155) in Cairo for StarkNet. + +## Multi Token Standard + +The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This +is why its [balance_of](/contracts-cairo/alpha/api/erc1155#IERC1155-balance_of) function differs from ERC20’s and ERC777’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 ERC721 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 ERC721 [balance_of](/contracts-cairo/alpha/api/erc721#IERC721-balance_of) function refers to how many different tokens an account +has, not how many of each. On the other hand, in ERC1155 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 ERC1155 token contract can hold the entire system state, reducing deployment +costs and complexity. + +## Usage + +Using Contracts for Cairo, constructing an ERC1155 contract requires integrating both `ERC1155Component` and `SRC5Component`. +The contract should also set up the constructor to initialize the token’s URI and interface support. +Here’s an example of a basic contract: + +```rust +#[starknet::contract] +mod MyERC1155 { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc1155::{ERC1155Component, ERC1155HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC1155Component, storage: erc1155, event: ERC1155Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155 Mixin + #[abi(embed_v0)] + impl ERC1155MixinImpl = ERC1155Component::ERC1155MixinImpl; + impl ERC1155InternalImpl = ERC1155Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155: ERC1155Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155Event: ERC1155Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + token_uri: ByteArray, + recipient: ContractAddress, + token_ids: Span, + values: Span + ) { + self.erc1155.initializer(token_uri); + self + .erc1155 + .batch_mint_with_acceptance_check(recipient, token_ids, values, array![].span()); + } +} +``` + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC1155Component](/contracts-cairo/alpha/api/erc1155#ERC1155Component). +The interface includes the [IERC1155](/contracts-cairo/alpha/api/erc1155#IERC1155) standard interface and the optional [IERC1155MetadataURI](/contracts-cairo/alpha/api/erc1155#IERC1155MetadataURI) interface together with [ISRC5](/contracts-cairo/alpha/api/introspection#ISRC5). + +To support older token deployments, as mentioned in [Dual interfaces](./guides/interfaces-and-dispatchers#dual-interfaces), the component also includes implementations of the interface written in camelCase. + +```rust +#[starknet::interface] +pub trait ERC1155ABI { + // IERC1155 + fn balance_of(account: ContractAddress, token_id: u256) -> u256; + fn balance_of_batch( + accounts: Span, token_ids: Span + ) -> Span; + fn safe_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_id: u256, + value: u256, + data: Span + ); + fn safe_batch_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ); + fn is_approved_for_all( + owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn set_approval_for_all(operator: ContractAddress, approved: bool); + + // IERC1155MetadataURI + fn uri(token_id: u256) -> ByteArray; + + // ISRC5 + fn supports_interface(interface_id: felt252) -> bool; + + // IERC1155Camel + fn balanceOf(account: ContractAddress, tokenId: u256) -> u256; + fn balanceOfBatch( + accounts: Span, tokenIds: Span + ) -> Span; + fn safeTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + value: u256, + data: Span + ); + fn safeBatchTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenIds: Span, + values: Span, + data: Span + ); + fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; + fn setApprovalForAll(operator: ContractAddress, approved: bool); +} +``` + +## ERC1155 Compatibility + +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC1155 standard but some differences can still be found, such as: + +* The optional `data` argument in both `safe_transfer_from` and `safe_batch_transfer_from` is implemented as `Span`. +* `IERC1155Receiver` compliant contracts must implement SRC5 and register the `IERC1155Receiver` interface ID. +* `IERC1155Receiver::on_erc1155_received` must return that interface ID on success. + +## 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, [balance_of_batch](/contracts-cairo/alpha/api/erc1155#IERC1155-balance_of_batch) and [safe_batch_transfer_from](/contracts-cairo/alpha/api/erc1155#IERC1155-safe_batch_transfer_from), that make querying multiple balances and transferring multiple tokens simpler and less gas-intensive. We also have [safe_transfer_from](/contracts-cairo/alpha/api/erc1155#IERC1155-safe_transfer_from) for non-batch operations. + +In the spirit of the standard, we’ve also included batch operations in the non-standard functions, such as +[batch_mint_with_acceptance_check](/contracts-cairo/alpha/api/erc1155#ERC1155Component-batch_mint_with_acceptance_check). + + +While [safe_transfer_from](/contracts-cairo/alpha/api/erc1155#IERC1155-safe_transfer_from) and [safe_batch_transfer_from](/contracts-cairo/alpha/api/erc1155#IERC1155-safe_batch_transfer_from) prevent loss by checking the receiver can handle the +tokens, this yields execution to the receiver which can result in a [reentrant call](./security#reentrancy-guard). + + +## Receiving tokens + +In order to be sure a non-account contract can safely accept ERC1155 tokens, the contract must implement the `IERC1155Receiver` interface. +The recipient contract must also implement the [SRC5](./introspection#src5) interface which supports interface introspection. + +### IERC1155Receiver + +```rust +#[starknet::interface] +pub trait IERC1155Receiver { + fn on_erc1155_received( + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + value: u256, + data: Span + ) -> felt252; + fn on_erc1155_batch_received( + operator: ContractAddress, + from: ContractAddress, + token_ids: Span, + values: Span, + data: Span + ) -> felt252; +} +``` + +Implementing the `IERC1155Receiver` interface exposes the [on_erc1155_received](/contracts-cairo/alpha/api/erc1155#IERC1155Receiver-on_erc1155_received) and [on_erc1155_batch_received](/contracts-cairo/alpha/api/erc1155#IERC1155Receiver-on_erc1155_batch_received) methods. +When [safe_transfer_from](/contracts-cairo/alpha/api/erc1155#IERC1155-safe_transfer_from) and [safe_batch_transfer_from](/contracts-cairo/alpha/api/erc1155#IERC1155-safe_batch_transfer_from) are called, they invoke the recipient contract’s `on_erc1155_received` or `on_erc1155_batch_received` methods respectively which **must** return the [IERC1155Receiver interface ID](/contracts-cairo/alpha/api/erc1155#IERC1155Receiver). +Otherwise, the transaction will fail. + + +For information on how to calculate interface IDs, see [Computing the interface ID](./introspection#computing-the-interface-id). + + +### Creating a token receiver contract + +The Contracts for Cairo ERC1155ReceiverComponent already returns the correct interface ID for safe token transfers. +To integrate the `IERC1155Receiver` interface into a contract, simply include the ABI embed directive to the implementations and add the `initializer` in the contract’s constructor. +Here’s an example of a simple token receiver contract: + +```rust +#[starknet::contract] +mod MyTokenReceiver { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc1155::ERC1155ReceiverComponent; + use starknet::ContractAddress; + + component!(path: ERC1155ReceiverComponent, storage: erc1155_receiver, event: ERC1155ReceiverEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC1155Receiver Mixin + #[abi(embed_v0)] + impl ERC1155ReceiverMixinImpl = ERC1155ReceiverComponent::ERC1155ReceiverMixinImpl; + impl ERC1155ReceiverInternalImpl = ERC1155ReceiverComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc1155_receiver: ERC1155ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC1155ReceiverEvent: ERC1155ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc1155_receiver.initializer(); + } +} +``` diff --git a/docs/content/contracts-cairo/alpha/erc20.mdx b/docs/content/contracts-cairo/alpha/erc20.mdx new file mode 100644 index 00000000..4c0c5ba1 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/erc20.mdx @@ -0,0 +1,252 @@ +--- +title: ERC20 +--- + +The ERC20 token standard is a specification for [fungible tokens](https://docs.openzeppelin.com/contracts/4.x/tokens#different-kinds-of-tokens), a type of token where all the units are exactly equal to each other. +`token::erc20::ERC20Component` provides an approximation of [EIP-20](https://eips.ethereum.org/EIPS/eip-20) in Cairo for Starknet. + + +Prior to [Contracts v0.7.0](https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v0.7.0), ERC20 contracts store and read `decimals` from storage; however, this implementation returns a static `18`. +If upgrading an older ERC20 contract that has a decimals value other than `18`, the upgraded contract **must** use a custom `decimals` implementation. +See the [Customizing decimals](#customizing-decimals) guide. + + +## Usage + +Using Contracts for Cairo, constructing an ERC20 contract requires setting up the constructor and instantiating the token implementation. +Here’s what that looks like: + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + initial_supply: u256, + recipient: ContractAddress + ) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + } +} +``` + +`MyToken` integrates both the `ERC20Impl` and `ERC20MetadataImpl` with the embed directive which marks the implementations as external in the contract. +While the `ERC20MetadataImpl` is optional, it’s generally recommended to include it because the vast majority of ERC20 tokens provide the metadata methods. +The above example also includes the `ERC20InternalImpl` instance. +This allows the contract’s constructor to initialize the contract and create an initial supply of tokens. + + +For a more complete guide on ERC20 token mechanisms, see [Creating ERC20 Supply](./guides/erc20-supply). + + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC20Component](/contracts-cairo/alpha/api/erc20#ERC20Component). +The interface includes the [IERC20](/contracts-cairo/alpha/api/erc20#IERC20) standard interface as well as the optional [IERC20Metadata](/contracts-cairo/alpha/api/erc20#IERC20Metadata). + +To support older token deployments, as mentioned in [Dual interfaces](./guides/interfaces-and-dispatchers#dual-interfaces), the component also includes an implementation of the interface written in camelCase. + +```rust +#[starknet::interface] +pub trait ERC20ABI { + // IERC20 + fn total_supply() -> u256; + fn balance_of(account: ContractAddress) -> u256; + fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn decimals() -> u8; + + // IERC20Camel + fn totalSupply() -> u256; + fn balanceOf(account: ContractAddress) -> u256; + fn transferFrom( + sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} +``` + +## ERC20 compatibility + +Although Starknet is not EVM compatible, this component aims to be as close as possible to the ERC20 token standard. +Some notable differences, however, can still be found, such as: + +* The `ByteArray` type is used to represent strings in Cairo. +* The component offers a [dual interface](./guides/interfaces-and-dispatchers#dual-interfaces) which supports both snake_case and camelCase methods, as opposed to just camelCase in Solidity. +* `transfer`, `transfer_from` and `approve` will never return anything different from `true` because they will revert on any error. +* Function selectors are calculated differently between [Cairo](https://github.com/starkware-libs/cairo/blob/7dd34f6c57b7baf5cd5a30c15e00af39cb26f7e1/crates/cairo-lang-starknet/src/contract.rs#L39-L48) and [Solidity](https://solidity-by-example.org/function-selector/). + +## Customizing decimals + +Cairo, like Solidity, does not support [floating-point numbers](https://en.wikipedia.org//wiki/Floating-point_arithmetic). +To get around this limitation, ERC20 token contracts may offer a `decimals` field which communicates to outside interfaces (wallets, exchanges, etc.) how the token should be displayed. +For instance, suppose a token had a `decimals` value of `3` and the total token supply was `1234`. +An outside interface would display the token supply as `1.234`. +In the actual contract, however, the supply would still be the integer `1234`. +In other words, **the decimals field in no way changes the actual arithmetic** because all operations are still performed on integers. + +Most contracts use `18` decimals and this was even proposed to be compulsory (see the [EIP discussion](https://github.com/ethereum/EIPs/issues/724)). + +### The static approach (SRC-107) + +The Contracts for Cairo `ERC20` component leverages SRC-107 to allow for a static and configurable number of decimals. +To use the default `18` decimals, you can use the `DefaultConfig` implementation by just importing it: + +```rust +#[starknet::contract] +mod MyToken { + // Importing the DefaultConfig implementation would make decimals 18 by default. + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) +} +``` + +To customize this value, you can implement the ImmutableConfig trait locally in the contract. +The following example shows how to set the decimals to `6`: + +```rust +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) + + // Custom implementation of the ERC20Component ImmutableConfig. + impl ERC20ImmutableConfig of ERC20Component::ImmutableConfig { + const DECIMALS: u8 = 6; + } +} +``` + +### The storage approach + +For more complex scenarios, such as a factory deploying multiple tokens with differing values for decimals, a flexible solution might be appropriate. + + +Note that we are not using the MixinImpl or the DefaultConfig in this case, since we need to customize the IERC20Metadata implementation. + + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_interfaces::erc20 as interface; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + // The decimals value is stored locally + decimals: u8, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, decimals: u8, initial_supply: u256, recipient: ContractAddress, + ) { + // Call the internal function that writes decimals to storage + self._set_decimals(decimals); + + // Initialize ERC20 + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + } + + #[abi(embed_v0)] + impl ERC20CustomMetadataImpl of interface::IERC20Metadata { + fn name(self: @ContractState) -> ByteArray { + self.erc20.ERC20_name.read() + } + + fn symbol(self: @ContractState) -> ByteArray { + self.erc20.ERC20_symbol.read() + } + + fn decimals(self: @ContractState) -> u8 { + self.decimals.read() + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _set_decimals(ref self: ContractState, decimals: u8) { + self.decimals.write(decimals); + } + } +} +``` + +This contract expects a `decimals` argument in the constructor and uses an internal function to write the decimals to storage. +Note that the `decimals` state variable must be defined in the contract’s storage because this variable does not exist in the component offered by OpenZeppelin Contracts for Cairo. +It’s important to include a custom ERC20 metadata implementation and NOT use the Contracts for Cairo `ERC20MetadataImpl` in this specific case since the `decimals` method will always return `18`. diff --git a/docs/content/contracts-cairo/alpha/erc4626.mdx b/docs/content/contracts-cairo/alpha/erc4626.mdx new file mode 100644 index 00000000..bcacf612 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/erc4626.mdx @@ -0,0 +1,433 @@ +--- +title: ERC4626 +--- + +[ERC4626](https://eips.ethereum.org/EIPS/eip-4626) is an extension of [ERC20](./erc20) 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 component of ERC4626 which is designed to allow developers to easily re-configure the vault’s behavior, using traits and hooks, while +staying compliant. In this guide, we will discuss some security considerations that affect ERC4626. We will also discuss common customizations of the vault. + +## Security concern: Inflation attack + +### Visualizing the vault + +In exchange for the assets deposited into an ERC4626 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. + +![Exchange rates in linear scale](/erc4626-rate-linear.png) + +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. + +![Exchange rates in logarithmic scale](/erc4626-rate-loglog.png) + +In such a representation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. + +![More exchange rates in logarithmic scale](/erc4626-rate-loglogext.png) + +### 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 less than 1 share worth of tokens, you will receive 0 shares, effectively making 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. + +![Depositing assets](/erc4626-deposit.png) + +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%. + +![Minting shares](/erc4626-mint.png) + +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. + +![Inflation attack without protection](/erc4626-attack.png) + +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: + +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | Assets | Shares | Rate | +| --- | --- | --- | --- | +| initial | $0$ | $0$ | - | +| after attacker's deposit | $a_0$ | $a_0$ | $1$ | +| after attacker's donation | $a_0 + a_1$ | $a_0$ | $\frac{a_0}{a_1 + a_0}$ | + +This means a deposit of $u$ will give this number of shares: + +```math +\frac{u \times a_0}{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 + +```math +\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0} +``` + +Using $a_0 = 1$ and $a_1 = u$ is enough. So the attacker only needs $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 $\frac{u}{n}$, the user needs to suffer rounding of a similar fraction, which means the user must receive at most $n$ shares. This results in: + +```math +\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 $n$ times less powerful (in how much it is stealing) and costs $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 [YieldBox](https://github.com/boringcrypto/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 to perform an attack. + +Following the previous math definitions, we have: + +* $\delta$ the vault offset +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | Assets | Shares | Rate | +| --- | --- | --- | --- | +| initial | $1$ | $10^\delta$ | $10^\delta$ | +| after attacker's deposit | $1+a_0$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | +| after attacker's donation | $1+a_0+a_1$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | + +One important thing to note is that the attacker only owns a fraction $\frac{a_0}{1 + a_0}$ of the shares, so when doing the donation, he will only be able +to recover that fraction $\frac{a_1 * a_0}{1 + a_0}$ of the donation. The remaining $\frac{a_1}{1+a_0}$ are captured by the vault. + +```math +\mathit{loss} = \frac{a_1}{1+a_0} +``` + +When the user deposits $u$, he receives + +```math +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 + +```math +10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1 +``` + +```math +\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0} +``` + +```math +\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0} +``` + +```math +\iff 10^\delta \times u \le \mathit{loss} +``` + +* If the offset is 0, the attacker 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. + +![Inflation attack without offset=3](/erc4626-attack-3a.png) +$\delta = 3$, $a_0 = 1$, $a_1 = 10^5$ + +![Inflation attack without offset=3 and an attacker deposit that limits its losses](/erc4626-attack-3b.png) +$\delta = 3$, $a_0 = 100$, $a_1 = 10^5$ + +![Inflation attack without offset=6](/erc4626-attack-6.png) +$\delta = 6$, $a_0 = 1$, $a_1 = 10^5$ + +## Usage + +### Custom behavior: Adding fees to the vault + +In ERC4626 vaults, fees can be captured during deposit/mint and/or withdraw/redeem operations. It is essential to remain +compliant with the ERC4626 requirements regarding the preview functions. Fees are calculated through the [FeeConfigTrait](/contracts-cairo/alpha/api/erc20#ERC4626Component-FeeConfigTrait) +implementation. By default, the ERC4626 component charges no fees. If this is the desired behavior, you can use the default +[ERC4626DefaultNoFees](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20/extensions/erc4626/erc4626.cairo#L899) implementation. + + +Starting from v3.0.0, fees can be charged in either assets or shares. Prior versions only supported fees taken in assets. +See the updated [FeeConfigTrait](/contracts-cairo/alpha/api/erc20#ERC4626Component-FeeConfigTrait) and implementation examples in [ERC4626 mocks](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/test_common/src/mocks/erc4626.cairo). + + +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 `preview_deposit(100)`. +Similarly, `preview_mint` 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 the user receives. +Any fees should be added to the quote (in shares) performed by `preview_withdraw`. + +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 taken in assets on deposits/withdrawals and in shares on mints/redemptions +proportional to the deposited/withdrawn amount can be implemented: + +```rust +#[starknet::contract] +#[with_components(./erc20, ERC4626)] +pub mod ERC4626Fees { + use openzeppelin_interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; + use openzeppelin_token::erc20::extensions::erc4626::ERC4626Component::{Fee, FeeConfigTrait}; + use openzeppelin_token::erc20::extensions::erc4626::{ + DefaultConfig, ERC4626DefaultNoLimits, ERC4626SelfAssetsManagement, + }; + use openzeppelin_token::erc20::{DefaultConfig as ERC20DefaultConfig, ERC20HooksEmptyImpl}; + use openzeppelin_utils::math; + use openzeppelin_utils::math::Rounding; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + const _BASIS_POINT_SCALE: u256 = 10_000; + const FEE_TRANSFER_FAILED: felt252 = 0x1; + + // ERC4626 + #[abi(embed_v0)] + impl ERC4626ComponentImpl = ERC4626Component::ERC4626Impl; + // ERC4626MetadataImpl is a custom impl of IERC20Metadata + #[abi(embed_v0)] + impl ERC4626MetadataImpl = ERC4626Component::ERC4626MetadataImpl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + #[abi(embed_v0)] + impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + + #[storage] + pub struct Storage { + pub entry_fee_basis_point_value: u256, + pub entry_fee_recipient: ContractAddress, + pub exit_fee_basis_point_value: u256, + pub exit_fee_recipient: ContractAddress, + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + underlying_asset: ContractAddress, + initial_supply: u256, + recipient: ContractAddress, + entry_fee: u256, + entry_treasury: ContractAddress, + exit_fee: u256, + exit_treasury: ContractAddress, + ) { + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); + self.erc4626.initializer(underlying_asset); + + self.entry_fee_basis_point_value.write(entry_fee); + self.entry_fee_recipient.write(entry_treasury); + self.exit_fee_basis_point_value.write(exit_fee); + self.exit_fee_recipient.write(exit_treasury); + } + + /// Hooks + impl ERC4626HooksImpl of ERC4626Component::ERC4626HooksTrait { + fn after_deposit( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + if let Option::Some(fee) = fee { + let mut contract_state = self.get_contract_mut(); + let fee_recipient = contract_state.entry_fee_recipient.read(); + match fee { + Fee::Assets(fee) => { + let asset_address = contract_state.asset(); + let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; + assert( + asset_dispatcher.transfer(fee_recipient, fee), FEE_TRANSFER_FAILED, + ); + }, + Fee::Shares(fee) => contract_state.erc20.mint(fee_recipient, fee), + }; + } + } + + fn before_withdraw( + ref self: ERC4626Component::ComponentState, + caller: ContractAddress, + receiver: ContractAddress, + owner: ContractAddress, + assets: u256, + shares: u256, + fee: Option, + ) { + if let Option::Some(fee) = fee { + let mut contract_state = self.get_contract_mut(); + let fee_recipient = contract_state.exit_fee_recipient.read(); + match fee { + Fee::Assets(fee) => { + let asset_address = contract_state.asset(); + let asset_dispatcher = IERC20Dispatcher { contract_address: asset_address }; + assert( + asset_dispatcher.transfer(fee_recipient, fee), FEE_TRANSFER_FAILED, + ); + }, + Fee::Shares(fee) => { + if caller != owner { + contract_state.erc20._spend_allowance(owner, caller, fee); + } + contract_state.erc20._transfer(owner, fee_recipient, fee); + }, + }; + } + } + } + + /// Calculate fees + impl FeeConfigImpl of FeeConfigTrait { + fn calculate_deposit_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_total(assets, contract_state.entry_fee_basis_point_value.read()); + Option::Some(Fee::Assets(fee)) + } + + fn calculate_mint_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_raw(shares, contract_state.entry_fee_basis_point_value.read()); + Option::Some(Fee::Shares(fee)) + } + + fn calculate_withdraw_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_raw(assets, contract_state.exit_fee_basis_point_value.read()); + Option::Some(Fee::Assets(fee)) + } + + fn calculate_redeem_fee( + self: @ERC4626Component::ComponentState, assets: u256, shares: u256, + ) -> Option { + let contract_state = self.get_contract(); + let fee = fee_on_total(shares, contract_state.exit_fee_basis_point_value.read()); + Option::Some(Fee::Shares(fee)) + } + } + + /// Calculates the fees that should be added to an amount `assets` that does not already + /// include fees. + /// Used in IERC4626::mint and IERC4626::withdraw operations. + fn fee_on_raw( + assets: u256, + fee_basis_points: u256, + ) -> u256 { + math::u256_mul_div(assets, fee_basis_points, _BASIS_POINT_SCALE, Rounding::Ceil) + } + + /// Calculates the fee part of an amount `assets` that already includes fees. + /// Used in IERC4626::deposit and IERC4626::redeem operations. + fn fee_on_total( + assets: u256, + fee_basis_points: u256, + ) -> u256 { + math::u256_mul_div( + assets, fee_basis_points, fee_basis_points + _BASIS_POINT_SCALE, Rounding::Ceil, + ) + } +} +``` + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC4626Component](/contracts-cairo/alpha/api/erc20#ERC4626Component). +The full interface includes the [IERC4626](/contracts-cairo/alpha/api/erc20#IERC4626), [IERC20](/contracts-cairo/alpha/api/erc20#IERC20), and [IERC20Metadata](/contracts-cairo/alpha/api/erc20#IERC20Metadata) interfaces. +Note that implementing the IERC20Metadata interface is a requirement of IERC4626. + +```rust +#[starknet::interface] +pub trait ERC4626ABI { + // IERC4626 + fn asset() -> ContractAddress; + fn total_assets() -> u256; + fn convert_to_shares(assets: u256) -> u256; + fn convert_to_assets(shares: u256) -> u256; + fn max_deposit(receiver: ContractAddress) -> u256; + fn preview_deposit(assets: u256) -> u256; + fn deposit(assets: u256, receiver: ContractAddress) -> u256; + fn max_mint(receiver: ContractAddress) -> u256; + fn preview_mint(shares: u256) -> u256; + fn mint(shares: u256, receiver: ContractAddress) -> u256; + fn max_withdraw(owner: ContractAddress) -> u256; + fn preview_withdraw(assets: u256) -> u256; + fn withdraw( + assets: u256, receiver: ContractAddress, owner: ContractAddress, + ) -> u256; + fn max_redeem(owner: ContractAddress) -> u256; + fn preview_redeem(shares: u256) -> u256; + fn redeem( + shares: u256, receiver: ContractAddress, owner: ContractAddress, + ) -> u256; + + // IERC20 + fn total_supply() -> u256; + fn balance_of(account: ContractAddress) -> u256; + fn allowance(owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; + fn approve(spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn decimals() -> u8; + + // IERC20CamelOnly + fn totalSupply() -> u256; + fn balanceOf(account: ContractAddress) -> u256; + fn transferFrom( + sender: ContractAddress, recipient: ContractAddress, amount: u256, + ) -> bool; +} +``` diff --git a/docs/content/contracts-cairo/alpha/erc721.mdx b/docs/content/contracts-cairo/alpha/erc721.mdx new file mode 100644 index 00000000..a2310b39 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/erc721.mdx @@ -0,0 +1,213 @@ +--- +title: ERC721 +--- + +The ERC721 token standard is a specification for [non-fungible tokens](https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens), or more colloquially: NFTs. +`token::erc721::ERC721Component` provides an approximation of [EIP-721](https://eips.ethereum.org/EIPS/eip-721) in Cairo for Starknet. + +## Usage + +Using Contracts for Cairo, constructing an ERC721 contract requires integrating both `ERC721Component` and `SRC5Component`. +The contract should also set up the constructor to initialize the token’s name, symbol, and interface support. +Here’s an example of a basic contract: + +```rust +#[starknet::contract] +mod MyNFT { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721 Mixin + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721: ERC721Component::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + recipient: ContractAddress + ) { + let name = "MyNFT"; + let symbol = "NFT"; + let base_uri = "https://api.example.com/v1/"; + let token_id = 1; + + self.erc721.initializer(name, symbol, base_uri); + self.erc721.mint(recipient, token_id); + } +} +``` + +## Interface + +The following interface represents the full ABI of the Contracts for Cairo [ERC721Component](/contracts-cairo/alpha/api/erc721#ERC721Component). +The interface includes the [IERC721](/contracts-cairo/alpha/api/erc721#IERC721) standard interface and the optional [IERC721Metadata](/contracts-cairo/alpha/api/erc721#IERC721Metadata) interface. + +To support older token deployments, as mentioned in [Dual interfaces](./guides/interfaces-and-dispatchers#dual-interfaces), the component also includes implementations of the interface written in camelCase. + +```rust +#[starknet::interface] +pub trait ERC721ABI { + // IERC721 + fn balance_of(account: ContractAddress) -> u256; + fn owner_of(token_id: u256) -> ContractAddress; + fn safe_transfer_from( + from: ContractAddress, + to: ContractAddress, + token_id: u256, + data: Span + ); + fn transfer_from(from: ContractAddress, to: ContractAddress, token_id: u256); + fn approve(to: ContractAddress, token_id: u256); + fn set_approval_for_all(operator: ContractAddress, approved: bool); + fn get_approved(token_id: u256) -> ContractAddress; + fn is_approved_for_all(owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721Metadata + fn name() -> ByteArray; + fn symbol() -> ByteArray; + fn token_uri(token_id: u256) -> ByteArray; + + // IERC721CamelOnly + fn balanceOf(account: ContractAddress) -> u256; + fn ownerOf(tokenId: u256) -> ContractAddress; + fn safeTransferFrom( + from: ContractAddress, + to: ContractAddress, + tokenId: u256, + data: Span + ); + fn transferFrom(from: ContractAddress, to: ContractAddress, tokenId: u256); + fn setApprovalForAll(operator: ContractAddress, approved: bool); + fn getApproved(tokenId: u256) -> ContractAddress; + fn isApprovedForAll(owner: ContractAddress, operator: ContractAddress) -> bool; + + // IERC721MetadataCamelOnly + fn tokenURI(tokenId: u256) -> ByteArray; +} +``` + +## ERC721 compatibility + +Although Starknet is not EVM compatible, this implementation aims to be as close as possible to the ERC721 standard. +This implementation does, however, include a few notable differences such as: + +* ``interface_id``s are hardcoded and initialized by the constructor. +The hardcoded values derive from Starknet’s selector calculations. +See the [Introspection](./introspection) docs. +* `safe_transfer_from` can only be expressed as a single function in Cairo as opposed to the two functions declared in EIP721, because function overloading is currently not possible in Cairo. +The difference between both functions consists of accepting `data` as an argument. +`safe_transfer_from` by default accepts the `data` argument which is interpreted as `Span`. +If `data` is not used, simply pass an empty array. +* ERC721 utilizes [SRC5](./introspection#src5) to declare and query interface support on Starknet as opposed to Ethereum’s [EIP165](https://eips.ethereum.org/EIPS/eip-165). +The design for `SRC5` is similar to OpenZeppelin’s [ERC165Storage](https://docs.openzeppelin.com/contracts/4.xapi/utils#ERC165Storage). +* `IERC721Receiver` compliant contracts return a hardcoded interface ID according to Starknet selectors (as opposed to selector calculation in Solidity). + +## Token transfers + +This library includes [transfer_from](/contracts-cairo/alpha/api/erc721#IERC721-transfer_from) and [safe_transfer_from](/contracts-cairo/alpha/api/erc721#IERC721-safe_transfer_from) to transfer NFTs. +If using `transfer_from`, **the caller is responsible to confirm that the recipient is capable of receiving NFTs or else they may be permanently lost.** +The `safe_transfer_from` method mitigates this risk by querying the recipient contract’s interface support. + + +Usage of `safe_transfer_from` prevents loss, though the caller must understand this adds an external call which potentially creates a reentrancy vulnerability. + + +## Receiving tokens + +In order to be sure a non-account contract can safely accept ERC721 tokens, said contract must implement the `IERC721Receiver` interface. +The recipient contract must also implement the [SRC5](./introspection#src5) interface which, as described earlier, supports interface introspection. + +### IERC721Receiver + +```rust +#[starknet::interface] +pub trait IERC721Receiver { + fn on_erc721_received( + operator: ContractAddress, + from: ContractAddress, + token_id: u256, + data: Span + ) -> felt252; +} +``` + +Implementing the `IERC721Receiver` interface exposes the [on_erc721_received](/contracts-cairo/alpha/api/erc721#IERC721Receiver-on_erc721_received) method. +When safe methods such as [safe_transfer_from](/contracts-cairo/alpha/api/erc721#IERC721-safe_transfer_from) and [safe_mint](/contracts-cairo/alpha/api/erc721#ERC721-safe_mint) are called, they invoke the recipient contract’s `on_erc721_received` method which **must** return the [IERC721Receiver interface ID](/contracts-cairo/alpha/api/erc721#IERC721Receiver). +Otherwise, the transaction will fail. + + +For information on how to calculate interface IDs, see [Computing the interface ID](./introspection#computing-the-interface-id). + + +### Creating a token receiver contract + +The Contracts for Cairo `IERC721ReceiverImpl` already returns the correct interface ID for safe token transfers. +To integrate the `IERC721Receiver` interface into a contract, simply include the ABI embed directive to the implementation and add the `initializer` in the contract’s constructor. +Here’s an example of a simple token receiver contract: + +```rust +#[starknet::contract] +mod MyTokenReceiver { + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::ERC721ReceiverComponent; + use starknet::ContractAddress; + + component!(path: ERC721ReceiverComponent, storage: erc721_receiver, event: ERC721ReceiverEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // ERC721Receiver Mixin + #[abi(embed_v0)] + impl ERC721ReceiverMixinImpl = ERC721ReceiverComponent::ERC721ReceiverMixinImpl; + impl ERC721ReceiverInternalImpl = ERC721ReceiverComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc721_receiver: ERC721ReceiverComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721ReceiverEvent: ERC721ReceiverComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721_receiver.initializer(); + } +} +``` + +## Storing ERC721 URIs + +Token URIs were previously stored as single field elements prior to Cairo v0.2.5. +ERC721Component now stores only the base URI as a `ByteArray` and the full token URI is returned as the `ByteArray` concatenation of the base URI and the token ID through the [token_uri](/contracts-cairo/alpha/api/erc721#IERC721Metadata-token_uri) method. +This design mirrors OpenZeppelin’s default [Solidity implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/932fddf69a699a9a80fd2396fd1a2ab91cdda123/contracts/token/ERC721/ERC721.sol#L85-L93) for ERC721. diff --git a/docs/content/contracts-cairo/alpha/finance.mdx b/docs/content/contracts-cairo/alpha/finance.mdx new file mode 100644 index 00000000..124efeb6 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/finance.mdx @@ -0,0 +1,216 @@ +--- +title: Finance +--- + +This module includes primitives for financial systems. + +## Vesting component + +The [VestingComponent](/contracts-cairo/alpha/api/finance#VestingComponent) manages the gradual release of ERC-20 tokens to a designated beneficiary based on a predefined vesting schedule. +The implementing contract must implement the [OwnableComponent](/contracts-cairo/alpha/api/access#OwnableComponent), where the contract owner is regarded as the vesting beneficiary. +This structure allows ownership rights of both the contract and the vested tokens to be assigned and transferred. + + +Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning of the vesting period. +As a result, if the vesting has already started, a portion of the newly transferred tokens may become immediately releasable. + + + +By setting the duration to 0, it’s possible to configure this contract to behave like an asset timelock that holds tokens +for a beneficiary until a specified date. + + +### Vesting schedule + +The [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) trait defines the logic for calculating the vested amount based on a given timestamp. This +logic is not part of the [VestingComponent](/contracts-cairo/alpha/api/finance#VestingComponent), so any contract implementing the [VestingComponent](/contracts-cairo/alpha/api/finance#VestingComponent) must provide its own +implementation of the [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) trait. + + +There’s a ready-made implementation of the [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) trait available named [LinearVestingSchedule](/contracts-cairo/alpha/api/finance#LinearVestingSchedule). +It incorporates a cliff period by returning 0 vested amount until the cliff ends. After the cliff, the vested amount +is calculated as directly proportional to the time elapsed since the beginning of the vesting schedule. + + +### Usage + +The contract must integrate [VestingComponent](/contracts-cairo/alpha/api/finance#VestingComponent) and [OwnableComponent](/contracts-cairo/alpha/api/access#OwnableComponent) as dependencies. The contract’s constructor +should initialize both components. Core vesting parameters, such as `beneficiary`, `start`, `duration` +and `cliff_duration`, are passed as arguments to the constructor and set at the time of deployment. + +The implementing contract must provide an implementation of the [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) trait. This can be achieved either by importing +a ready-made [LinearVestingSchedule](/contracts-cairo/alpha/api/finance#LinearVestingSchedule) implementation or by defining a custom one. + +Here’s an example of a simple vesting wallet contract with a [LinearVestingSchedule](/contracts-cairo/alpha/api/finance#LinearVestingSchedule), where the vested amount +is calculated as being directly proportional to the time elapsed since the start of the vesting period. + +```rust +#[starknet::contract] +mod LinearVestingWallet { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_finance::vesting::{VestingComponent, LinearVestingSchedule}; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl VestingImpl = VestingComponent::VestingImpl; + impl VestingInternalImpl = VestingComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + vesting: VestingComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + VestingEvent: VestingComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff_duration: u64 + ) { + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff_duration); + } +} +``` + +A vesting schedule will often follow a custom formula. In such cases, the [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) trait is useful. +To support a custom vesting schedule, the contract must provide an implementation of the +[calculate_vested_amount](/contracts-cairo/alpha/api/finance#VestingComponent-calculate_vested_amount) function based on the desired formula. + + +When using a custom [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) implementation, the [LinearVestingSchedule](/contracts-cairo/alpha/api/finance#LinearVestingSchedule) must be excluded from the imports. + + + +If there are additional parameters required for calculations, which are stored in the contract’s storage, you can access them using `self.get_contract()`. + + +Here’s an example of a vesting wallet contract with a custom [VestingSchedule](/contracts-cairo/alpha/api/finance#VestingComponent-Vesting-Schedule) implementation, where tokens +are vested in a number of steps. + +```rust +#[starknet::contract] +mod StepsVestingWallet { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_finance::vesting::VestingComponent::VestingScheduleTrait; + use openzeppelin_finance::vesting::VestingComponent; + use starknet::ContractAddress; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: VestingComponent, storage: vesting, event: VestingEvent); + + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + #[abi(embed_v0)] + impl VestingImpl = VestingComponent::VestingImpl; + impl VestingInternalImpl = VestingComponent::InternalImpl; + + #[storage] + struct Storage { + total_steps: u64, + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + vesting: VestingComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + VestingEvent: VestingComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + total_steps: u64, + beneficiary: ContractAddress, + start: u64, + duration: u64, + cliff: u64, + ) { + self.total_steps.write(total_steps); + self.ownable.initializer(beneficiary); + self.vesting.initializer(start, duration, cliff); + } + + impl VestingSchedule of VestingScheduleTrait { + fn calculate_vested_amount( + self: @VestingComponent::ComponentState, + token: ContractAddress, + total_allocation: u256, + timestamp: u64, + start: u64, + duration: u64, + cliff: u64, + ) -> u256 { + if timestamp < cliff { + 0 + } else if timestamp >= start + duration { + total_allocation + } else { + let total_steps = self.get_contract().total_steps.read(); + let vested_per_step = total_allocation / total_steps.into(); + let step_duration = duration / total_steps; + let current_step = (timestamp - start) / step_duration; + let vested_amount = vested_per_step * current_step.into(); + vested_amount + } + } + } +} +``` + +### Interface + +Here is the full interface of a standard contract implementing the vesting functionality: + +```rust +#[starknet::interface] +pub trait VestingABI { + // IVesting + fn start(self: @TState) -> u64; + fn cliff(self: @TState) -> u64; + fn duration(self: @TState) -> u64; + fn end(self: @TState) -> u64; + fn released(self: @TState, token: ContractAddress) -> u256; + fn releasable(self: @TState, token: ContractAddress) -> u256; + fn vested_amount(self: @TState, token: ContractAddress, timestamp: u64) -> u256; + fn release(ref self: TState, token: ContractAddress) -> u256; + + // IOwnable + fn owner(self: @TState) -> ContractAddress; + fn transfer_ownership(ref self: TState, new_owner: ContractAddress); + fn renounce_ownership(ref self: TState); + + // IOwnableCamelOnly + fn transferOwnership(ref self: TState, newOwner: ContractAddress); + fn renounceOwnership(ref self: TState); +} +``` diff --git a/docs/content/contracts-cairo/alpha/governance/governor.mdx b/docs/content/contracts-cairo/alpha/governance/governor.mdx new file mode 100644 index 00000000..cfad3946 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/governance/governor.mdx @@ -0,0 +1,443 @@ +--- +title: Governor +--- + +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”. In +OpenZeppelin Contracts for Cairo, we set out to build a modular system of Governor components where different +requirements can be accommodated by implementing specific traits. You will find the most common requirements out of the box, +but writing additional ones is simple, and we will be adding new features as requested by the community in future releases. + +## Usage and setup + +### Token + +The voting power of each account in our governance setup will be determined by an ERC20 or an ERC721 token. The token has +to implement the [VotesComponent](../api/governance#VotesComponent) 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. + +If your project already has a live token that does not include Votes and is not upgradeable, you can wrap it in a +governance token by using a wrapper. This will allow token holders to participate in governance by wrapping their tokens 1-to-1. + + +The library currently does not include a wrapper for tokens, but it will be added in a future release. + + + +Currently, the clock mode is fixed to block timestamps, since the Votes component uses the block timestamp to track +checkpoints. We plan to add support for more flexible clock modes in Votes in a future release, allowing to use, for example, +block numbers instead. + + +### Governor + +We will initially build a Governor without a timelock. The core logic is given by the [GovernorComponent](../api/governance#GovernorComponent), 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) the execution mechanism that should be used. + +Each of these aspects is customizable by writing your own extensions, +or more easily choosing one from the library. + +***For 1)*** we will use the GovernorVotes extension, which hooks to an [IVotes](../api/governance#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 the address of the token to be passed as an argument to the initializer. + +***For 2)*** we will use GovernorVotesQuorumFraction. This works together with the [IVotes](../api/governance#IVotes) instance to define the quorum as a +percentage of the total supply at the block when a proposal’s voting power is retrieved. This requires an initializer +parameter to set the percentage besides the votes token address. Most Governors nowadays use 4%. Since the quorum denominator +is 1000 for precision, we initialize the module with a numerator of 40, resulting in a 4% quorum (40/1000 = 0.04 or 4%). + +***For 3)*** we will use GovernorCountingSimple, an extension that offers 3 options to voters: For, Against, and Abstain, +and where only For and Abstain votes are counted towards quorum. + +***For 4)*** we will use GovernorCoreExecution, an extension that allows proposal execution directly through the governor. + + +Another option is GovernorTimelockExecution. An example can be found in the next section. + + +Besides these, we also need an implementation for the GovernorSettingsTrait defining the voting delay, voting period, +and proposal threshold. While we can use the GovernorSettings extension which allows to set these parameters by the +governor itself, we will implement the trait locally in the contract and set the voting delay, voting period, +and proposal threshold as constant values. + +__voting_delay__: How long after a proposal is created should voting power be fixed. A large voting delay gives +users time to unstake tokens if necessary. + +__voting_period__: How long does a proposal remain open to votes. + + +These parameters are specified in the unit defined in the token’s clock, which is for now always timestamps. + + +__proposal_threshold__: This restricts proposal creation to accounts who have enough voting power. + +An implementation of `GovernorComponent::ImmutableConfig` is also required. For the example below, we have used +the `DefaultConfig`. Check the immutable-config guide for more details. + +The last missing step is to add an `SNIP12Metadata` implementation used to retrieve the name and version of the governor. + +```rust +#[starknet::contract] +mod MyGovernor { + use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorVotesQuorumFractionComponent::InternalTrait; + use openzeppelin_governance::governor::extensions::{ + GovernorVotesQuorumFractionComponent, GovernorCountingSimpleComponent, + GovernorCoreExecutionComponent, + }; + use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + pub const VOTING_DELAY: u64 = 86400; // 1 day + pub const VOTING_PERIOD: u64 = 604800; // 1 week + pub const PROPOSAL_THRESHOLD: u256 = 10; + pub const QUORUM_NUMERATOR: u256 = 40; // 4% + + component!(path: GovernorComponent, storage: governor, event: GovernorEvent); + component!( + path: GovernorVotesQuorumFractionComponent, + storage: governor_votes, + event: GovernorVotesEvent + ); + component!( + path: GovernorCountingSimpleComponent, + storage: governor_counting_simple, + event: GovernorCountingSimpleEvent + ); + component!( + path: GovernorCoreExecutionComponent, + storage: governor_core_execution, + event: GovernorCoreExecutionEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Governor + #[abi(embed_v0)] + impl GovernorImpl = GovernorComponent::GovernorImpl; + + // Extensions external + #[abi(embed_v0)] + impl QuorumFractionImpl = + GovernorVotesQuorumFractionComponent::QuorumFractionImpl; + + // Extensions internal + impl GovernorQuorumImpl = GovernorVotesQuorumFractionComponent::GovernorQuorum; + impl GovernorVotesImpl = GovernorVotesQuorumFractionComponent::GovernorVotes; + impl GovernorCountingSimpleImpl = + GovernorCountingSimpleComponent::GovernorCounting; + impl GovernorCoreExecutionImpl = + GovernorCoreExecutionComponent::GovernorExecution; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub governor: GovernorComponent::Storage, + #[substorage(v0)] + pub governor_votes: GovernorVotesQuorumFractionComponent::Storage, + #[substorage(v0)] + pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, + #[substorage(v0)] + pub governor_core_execution: GovernorCoreExecutionComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + GovernorEvent: GovernorComponent::Event, + #[flat] + GovernorVotesEvent: GovernorVotesQuorumFractionComponent::Event, + #[flat] + GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, + #[flat] + GovernorCoreExecutionEvent: GovernorCoreExecutionComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, votes_token: ContractAddress) { + self.governor.initializer(); + self.governor_votes.initializer(votes_token, QUORUM_NUMERATOR); + } + + // + // SNIP12 Metadata + // + + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // + // Locally implemented extensions + // + + pub impl GovernorSettings of GovernorComponent::GovernorSettingsTrait { + /// See `GovernorComponent::GovernorSettingsTrait::voting_delay`. + fn voting_delay(self: @GovernorComponent::ComponentState) -> u64 { + VOTING_DELAY + } + + /// See `GovernorComponent::GovernorSettingsTrait::voting_period`. + fn voting_period(self: @GovernorComponent::ComponentState) -> u64 { + VOTING_PERIOD + } + + /// See `GovernorComponent::GovernorSettingsTrait::proposal_threshold`. + fn proposal_threshold(self: @GovernorComponent::ComponentState) -> u256 { + PROPOSAL_THRESHOLD + } + } +} +``` + +### 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](#timelock) in combination with the +GovernorTimelockExecution extension. + + +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. + + +TimelockController uses an [AccessControl](../access#role-based-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 must be granted, +and it MUST be the only proposer (and canceller) 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 is in charge of canceling operations: the Governor instance must be granted this role, +and it MUST be the only canceller in the system. + +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. + +The following example uses the GovernorTimelockExecution extension, together with GovernorSettings, and uses a +fixed quorum value instead of a percentage: + +```rust +#[starknet::contract] +pub mod MyTimelockedGovernor { + use openzeppelin_governance::governor::GovernorComponent::InternalTrait as GovernorInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorSettingsComponent::InternalTrait as GovernorSettingsInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorTimelockExecutionComponent::InternalTrait as GovernorTimelockExecutionInternalTrait; + use openzeppelin_governance::governor::extensions::GovernorVotesComponent::InternalTrait as GovernorVotesInternalTrait; + use openzeppelin_governance::governor::extensions::{ + GovernorVotesComponent, GovernorSettingsComponent, GovernorCountingSimpleComponent, + GovernorTimelockExecutionComponent + }; + use openzeppelin_governance::governor::{GovernorComponent, DefaultConfig}; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + pub const VOTING_DELAY: u64 = 86400; // 1 day + pub const VOTING_PERIOD: u64 = 604800; // 1 week + pub const PROPOSAL_THRESHOLD: u256 = 10; + pub const QUORUM: u256 = 100_000_000; + + component!(path: GovernorComponent, storage: governor, event: GovernorEvent); + component!(path: GovernorVotesComponent, storage: governor_votes, event: GovernorVotesEvent); + component!( + path: GovernorSettingsComponent, storage: governor_settings, event: GovernorSettingsEvent + ); + component!( + path: GovernorCountingSimpleComponent, + storage: governor_counting_simple, + event: GovernorCountingSimpleEvent + ); + component!( + path: GovernorTimelockExecutionComponent, + storage: governor_timelock_execution, + event: GovernorTimelockExecutionEvent + ); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Governor + #[abi(embed_v0)] + impl GovernorImpl = GovernorComponent::GovernorImpl; + + // Extensions external + #[abi(embed_v0)] + impl VotesTokenImpl = GovernorVotesComponent::VotesTokenImpl; + #[abi(embed_v0)] + impl GovernorSettingsAdminImpl = + GovernorSettingsComponent::GovernorSettingsAdminImpl; + #[abi(embed_v0)] + impl TimelockedImpl = + GovernorTimelockExecutionComponent::TimelockedImpl; + + // Extensions internal + impl GovernorVotesImpl = GovernorVotesComponent::GovernorVotes; + impl GovernorSettingsImpl = GovernorSettingsComponent::GovernorSettings; + impl GovernorCountingSimpleImpl = + GovernorCountingSimpleComponent::GovernorCounting; + impl GovernorTimelockExecutionImpl = + GovernorTimelockExecutionComponent::GovernorExecution; + + // SRC5 + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub governor: GovernorComponent::Storage, + #[substorage(v0)] + pub governor_votes: GovernorVotesComponent::Storage, + #[substorage(v0)] + pub governor_settings: GovernorSettingsComponent::Storage, + #[substorage(v0)] + pub governor_counting_simple: GovernorCountingSimpleComponent::Storage, + #[substorage(v0)] + pub governor_timelock_execution: GovernorTimelockExecutionComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + GovernorEvent: GovernorComponent::Event, + #[flat] + GovernorVotesEvent: GovernorVotesComponent::Event, + #[flat] + GovernorSettingsEvent: GovernorSettingsComponent::Event, + #[flat] + GovernorCountingSimpleEvent: GovernorCountingSimpleComponent::Event, + #[flat] + GovernorTimelockExecutionEvent: GovernorTimelockExecutionComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, votes_token: ContractAddress, timelock_controller: ContractAddress + ) { + self.governor.initializer(); + self.governor_votes.initializer(votes_token); + self.governor_settings.initializer(VOTING_DELAY, VOTING_PERIOD, PROPOSAL_THRESHOLD); + self.governor_timelock_execution.initializer(timelock_controller); + } + + // + // SNIP12 Metadata + // + + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // + // Locally implemented extensions + // + + impl GovernorQuorum of GovernorComponent::GovernorQuorumTrait { + /// See `GovernorComponent::GovernorQuorumTrait::quorum`. + fn quorum(self: @GovernorComponent::ComponentState, timepoint: u64) -> u256 { + QUORUM + } + } +} +``` + +## Interface + +This is the full interface of the `Governor` implementation: +```rust +#[starknet::interface] +pub trait IGovernor { + fn name(self: @TState) -> felt252; + fn version(self: @TState) -> felt252; + fn COUNTING_MODE(self: @TState) -> ByteArray; + fn hash_proposal(self: @TState, calls: Span, description_hash: felt252) -> felt252; + fn state(self: @TState, proposal_id: felt252) -> ProposalState; + fn proposal_threshold(self: @TState) -> u256; + fn proposal_snapshot(self: @TState, proposal_id: felt252) -> u64; + fn proposal_deadline(self: @TState, proposal_id: felt252) -> u64; + fn proposal_proposer(self: @TState, proposal_id: felt252) -> ContractAddress; + fn proposal_eta(self: @TState, proposal_id: felt252) -> u64; + fn proposal_needs_queuing(self: @TState, proposal_id: felt252) -> bool; + fn voting_delay(self: @TState) -> u64; + fn voting_period(self: @TState) -> u64; + fn quorum(self: @TState, timepoint: u64) -> u256; + fn get_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; + fn get_votes_with_params( + self: @TState, account: ContractAddress, timepoint: u64, params: Span + ) -> u256; + fn has_voted(self: @TState, proposal_id: felt252, account: ContractAddress) -> bool; + fn propose(ref self: TState, calls: Span, description: ByteArray) -> felt252; + fn queue(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn execute(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn cancel(ref self: TState, calls: Span, description_hash: felt252) -> felt252; + fn cast_vote(ref self: TState, proposal_id: felt252, support: u8) -> u256; + fn cast_vote_with_reason( + ref self: TState, proposal_id: felt252, support: u8, reason: ByteArray + ) -> u256; + fn cast_vote_with_reason_and_params( + ref self: TState, + proposal_id: felt252, + support: u8, + reason: ByteArray, + params: Span + ) -> u256; + fn cast_vote_by_sig( + ref self: TState, + proposal_id: felt252, + support: u8, + voter: ContractAddress, + signature: Span + ) -> u256; + fn cast_vote_with_reason_and_params_by_sig( + ref self: TState, + proposal_id: felt252, + support: u8, + voter: ContractAddress, + reason: ByteArray, + params: Span, + signature: Span + ) -> u256; + fn nonces(self: @TState, voter: ContractAddress) -> felt252; + fn relay(ref self: TState, call: Call); +} +``` diff --git a/docs/content/contracts-cairo/alpha/governance/multisig.mdx b/docs/content/contracts-cairo/alpha/governance/multisig.mdx new file mode 100644 index 00000000..c98fdf9d --- /dev/null +++ b/docs/content/contracts-cairo/alpha/governance/multisig.mdx @@ -0,0 +1,150 @@ +--- +title: Multisig +--- + +The Multisig component implements a multi-signature mechanism to enhance the security and +governance of smart contract transactions. It ensures that no single signer can unilaterally +execute critical actions, requiring multiple registered signers to approve and collectively +execute transactions. + +This component is designed to secure operations such as fund management or protocol governance, +where collective decision-making is essential. The Multisig Component is self-administered, +meaning that changes to signers or quorum must be approved through the multisig process itself. + +## Key features + +* **Multi-Signature Security**: transactions must be approved by multiple signers, ensuring +distributed governance. +* **Quorum Enforcement**: defines the minimum number of approvals required for transaction execution. +* **Self-Administration**: all modifications to the component (e.g., adding or removing signers) +must pass through the multisig process. +* **Event Logging**: provides comprehensive event logging for transparency and auditability. + +## Signer management + +The Multisig component introduces the concept of signers and quorum: + +* **Signers**: only registered signers can submit, confirm, revoke, or execute transactions. The Multisig +Component supports adding, removing, or replacing signers. +* **Quorum**: the quorum defines the minimum number of confirmations required to approve a transaction. + + +To prevent unauthorized modifications, only the contract itself can add, remove, or replace signers or change the quorum. +This ensures that all modifications pass through the multisig approval process. + + +## Transaction lifecycle + +The state of a transaction is represented by the `TransactionState` enum and can be retrieved +by calling the `get_transaction_state` function with the transaction’s identifier. + +The identifier of a multisig transaction is a `felt252` value, computed as the Pedersen hash +of the transaction’s calls and salt. It can be computed by invoking the implementing contract’s +`hash_transaction` method for single-call transactions or `hash_transaction_batch` for multi-call +transactions. Submitting a transaction with identical calls and the same salt value a second time +will fail, as transaction identifiers must be unique. To resolve this, use a different salt value +to generate a unique identifier. + +A transaction in the Multisig component follows a specific lifecycle: + +`NotFound` → `Pending` → `Confirmed` → `Executed` + +* **NotFound**: the transaction does not exist. +* **Pending**: the transaction exists but has not reached the required confirmations. +* **Confirmed**: the transaction has reached the quorum but has not yet been executed. +* **Executed**: the transaction has been successfully executed. + +## Usage + +Integrating the Multisig functionality into a contract requires implementing [MultisigComponent](../api/governance#MultisigComponent). +The contract’s constructor should initialize the component with a quorum value and a list of initial signers. + +Here’s an example of a simple wallet contract featuring the Multisig functionality: + +```rust +#[starknet::contract] +mod MultisigWallet { + use openzeppelin_governance::multisig::MultisigComponent; + use starknet::ContractAddress; + + component!(path: MultisigComponent, storage: multisig, event: MultisigEvent); + + #[abi(embed_v0)] + impl MultisigImpl = MultisigComponent::MultisigImpl; + impl MultisigInternalImpl = MultisigComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + multisig: MultisigComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + MultisigEvent: MultisigComponent::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, quorum: u32, signers: Span) { + self.multisig.initializer(quorum, signers); + } +} +``` + +## Interface + +This is the interface of a contract implementing the [MultisigComponent](../api/governance#MultisigComponent): + +```rust +#[starknet::interface] +pub trait MultisigABI { + // Read functions + fn get_quorum(self: @TState) -> u32; + fn is_signer(self: @TState, signer: ContractAddress) -> bool; + fn get_signers(self: @TState) -> Span; + fn is_confirmed(self: @TState, id: TransactionID) -> bool; + fn is_confirmed_by(self: @TState, id: TransactionID, signer: ContractAddress) -> bool; + fn is_executed(self: @TState, id: TransactionID) -> bool; + fn get_submitted_block(self: @TState, id: TransactionID) -> u64; + fn get_transaction_state(self: @TState, id: TransactionID) -> TransactionState; + fn get_transaction_confirmations(self: @TState, id: TransactionID) -> u32; + fn hash_transaction( + self: @TState, + to: ContractAddress, + selector: felt252, + calldata: Span, + salt: felt252, + ) -> TransactionID; + fn hash_transaction_batch(self: @TState, calls: Span, salt: felt252) -> TransactionID; + + // Write functions + fn add_signers(ref self: TState, new_quorum: u32, signers_to_add: Span); + fn remove_signers(ref self: TState, new_quorum: u32, signers_to_remove: Span); + fn replace_signer( + ref self: TState, signer_to_remove: ContractAddress, signer_to_add: ContractAddress, + ); + fn change_quorum(ref self: TState, new_quorum: u32); + fn submit_transaction( + ref self: TState, + to: ContractAddress, + selector: felt252, + calldata: Span, + salt: felt252, + ) -> TransactionID; + fn submit_transaction_batch( + ref self: TState, calls: Span, salt: felt252, + ) -> TransactionID; + fn confirm_transaction(ref self: TState, id: TransactionID); + fn revoke_confirmation(ref self: TState, id: TransactionID); + fn execute_transaction( + ref self: TState, + to: ContractAddress, + selector: felt252, + calldata: Span, + salt: felt252, + ); + fn execute_transaction_batch(ref self: TState, calls: Span, salt: felt252); +} +``` diff --git a/docs/content/contracts-cairo/alpha/governance/timelock.mdx b/docs/content/contracts-cairo/alpha/governance/timelock.mdx new file mode 100644 index 00000000..d3404511 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/governance/timelock.mdx @@ -0,0 +1,198 @@ +--- +title: Timelock Controller +--- + +The Timelock Controller provides a means of enforcing time delays on the execution of transactions. This is considered good practice regarding governance systems because it allows users the opportunity to exit the system if they disagree with a decision before it is executed. + + +The Timelock contract itself executes transactions, not the user. The Timelock should, therefore, hold associated funds, ownership, and access control roles. + + +## Operation lifecycle + +The state of an operation is represented by the `OperationState` enum and can be retrieved +by calling the `get_operation_state` function with the operation’s identifier. + +The identifier of an operation is a `felt252` value, computed as the Pedersen hash of the +operation’s call or calls, its predecessor, and salt. It can be computed by invoking the +implementing contract’s `hash_operation` function for single-call operations or +`hash_operation_batch` for multi-call operations. Submitting an operation with identical calls, +predecessor, and the same salt value a second time will fail, as operation identifiers must be +unique. To resolve this, use a different salt value to generate a unique identifier. + +Timelocked operations follow a specific lifecycle: + +`Unset` → `Waiting` → `Ready` → `Done` + +* `Unset`: the operation has not been scheduled or has been canceled. +* `Waiting`: the operation has been scheduled and is pending the scheduled delay. +* `Ready`: the timer has expired, and the operation is eligible for execution. +* `Done`: the operation has been executed. + +## Timelock flow + +### Schedule + +When a proposer calls [schedule](../api/governance#ITimelock-schedule), the `OperationState` moves from `Unset` to `Waiting`. +This starts a timer that must be greater than or equal to the minimum delay. +The timer expires at a timestamp accessible through [get_timestamp](../api/governance#ITimelock-get_timestamp). +Once the timer expires, the `OperationState` automatically moves to the `Ready` state. +At this point, it can be executed. + +### Execute + +By calling [execute](../api/governance#ITimelock-execute), an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, the predecessor’s operation must be in the `Done` state for this transaction to succeed. + +### Cancel + +The [cancel](../api/governance#ITimelock-cancel) function allows cancellers to cancel any pending operations. +This resets the operation to the `Unset` state. +It is therefore possible for a proposer to re-schedule an operation that has been cancelled. +In this case, the timer restarts when the operation is re-scheduled. + +## Roles + +[TimelockControllerComponent](../api/governance#TimelockControllerComponent) leverages an [AccessControlComponent](../api/access#AccessControlComponent) setup that we need to understand in order to set up roles. + +* `PROPOSER_ROLE` - in charge of queueing operations. +* `CANCELLER_ROLE` - may cancel scheduled operations. +During initialization, accounts granted with `PROPOSER_ROLE` will also be granted `CANCELLER_ROLE`. +Therefore, the initial proposers may also cancel operations after they are scheduled. +* `EXECUTOR_ROLE` - in charge of executing already available operations. +* `DEFAULT_ADMIN_ROLE` - can grant and revoke the three previous roles. + + +The `DEFAULT_ADMIN_ROLE` is a sensitive role that will be granted automatically to the timelock itself and optionally to a second account. +The latter case may be required to ease a contract’s initial configuration; however, this role should promptly be renounced. + + +Furthermore, the timelock component supports the concept of open roles for the `EXECUTOR_ROLE`. +This allows anyone to execute an operation once it’s in the `Ready` OperationState. +To enable the `EXECUTOR_ROLE` to be open, grant the zero address with the `EXECUTOR_ROLE`. + + +Be very careful with enabling open roles as _anyone_ can call the function. + + +## Minimum delay + +The minimum delay of the timelock acts as a buffer from when a proposer schedules an operation to the earliest point at which an executor may execute that operation. +The idea is for users, should they disagree with a scheduled proposal, to have options such as exiting the system or making their case for cancellers to cancel the operation. + +After initialization, the only way to change the timelock’s minimum delay is to schedule it and execute it with the same flow as any other operation. + +The minimum delay of a contract is accessible through [get_min_delay](../api/governance#ITimelock-get_min_delay). + +## Usage + +Integrating the timelock into a contract requires integrating [TimelockControllerComponent](../api/governance#TimelockControllerComponent) as well as [SRC5Component](../api/introspection#SRC5Component) and [AccessControlComponent](../api/access#AccessControlComponent) as dependencies. +The contract’s constructor should initialize the timelock which consists of setting the: + +* Proposers and executors. +* Minimum delay between scheduling and executing an operation. +* Optional admin if additional configuration is required. + + +The optional admin should renounce their role once configuration is complete. + + +Here’s an example of a simple timelock contract: + +```rust +#[starknet::contract] +mod TimelockControllerContract { + use openzeppelin_access::accesscontrol::AccessControlComponent; + use openzeppelin_governance::timelock::TimelockControllerComponent; + use openzeppelin_introspection::src5::SRC5Component; + use starknet::ContractAddress; + + component!(path: AccessControlComponent, storage: access_control, event: AccessControlEvent); + component!(path: TimelockControllerComponent, storage: timelock, event: TimelockEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // Timelock Mixin + #[abi(embed_v0)] + impl TimelockMixinImpl = + TimelockControllerComponent::TimelockMixinImpl; + impl TimelockInternalImpl = TimelockControllerComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + access_control: AccessControlComponent::Storage, + #[substorage(v0)] + timelock: TimelockControllerComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccessControlEvent: AccessControlComponent::Event, + #[flat] + TimelockEvent: TimelockControllerComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + min_delay: u64, + proposers: Span, + executors: Span, + admin: ContractAddress + ) { + self.timelock.initializer(min_delay, proposers, executors, admin); + } +} +``` + +## Interface + +This is the full interface of the TimelockMixinImpl implementation: + +```rust +#[starknet::interface] +pub trait TimelockABI { + // ITimelock + fn is_operation(self: @TState, id: felt252) -> bool; + fn is_operation_pending(self: @TState, id: felt252) -> bool; + fn is_operation_ready(self: @TState, id: felt252) -> bool; + fn is_operation_done(self: @TState, id: felt252) -> bool; + fn get_timestamp(self: @TState, id: felt252) -> u64; + fn get_operation_state(self: @TState, id: felt252) -> OperationState; + fn get_min_delay(self: @TState) -> u64; + fn hash_operation(self: @TState, call: Call, predecessor: felt252, salt: felt252) -> felt252; + fn hash_operation_batch( + self: @TState, calls: Span, predecessor: felt252, salt: felt252 + ) -> felt252; + fn schedule(ref self: TState, call: Call, predecessor: felt252, salt: felt252, delay: u64); + fn schedule_batch( + ref self: TState, calls: Span, predecessor: felt252, salt: felt252, delay: u64 + ); + fn cancel(ref self: TState, id: felt252); + fn execute(ref self: TState, call: Call, predecessor: felt252, salt: felt252); + fn execute_batch(ref self: TState, calls: Span, predecessor: felt252, salt: felt252); + fn update_delay(ref self: TState, new_delay: u64); + + // ISRC5 + fn supports_interface(self: @TState, interface_id: felt252) -> bool; + + // IAccessControl + fn has_role(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn get_role_admin(self: @TState, role: felt252) -> felt252; + fn grant_role(ref self: TState, role: felt252, account: ContractAddress); + fn revoke_role(ref self: TState, role: felt252, account: ContractAddress); + fn renounce_role(ref self: TState, role: felt252, account: ContractAddress); + + // IAccessControlCamel + fn hasRole(self: @TState, role: felt252, account: ContractAddress) -> bool; + fn getRoleAdmin(self: @TState, role: felt252) -> felt252; + fn grantRole(ref self: TState, role: felt252, account: ContractAddress); + fn revokeRole(ref self: TState, role: felt252, account: ContractAddress); + fn renounceRole(ref self: TState, role: felt252, account: ContractAddress); +} +``` diff --git a/docs/content/contracts-cairo/alpha/governance/votes.mdx b/docs/content/contracts-cairo/alpha/governance/votes.mdx new file mode 100644 index 00000000..44d9e731 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/governance/votes.mdx @@ -0,0 +1,222 @@ +--- +title: Votes +--- + +The [VotesComponent](../api/governance#VotesComponent) provides a flexible system for tracking and delegating voting power. This system allows users to delegate their voting power to other addresses, enabling more active participation in governance. + + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + + + +The transferring of voting units must be handled by the implementing contract. In the case of `ERC20` and `ERC721` this is usually done via the hooks. You can check the [usage](#usage) section for examples of how to implement this. + + +## Key features + +1. **Delegation**: Users can delegate their voting power to any address, including themselves. Vote power can be delegated either by calling the [delegate](../api/governance#VotesComponent-delegate) function directly, or by providing a signature to be used with [delegate_by_sig](../api/governance#VotesComponent-delegate_by_sig). +2. **Historical lookups**: The system keeps track of historical snapshots for each account, which allows the voting power of an account to be queried at a specific timestamp.\ +This can be used for example to determine the voting power of an account when a proposal was created, rather than using the current balance. + +## Usage + +When integrating the `VotesComponent`, the [VotingUnitsTrait](../api/governance#VotingUnitsTrait) must be implemented to get the voting units for a given account as a function of the implementing contract.\ +For simplicity, this module already provides two implementations for `ERC20` and `ERC721` tokens, which will work out of the box if the respective components are integrated.\ +Additionally, you must implement the [NoncesComponent](../api/utilities#NoncesComponent) and the [SNIP12Metadata](../api/utilities#snip12) trait to enable delegation by signatures. + +Here’s an example of how to structure a simple ERC20Votes contract: + +```rust +#[starknet::contract] +mod ERC20VotesContract { + use openzeppelin_governance::votes::VotesComponent; + use openzeppelin_token::erc20::{ERC20Component, DefaultConfig}; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + // Votes + #[abi(embed_v0)] + impl VotesImpl = VotesComponent::VotesImpl; + impl VotesInternalImpl = VotesComponent::InternalImpl; + + // ERC20 + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // Nonces + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc20_votes: VotesComponent::Storage, + #[substorage(v0)] + pub erc20: ERC20Component::Storage, + #[substorage(v0)] + pub nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20VotesEvent: VotesComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + // Required for hash computation. + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // We need to call the `transfer_voting_units` function after + // every mint, burn and transfer. + // For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`. + impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait { + fn after_update( + ref self: ERC20Component::ComponentState, + from: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) { + let mut contract_state = self.get_contract_mut(); + contract_state.erc20_votes.transfer_voting_units(from, recipient, amount); + } + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc20.initializer("MyToken", "MTK"); + } +} +``` + +And here’s an example of how to structure a simple ERC721Votes contract: + +```rust +#[starknet::contract] +pub mod ERC721VotesContract { + use openzeppelin_governance::votes::VotesComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_token::erc721::ERC721Component; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use openzeppelin_utils::cryptography::snip12::SNIP12Metadata; + use starknet::ContractAddress; + + component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + // Votes + #[abi(embed_v0)] + impl VotesImpl = VotesComponent::VotesImpl; + impl VotesInternalImpl = VotesComponent::InternalImpl; + + // ERC721 + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + // Nonces + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + + #[storage] + pub struct Storage { + #[substorage(v0)] + pub erc721_votes: VotesComponent::Storage, + #[substorage(v0)] + pub erc721: ERC721Component::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + #[substorage(v0)] + pub nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC721VotesEvent: VotesComponent::Event, + #[flat] + ERC721Event: ERC721Component::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + /// Required for hash computation. + pub impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'DAPP_VERSION' + } + } + + // We need to call the `transfer_voting_units` function after + // every mint, burn and transfer. + // For this, we use the `before_update` hook of the + //`ERC721Component::ERC721HooksTrait`. + // This hook is called before the transfer is executed. + // This gives us access to the previous owner. + impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait { + fn before_update( + ref self: ERC721Component::ComponentState, + to: ContractAddress, + token_id: u256, + auth: ContractAddress + ) { + let mut contract_state = self.get_contract_mut(); + + // We use the internal function here since it does not check if the token + // id exists which is necessary for mints + let previous_owner = self._owner_of(token_id); + contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1); + } + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.erc721.initializer("MyToken", "MTK", ""); + } +} +``` + +## Interface + +This is the full interface of the `VotesImpl` implementation: + +```rust +#[starknet::interface] +pub trait VotesABI { + // IVotes + fn get_votes(self: @TState, account: ContractAddress) -> u256; + fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256; + fn get_past_total_supply(self: @TState, timepoint: u64) -> u256; + fn delegates(self: @TState, account: ContractAddress) -> ContractAddress; + fn delegate(ref self: TState, delegatee: ContractAddress); + fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span); + + // INonces + fn nonces(self: @TState, owner: ContractAddress) -> felt252; +} +``` diff --git a/docs/content/contracts-cairo/alpha/guides/deploy-udc.mdx b/docs/content/contracts-cairo/alpha/guides/deploy-udc.mdx new file mode 100644 index 00000000..5e20ac1e --- /dev/null +++ b/docs/content/contracts-cairo/alpha/guides/deploy-udc.mdx @@ -0,0 +1,273 @@ +--- +title: UDC Appchain Deployment +--- + +While the Universal Deployer Contract (UDC) is deployed on Starknet public networks, appchains may need to deploy +their own instance of the UDC for their own use. This guide will walk you through this process while keeping the +same final address on all networks. + +## Prerequisites + +This guide assumes you have: + +* Familiarity with [Scarb](https://docs.swmansion.com/scarb/docs.html) and Starknet development environment. +* A functional account available on the network you’re deploying to. +* Familiarity with the process of declaring contracts through the [declare transaction](https://docs.starknet.io/resources/transactions-reference/#declare_transaction). + + +For declaring contracts on Starknet, you can use the [sncast](https://foundry-rs.github.io/starknet-foundry/starknet/declare.html) tool from the [starknet-foundry](https://foundry-rs.github.io/starknet-foundry/index.html) project. + + +## Note on the UDC final address + +It is important that the Universal Deployer Contract (UDC) in Starknet maintains the same address across all +networks because essential developer tools like **starkli** and **sncast** rely on this address by default when deploying contracts. +These tools are widely used in the Starknet ecosystem to streamline and standardize contract deployment workflows. + +If the UDC address is consistent, developers can write deployment scripts, CI/CD pipelines, and integrations that work seamlessly +across testnets, mainnet, and appchains without needing to update configuration files or handle special cases for each +environment. + +In the following sections, we’ll walk you through the process of deploying the UDC on appchains while keeping the same address, +under one important assumption: **the declared UDC class hash MUST be the same across all networks**. +Different compiler versions may produce different class hashes for the same contract, so you need to make +sure you are using the same compiler version to build the UDC class (and the release profile). + +The latest version of the UDC available in the `openzeppelin_presets` package was compiled with **Cairo v2.11.4** (release profile) and the resulting class hash is `0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8`. + + +If you are using a different compiler version, you need to make sure the class hash is the same as the one above in order to keep the same address across all networks. + + + +To avoid potential issues by using a different compiler version, you can directly import the contract class deployed on Starknet mainnet and declare it on your appchain. At +the time of writing, this is not easily achievable with the `sncast` tool, but you can leverage `[starkli](https://book.starkli.rs/declaring-classes)` to do it. + +Quick reference: + +```bash +starkli class-by-hash --parse \ + 0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8 \ + --network mainnet \ + > udc.json +``` + +This will output a `udc.json` file that you can use to declare the UDC on your appchain. + +```bash +starkli declare udc.json --rpc +``` + + + +## Madara Appchains + +[Madara](https://github.com/madara-alliance/madara/blob/main/README.md) is a popular Starknet node implementation that has a friendly and robust interface for building appchains. If +you are using it for this purpose, you are probably familiar with the [Madara Bootstrapper](https://github.com/madara-alliance/madara/tree/main/bootstrapper#readme), which already declares and +deploys a few contracts for you when you create a new appchain, including accounts and the UDC. + +However, since the UDC was migrated to a new version in June 2025, it’s possible that the appchain was created before +this change, meaning the UDC on the appchain is an older version. If that’s the case, you can follow the steps below to +deploy the new UDC. + +### 1. Declare and deploy the Bootstrapper + +In the Starknet ecosystem, contracts need to be declared before they can be deployed, and deployments can only happen +either via the `deploy_syscall`, or using a `deploy_account` transaction. The latter would require adding account +functionality to the UDC, which is not optimal, so we’ll use the `deploy_syscall`, which requires having an account +with this functionality enabled. + + +Madara declares an account with this functionality enabled as part of the bootstrapping process. You may be able to +use that implementation directly to skip this step. + + +#### Bootstrapper Contract + +The bootstrapper contract is a simple contract that declares the UDC and allows for its deployment via the `deploy_syscall`. +You can find a reference implementation below: + + +This reference implementation targets Cairo v2.11.4. If you are using a different version of Cairo, you may need to update the code to match your compiler version. + + +```rust +#[starknet::contract(account)] +mod UniversalDeployerBootstrapper { + use core::num::traits::Zero; + use openzeppelin_account::AccountComponent; + use openzeppelin_introspection::src5::SRC5Component; + use openzeppelin_utils::deployments::calculate_contract_address_from_deploy_syscall; + use starknet::{ClassHash, ContractAddress, SyscallResultTrait}; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + // + // Account features (deployable, declarer, and invoker) + // + + #[abi(embed_v0)] + pub(crate) impl DeployableImpl = + AccountComponent::DeployableImpl; + #[abi(embed_v0)] + impl DeclarerImpl = AccountComponent::DeclarerImpl; + #[abi(embed_v0)] + impl SRC6Impl = AccountComponent::SRC6Impl; + impl AccountInternalImpl = AccountComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + pub account: AccountComponent::Storage, + #[substorage(v0)] + pub src5: SRC5Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub(crate) enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + } + + #[constructor] + pub fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + } + + #[abi(per_item)] + #[generate_trait] + impl ExternalImpl of ExternalTrait { + #[external(v0)] + fn deploy_udc(ref self: ContractState, udc_class_hash: ClassHash) { + self.account.assert_only_self(); + starknet::syscalls::deploy_syscall(udc_class_hash, 0, array![].span(), true) + .unwrap_syscall(); + } + + #[external(v0)] + fn get_udc_address(ref self: ContractState, udc_class_hash: ClassHash) -> ContractAddress { + calculate_contract_address_from_deploy_syscall( + 0, udc_class_hash, array![].span(), Zero::zero(), + ) + } + +} +``` + +#### Deploying the Bootstrapper + +This guide assumes you have a functional account available on the network you’re deploying to, and familiarity +with the process of declaring contracts through the `declare` transaction. To recap, the reason we are deploying +this bootstrapper account contract is to be able to deploy the UDC via the `deploy_syscall`. + + +sncast v0.45.0 was used in the examples below. + + +As a quick example, if your account is configured for **sncast**, you can declare the bootstrapper contract with the following command: + +```bash +sncast -p declare \ + --contract-name UniversalDeployerBootstrapper +``` + +The bootstrapper implements the `IDeployable` trait, meaning it can be counterfactually deployed. Check out the +[Counterfactual Deployments](./deployment) guide. Continuing with the **sncast** examples, you can create and deploy the bootstrapper with the following commands: + +##### Create the account + +```bash +sncast account create --name bootstrapper \ + --network \ + --class-hash \ + --type oz +``` + +##### Deploy it to the network + + +You need to prefund the account with enough funds before you can deploy it. + + +```bash +sncast account deploy \ + --network \ + --name bootstrapper +``` + +### 2. Declare and deploy the UDC + +Once the bootstrapper is deployed, you can declare and deploy the UDC through it. + +#### Declaring the UDC + +The UDC source code is available in the `openzeppelin_presets` package. You can copy it to your project and declare it with the following command: + +```bash +sncast -p declare \ + --contract-name UniversalDeployer +``` + + +If you followed the [Note on the UDC final address](#note-on-the-udc-final-address) section, your declared class hash should be +`0x01b2df6d8861670d4a8ca4670433b2418d78169c2947f46dc614e69f333745c8`. + + +#### Previewing the UDC address + +You can preview the UDC address with the following command: + +```bash +sncast call \ + --network \ + --contract-address \ + --function "get_udc_address" \ + --arguments '' +``` + +If the UDC class hash is the same as the one in the [Note on the UDC final address](#note-on-the-udc-final-address) section, +the output should be `0x2ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125`. + +#### Deploying the UDC + +Now everything is set up to deploy the UDC. You can use the following command to deploy it: + + +Note that the bootstrapper contract MUST call itself to successfully deploy the UDC, since the `deploy_udc` function is protected. + + +```bash +sncast \ + --account bootstrapper \ + invoke \ + --network \ + --contract-address \ + --function "deploy_udc" \ + --arguments '' +``` + +## Other Appchain providers + +If you are using an appchain provider different from Madara, you can follow the same steps to deploy the UDC +as long as you have access to an account that can declare contracts. + +Summarizing, the steps to follow are: + +1. Declare the Bootstrapper +2. Counterfactually deploy the Bootstrapper +3. Declare the UDC +4. Preview the UDC address +5. Deploy the UDC from the Bootstrapper + +## Conclusion + +By following this guide, you have successfully deployed the Universal Deployer Contract on your appchain while ensuring consistency with +Starknet’s public networks. Maintaining the same UDC address and class hash across all environments is crucial for seamless contract deployment +and tooling compatibility, allowing developers to leverage tools like **sncast** and **starkli** without additional configuration. This process not only +improves the reliability of your deployment workflows but also ensures that your appchain remains compatible with the broader Starknet ecosystem. +With the UDC correctly deployed, you are now ready to take full advantage of streamlined contract +deployments and robust developer tooling on your appchain. diff --git a/docs/content/contracts-cairo/alpha/guides/deployment.mdx b/docs/content/contracts-cairo/alpha/guides/deployment.mdx new file mode 100644 index 00000000..42a924db --- /dev/null +++ b/docs/content/contracts-cairo/alpha/guides/deployment.mdx @@ -0,0 +1,40 @@ +--- +title: Counterfactual deployments +--- + +A counterfactual contract is a contract we can interact with even before actually deploying it on-chain. +For example, we can send funds or assign privileges to a contract that doesn’t yet exist. +Why? Because deployments in Starknet are deterministic, allowing us to predict the address where our contract will be deployed. +We can leverage this property to make a contract pay for its own deployment by simply sending funds in advance. We call this a counterfactual deployment. + +This process can be described with the following steps: + + +For testing this flow you can check the [Starknet Foundry](https://foundry-rs.github.io/starknet-foundry/starknet/account.html) or the [Starkli](https://book.starkli.rs/accounts#account-deployment) guides for deploying accounts. + + +1. Deterministically precompute the `contract_address` given a `class_hash`, `salt`, and constructor `calldata`. +Note that the `class_hash` must be previously declared for the deployment to succeed. +2. Send funds to the `contract_address`. Usually you will estimate the fee of the transaction first. Existing +tools usually do this for you. +3. Send a `DeployAccount` type transaction to the network. +4. The protocol will then validate the transaction with the `__validate_deploy__` entrypoint of the contract to be deployed. +5. If the validation succeeds, the protocol will charge the fee and then register the contract as deployed. + + +Although this method is very popular to deploy accounts, this works for any kind of contract. + + +## Deployment validation + +To be counterfactually deployed, the deploying contract must implement the `__validate_deploy__` entrypoint, +called by the protocol when a `DeployAccount` transaction is sent to the network. + +```rust +trait IDeployable { + /// Must return 'VALID' when the validation is successful. + fn __validate_deploy__( + class_hash: felt252, contract_address_salt: felt252, public_key: felt252 + ) -> felt252; +} +``` diff --git a/docs/content/contracts-cairo/alpha/guides/erc20-permit.mdx b/docs/content/contracts-cairo/alpha/guides/erc20-permit.mdx new file mode 100644 index 00000000..28259458 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/guides/erc20-permit.mdx @@ -0,0 +1,63 @@ +--- +title: ERC20Permit +--- + +The [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) standard, commonly referred to as ERC20Permit, is designed to support gasless token approvals. This is achieved with an off-chain +signature following the [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) standard, rather than with an on-chain transaction. The [permit](../api/erc20#ERC20Component-permit) function verifies the signature and sets +the spender’s allowance if the signature is valid. This approach improves user experience and reduces gas costs. + +## Differences from Solidity + +Although this extension is mostly similar to the [Solidity implementation](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol) of [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612), there are some notable differences in the parameters of the [permit](../api/erc20#ERC20Component-permit) function: + +* The `deadline` parameter is represented by `u64` rather than `u256`. +* The `signature` parameter is represented by a span of felts rather than `v`, `r`, and `s` values. + + +Unlike Solidity, there is no enforced format for signatures on Starknet. A signature is represented by an array or span of felts, +and there is no universal method for validating signatures of unknown formats. Consequently, a signature provided to the [permit](../api/erc20#ERC20Component-permit) function +is validated through an external `is_valid_signature` call to the contract at the `owner` address. + + + +## Usage + +The functionality is provided as an embeddable [ERC20Permit](../api/erc20#ERC20Component-Embeddable-Impls-ERC20PermitImpl) trait of the [ERC20Component](../api/erc20#ERC20Component). + +```rust +#[abi(embed_v0)] +impl ERC20PermitImpl = ERC20Component::ERC20PermitImpl; +``` + +A contract must meet the following requirements to be able to use the [ERC20Permit](../api/erc20#ERC20Component-Embeddable-Impls-ERC20PermitImpl) trait: + +* Implement [ERC20Component](../api/erc20#ERC20Component). +* Implement [NoncesComponent](../api/utilities#NoncesComponent). +* Implement [SNIP12Metadata](../api/utilities#snip12) trait (used in signature generation). + +## Typed message + +To safeguard against replay attacks and ensure the uniqueness of each approval via [permit](../api/erc20#ERC20Component-permit), the data signed includes: + +* The address of the `owner`. +* The parameters specified in the [approve](../api/erc20#ERC20Component-approve) function (`spender` and `amount`) +* The address of the `token` contract itself. +* A `nonce`, which must be unique for each operation. +* The `chain_id`, which protects against cross-chain replay attacks. + +The format of the `Permit` structure in a signed permit message is as follows: +```rust +struct Permit { + token: ContractAddress, + spender: ContractAddress, + amount: u256, + nonce: felt252, + deadline: u64, +} +``` + + +The owner of the tokens is also part of the signed message. It is used as the `signer` parameter in the `get_message_hash` call. + + +Further details on preparing and signing a typed message can be found in the [SNIP12 guide](./snip12). diff --git a/docs/content/contracts-cairo/alpha/guides/erc20-supply.mdx b/docs/content/contracts-cairo/alpha/guides/erc20-supply.mdx new file mode 100644 index 00000000..51465637 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/guides/erc20-supply.mdx @@ -0,0 +1,148 @@ +--- +title: Creating ERC20 Supply +--- + +The standard interface implemented by tokens built on Starknet comes from the popular token standard on Ethereum called ERC20. +[EIP20](https://eips.ethereum.org/EIPS/eip-20), from which ERC20 contracts are derived, does not specify how tokens are created. +This guide will go over strategies for creating both a fixed and dynamic token supply. + +## Fixed Supply + +Let’s say we want to create a token named `MyToken` with a fixed token supply. +We can achieve this by setting the token supply in the constructor which will execute upon deployment. + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + fixed_supply: u256, + recipient: ContractAddress + ) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, fixed_supply); + } +} +``` + +In the constructor, we’re first calling the ERC20 initializer to set the token name and symbol. +Next, we’re calling the internal `mint` function which creates `fixed_supply` of tokens and allocates them to `recipient`. +Since the internal `mint` is not exposed in our contract, it will not be possible to create any more tokens. +In other words, we’ve implemented a fixed token supply! + +## Dynamic Supply + +ERC20 contracts with a dynamic supply include a mechanism for creating or destroying tokens. +Let’s make a few changes to the almighty `MyToken` contract and create a minting mechanism. + +```rust +#[starknet::contract] +mod MyToken { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + let name = "MyToken"; + let symbol = "MTK"; + + self.erc20.initializer(name, symbol); + } + + #[external(v0)] + fn mint( + ref self: ContractState, + recipient: ContractAddress, + amount: u256 + ) { + // This function is NOT protected which means + // ANYONE can mint tokens + self.erc20.mint(recipient, amount); + } +} +``` + +The exposed `mint` above will create `amount` tokens and allocate them to `recipient`. +We now have our minting mechanism! + +There is, however, a big problem. +`mint` does not include any restrictions on who can call this function. +For the sake of good practices, let’s implement a simple permissioning mechanism with `Ownable`. + +```rust +#[starknet::contract] +mod MyToken { + + (...) + + // Integrate Ownable + + #[external(v0)] + fn mint( + ref self: ContractState, + recipient: ContractAddress, + amount: u256 + ) { + // Set permissions with Ownable + self.ownable.assert_only_owner(); + + // Mint tokens if called by the contract owner + self.erc20.mint(recipient, amount); + } +} +``` + +In the constructor, we pass the owner address to set the owner of the `MyToken` contract. +The `mint` function includes `assert_only_owner` which will ensure that only the contract owner can call this function. +Now, we have a protected ERC20 minting mechanism to create a dynamic token supply. + + +For a more thorough explanation of permission mechanisms, see [Access Control](../access). + diff --git a/docs/content/contracts-cairo/alpha/guides/interfaces-and-dispatchers.mdx b/docs/content/contracts-cairo/alpha/guides/interfaces-and-dispatchers.mdx new file mode 100644 index 00000000..fbabbc32 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/guides/interfaces-and-dispatchers.mdx @@ -0,0 +1,168 @@ +--- +title: Interfaces and Dispatchers +--- + +This section describes the interfaces OpenZeppelin Contracts for Cairo offer, and explains the design choices behind them. + +Interfaces can be found in the `openzeppelin_interfaces` package modules, such as `openzeppelin_interfaces::erc20`. + + +Starting from version `3.x.x`, OpenZeppelin Contracts for Cairo interfaces have been separated from their implementation modules into a dedicated package. +This architectural change brings several important benefits. Check the [Interfaces module](../interfaces) section for more information. + + +For example: + +```rust +use openzeppelin_interfaces::erc20::IERC20; +``` + +or + +```rust +use openzeppelin_interfaces::erc20::ERC20ABI; +``` + + +For simplicity, we’ll use ERC20 as example but the same concepts apply to other modules. + + +## Interface traits + +The library offers three types of traits to implement or interact with contracts: + +### Standard traits + +These are associated with a predefined interface such as a standard. +This includes only the functions defined in the interface, and is the standard way to interact with a compliant contract. + +```rust +#[starknet::interface] +pub trait IERC20 { + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +### ABI traits + +They describe a contract’s complete interface. This is useful to interface with a preset contract offered by this library, such as +the ERC20 preset that includes functions from different standards such as `IERC20` and `IERC20Camel`. + + +The library offers an ABI trait for most components, providing all external function signatures +even when most of the time all of them don’t need to be implemented at the same time. This can be helpful when interacting with +a contract implementing the component, instead of defining a new dispatcher. + + +```rust +#[starknet::interface] +pub trait ERC20ABI { + // IERC20 + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; + + // IERC20Metadata + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + + // IERC20CamelOnly + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; +} +``` + +### Dispatcher traits + +Traits annotated with `#[starknet::interface]` automatically generate a dispatcher that can be used to interact with contracts that implement the given interface. They can be imported by appending the `Dispatcher` and `DispatcherTrait` suffixes to the trait name, like this: + +```rust +use openzeppelin_interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; +``` + +Other types of dispatchers are also auto-generated from the annotated trait. See the +[Interacting with another contract](https://book.cairo-lang.org/ch15-02-interacting-with-another-contract.html) section of the Cairo book for more information. + + +In the example, the `IERC20Dispatcher` is the one used to interact with contracts, but the +`IERC20DispatcherTrait` needs to be in scope for the functions to be available. + + +## Dual interfaces + + +The `camelCase` functions are deprecated and maintained only for backwards compatibility. +It’s recommended to only use `snake_case` interfaces with contracts and components. The `camelCase` functions will be removed in +future versions. + + +Following the [Great Interface Migration](https://community.starknet.io/t/the-great-interface-migration/92107) plan, we added `snake_case` functions to all of our preexisting `camelCase` contracts with the goal of eventually dropping support for the latter. + +In short, the library offers two types of interfaces and utilities to handle them: + +1. `camelCase` interfaces, which are the ones we’ve been using so far. +2. `snake_case` interfaces, which are the ones we’re migrating to. + +This means that currently most of our contracts implement _dual interfaces_. For example, the ERC20 preset contract exposes `transferFrom`, `transfer_from`, `balanceOf`, `balance_of`, etc. + + +Dual interfaces are available for all external functions present in previous versions of OpenZeppelin Contracts for Cairo ([v0.6.1](https://github.com/OpenZeppelin/cairo-contracts/releases/tag/v0.6.1) and below). + + +### `IERC20` + +The default version of the ERC20 interface trait exposes `snake_case` functions: + +```rust +#[starknet::interface] +pub trait IERC20 { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + fn total_supply(self: @TState) -> u256; + fn balance_of(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transfer_from( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` + +### `IERC20Camel` + +On top of that, the library also offers a `camelCase` version of the same interface: + +```rust +#[starknet::interface] +pub trait IERC20Camel { + fn name(self: @TState) -> ByteArray; + fn symbol(self: @TState) -> ByteArray; + fn decimals(self: @TState) -> u8; + fn totalSupply(self: @TState) -> u256; + fn balanceOf(self: @TState, account: ContractAddress) -> u256; + fn allowance(self: @TState, owner: ContractAddress, spender: ContractAddress) -> u256; + fn transfer(ref self: TState, recipient: ContractAddress, amount: u256) -> bool; + fn transferFrom( + ref self: TState, sender: ContractAddress, recipient: ContractAddress, amount: u256 + ) -> bool; + fn approve(ref self: TState, spender: ContractAddress, amount: u256) -> bool; +} +``` diff --git a/docs/content/contracts-cairo/alpha/guides/snip12.mdx b/docs/content/contracts-cairo/alpha/guides/snip12.mdx new file mode 100644 index 00000000..a86aae6b --- /dev/null +++ b/docs/content/contracts-cairo/alpha/guides/snip12.mdx @@ -0,0 +1,344 @@ +--- +title: SNIP12 and Typed Messages +--- + +Similar to [EIP712](https://eips.ethereum.org/EIPS/eip-712), [SNIP12](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) is a standard for secure off-chain signature verification on Starknet. +It provides a way to hash and sign generic typed structs rather than just strings. When building decentralized +applications, usually you might need to sign a message with complex data. The purpose of signature verification +is then to ensure that the received message was indeed signed by the expected signer, and it hasn’t been tampered with. + +OpenZeppelin Contracts for Cairo provides a set of utilities to make the implementation of this standard +as easy as possible, and in this guide we will walk you through the process of generating the hashes of typed messages +using these utilities for on-chain signature verification. For that, let’s build an example with a custom [ERC20](/contracts-cairo/alpha/api/erc20#ERC20) contract +adding an extra `transfer_with_signature` method. + + +This is an educational example, and it is not intended to be used in production environments. + + +## CustomERC20 + +Let’s start with a basic ERC20 contract leveraging the [ERC20Component](/contracts-cairo/alpha/api/erc20#ERC20Component), and let’s add the new function. +Note that some declarations are omitted for brevity. The full example will be available at the end of the guide. + +```rust +#[starknet::contract] +mod CustomERC20 { + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + (...) + + #[constructor] + fn constructor( + ref self: ContractState, + initial_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer("MyToken", "MTK"); + self.erc20.mint(recipient, initial_supply); + } + + #[external(v0)] + fn transfer_with_signature( + ref self: ContractState, + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64, + signature: Array + ) { + (...) + } +} +``` + +The `transfer_with_signature` function will allow a user to transfer tokens to another account by providing a signature. +The signature will be generated off-chain, and it will be used to verify the message on-chain. Note that the message +we need to verify is a struct with the following fields: + +* `recipient`: The address of the recipient. +* `amount`: The amount of tokens to transfer. +* `nonce`: A unique number to prevent replay attacks. +* `expiry`: The timestamp when the signature expires. + +Note that generating the hash of this message on-chain is a requirement to verify the signature, because if we accept +the message as a parameter, it could be easily tampered with. + +## Generating the Typed Message Hash + +To generate the hash of the message, we need to follow these steps: + +### 1. Define the message struct. + +In this particular example, the message struct looks like this: + +```rust +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} +``` + +### 2. Get the message type hash. + +This is the `starknet_keccak(encode_type(message))` as defined in the [SNIP](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md#how-to-work-with-each-type). + +In this case it can be computed as follows: + +```rust +// Since there's no u64 type in SNIP-12, we use u128 for `expiry` in the type hash generation. +let message_type_hash = selector!( + "\"Message\"(\"recipient\":\"ContractAddress\",\"amount\":\"u256\",\"nonce\":\"felt\",\"expiry\":\"u128\")\"u256\"(\"low\":\"u128\",\"high\":\"u128\")" +); +``` + +which is the same as: + +```rust +let message_type_hash = 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; +``` + + +In practice it’s better to compute the type hash off-chain and hardcode it in the contract, since it is a constant value. + + +### 3. Implement the `StructHash` trait for the struct. + +You can import the trait from: `openzeppelin_utils::snip12::StructHash`. And this implementation +is nothing more than the encoding of the message as defined in the [SNIP](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md#how-to-work-with-each-type). + +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; +use openzeppelin_utils::snip12::StructHash; +use starknet::ContractAddress; + +const MESSAGE_TYPE_HASH: felt252 = + 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; + +#[derive(Copy, Drop, Hash)] +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} + +impl StructHashImpl of StructHash { + fn hash_struct(self: @Message) -> felt252 { + let hash_state = PoseidonTrait::new(); + hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() + } +} +``` + +### 4. Implement the `SNIP12Metadata` trait. + +This implementation determines the values of the domain separator. Only the `name` and `version` fields are required +because the `chain_id` is obtained on-chain, and the `revision` is hardcoded to `1`. + +```rust +use openzeppelin_utils::snip12::SNIP12Metadata; + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { 'DAPP_NAME' } + fn version() -> felt252 { 'v1' } +} +``` + +In the above example, no storage reads are required which avoids unnecessary extra gas costs, but in +some cases we may need to read from storage to get the domain separator values. This can be accomplished even when +the trait is not bounded to the ContractState, like this: + +```rust +use openzeppelin_utils::snip12::SNIP12Metadata; + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + let state = unsafe_new_contract_state(); + + // Some logic to get the name from storage + state.erc20.name().at(0).unwrap().into() + } + + fn version() -> felt252 { 'v1' } +} +``` + +### 5. Generate the hash. + +The final step is to use the `OffchainMessageHashImpl` implementation to generate the hash of the message +using the `get_message_hash` function. The implementation is already available as a utility. + +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; +use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; +use starknet::ContractAddress; + +const MESSAGE_TYPE_HASH: felt252 = + 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; + +#[derive(Copy, Drop, Hash)] +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} + +impl StructHashImpl of StructHash { + fn hash_struct(self: @Message) -> felt252 { + let hash_state = PoseidonTrait::new(); + hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() + } +} + +impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'DAPP_NAME' + } + fn version() -> felt252 { + 'v1' + } +} + +fn get_hash( + account: ContractAddress, recipient: ContractAddress, amount: u256, nonce: felt252, expiry: u64 +) -> felt252 { + let message = Message { recipient, amount, nonce, expiry }; + message.get_message_hash(account) +} +``` + + +The expected parameter for the `get_message_hash` function is the address of account that signed the message. + + +## Full Implementation + +Finally, the full implementation of the `CustomERC20` contract looks like this: + + +We are using the [`ISRC6Dispatcher`](/contracts-cairo/alpha/api/account#ISRC6) to verify the signature, +and the [`NoncesComponent`](/contracts-cairo/alpha/api/utilities#NoncesComponent) to handle nonces to prevent replay attacks. + + +```rust +use core::hash::{HashStateExTrait, HashStateTrait}; +use core::poseidon::PoseidonTrait; +use openzeppelin_utils::snip12::{SNIP12Metadata, StructHash, OffchainMessageHash}; +use starknet::ContractAddress; + +const MESSAGE_TYPE_HASH: felt252 = + 0x28bf13f11bba405c77ce010d2781c5903cbed100f01f72fcff1664f98343eb6; + +#[derive(Copy, Drop, Hash)] +struct Message { + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64 +} + +impl StructHashImpl of StructHash { + fn hash_struct(self: @Message) -> felt252 { + let hash_state = PoseidonTrait::new(); + hash_state.update_with(MESSAGE_TYPE_HASH).update_with(*self).finalize() + } +} + +#[starknet::contract] +mod CustomERC20 { + use openzeppelin_interfaces::accounts::{ISRC6Dispatcher, ISRC6DispatcherTrait}; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin_utils::cryptography::nonces::NoncesComponent; + use starknet::ContractAddress; + + use super::{Message, OffchainMessageHash, SNIP12Metadata}; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: NoncesComponent, storage: nonces, event: NoncesEvent); + + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[abi(embed_v0)] + impl NoncesImpl = NoncesComponent::NoncesImpl; + impl NoncesInternalImpl = NoncesComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + nonces: NoncesComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + NoncesEvent: NoncesComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, initial_supply: u256, recipient: ContractAddress) { + self.erc20.initializer("MyToken", "MTK"); + self.erc20.mint(recipient, initial_supply); + } + + /// Required for hash computation. + impl SNIP12MetadataImpl of SNIP12Metadata { + fn name() -> felt252 { + 'CustomERC20' + } + fn version() -> felt252 { + 'v1' + } + } + + #[external(v0)] + fn transfer_with_signature( + ref self: ContractState, + recipient: ContractAddress, + amount: u256, + nonce: felt252, + expiry: u64, + signature: Array + ) { + assert(starknet::get_block_timestamp() <= expiry, 'Expired signature'); + let owner = starknet::get_caller_address(); + + // Check and increase nonce + self.nonces.use_checked_nonce(owner, nonce); + + // Build hash for calling `is_valid_signature` + let message = Message { recipient, amount, nonce, expiry }; + let hash = message.get_message_hash(owner); + + let is_valid_signature_felt = ISRC6Dispatcher { contract_address: owner } + .is_valid_signature(hash, signature); + + // Check either 'VALID' or true for backwards compatibility + let is_valid_signature = is_valid_signature_felt == starknet::VALIDATED + || is_valid_signature_felt == 1; + assert(is_valid_signature, 'Invalid signature'); + + // Transfer tokens + self.erc20._transfer(owner, recipient, amount); + } +} +``` diff --git a/docs/content/contracts-cairo/alpha/index.mdx b/docs/content/contracts-cairo/alpha/index.mdx new file mode 100644 index 00000000..9c768d64 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/index.mdx @@ -0,0 +1,156 @@ +--- +title: Contracts for Cairo +--- + +[starknet]:https://starkware.co/product/starknet/ +[scarb]:https://docs.swmansion.com/scarb/ +[scarb-install]:https://docs.swmansion.com/scarb/download.html + + +**A library for secure smart contract development** written in Cairo for [Starknet][starknet]. This library consists of a set of +[reusable components](/contracts-cairo/alpha/components) to build custom smart contracts, as well as ready-to-deploy [presets](/contracts-cairo/alpha/presets). You can also +find other [utilities](/contracts-cairo/alpha/api/utilities) including [interfaces and dispatchers](/contracts-cairo/alpha/interfaces) and [test utilities](/contracts-cairo/alpha/api/testing) +that facilitate testing with Starknet Foundry. + + +You can track our roadmap and future milestones in our [Github Project](https://github.com/orgs/OpenZeppelin/projects/29/). + + +## Installation + +The library is available as a [Scarb][scarb] package. Follow [this guide][scarb-install] for installing Cairo and Scarb on your machine +before proceeding, and run the following command to check that the installation was successful: + +```bash +$ scarb --version + +scarb 2.12.2 (dc0dbfd50 2025-09-15) +cairo: 2.12.2 (https://crates.io/crates/cairo-lang-compiler/2.12.2) +sierra: 1.7.0 +``` + +### Set up your project + +Create an empty directory, and `cd` into it: + +```bash +mkdir my_project/ && cd my_project/ +``` + +Initialize a new Scarb project: + +```bash +scarb init +``` + +The contents of `my_project/` should now look like this: + +```bash +$ ls + +Scarb.toml src +``` + +### Install the library + + + +The `openzeppelin` package is an umbrella (meta) package that aggregates all library subpackages. Prior to v3.x, the umbrella and its subpackages were +versioned in lockstep—their versions always matched. Starting with v3.x, following the introduction of `openzeppelin_interfaces`, the umbrella is +versioned independently from some of the subpackages. + +See the [Versioning of the sub-packages](/contracts-cairo#versioning-of-the-sub-packages) section for more information. + + + +Install the library by declaring it as a dependency in the project’s `Scarb.toml` file: + +```javascript +[dependencies] +openzeppelin = "{{umbrella_version}}" +``` + +The previous example would import the entire library. We can also add each package as a separate dependency to +improve the building time by not including modules that won’t be used: + +```toml +[dependencies] +openzeppelin_access = "{{umbrella_version}}" +openzeppelin_token = "{{umbrella_version}}" +openzeppelin_interfaces = "{{openzeppelin_interfaces_version}}" +``` + +## Versioning of the sub-packages + +Here you can find a reference of the versioning of the sub-packages for this umbrella version: + +```javascript +[dependencies] +openzeppelin_access = "{{umbrella_version}}" +openzeppelin_token = "{{umbrella_version}}" +openzeppelin_access = "{{umbrella_version}}" +openzeppelin_account = "{{umbrella_version}}" +openzeppelin_finance = "{{umbrella_version}}" +openzeppelin_interfaces = "{{openzeppelin_interfaces_version}}" +openzeppelin_governance = "{{umbrella_version}}" +openzeppelin_introspection = "{{umbrella_version}}" +openzeppelin_merkle_tree = "{{umbrella_version}}" +openzeppelin_presets = "{{umbrella_version}}" +openzeppelin_security = "{{umbrella_version}}" +openzeppelin_token = "{{umbrella_version}}" +openzeppelin_upgrades = "{{umbrella_version}}" +openzeppelin_utils = "{{openzeppelin_utils_version}}" +``` + +## Basic usage + +This is how it looks to build an ERC20 contract using the [ERC20 component](/contracts-cairo/alpha/erc20). +Copy the code into `src/lib.cairo`. + +```rust +#[starknet::contract] +mod MyERC20Token { + // NOTE: If you added the entire library as a dependency, + // use `openzeppelin::token` instead. + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl, DefaultConfig}; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + fixed_supply: u256, + recipient: ContractAddress + ) { + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, fixed_supply); + } +} +``` + +You can now compile it: + +```bash +scarb build +``` diff --git a/docs/content/contracts-cairo/alpha/interfaces.mdx b/docs/content/contracts-cairo/alpha/interfaces.mdx new file mode 100644 index 00000000..48df12f1 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/interfaces.mdx @@ -0,0 +1,62 @@ +--- +title: Interfaces +--- + +Starting from version `3.x.x`, OpenZeppelin Contracts for Cairo interfaces have been separated from their implementation modules into a dedicated package called `openzeppelin_interfaces`. This architectural change brings several important benefits: + +## Decoupled Dependencies + +The main motivation behind this separation is to decouple dependencies among packages when only interfaces are needed. For example, if a project only needs to interact with an ERC20 contract but doesn’t need to implement one, it can depend solely on the interfaces package without pulling in the full implementation. + +```javascript +[dependencies] +openzeppelin_interfaces = "{{openzeppelin_interfaces_version}}" +``` + + +The version of the interfaces package is independent from the version of the implementation packages. +The current umbrella version is `v{{umbrella_version}}`, and it depends on the `openzeppelin_interfaces` package `v{{openzeppelin_interfaces_version}}`. + + +## Stable Versioning + +The interfaces package follows its own versioning scheme, independent from the implementation packages. Since interfaces are meant to be stable and rarely change, the major version of the interfaces package won’t be updated as frequently as the implementation packages. + +This stability means that: + +* Dependencies can remain compatible for longer periods +* Breaking changes in implementations won’t force interface updates +* Projects depending only on interfaces are less likely to face version conflicts + +## Usage + +The interfaces package provides three main types of traits that can be imported directly from their respective modules: + +### Interface Traits + +Standard interface traits that define the contract’s functions: + +```rust +use openzeppelin_interfaces::erc20::IERC20; +use openzeppelin_interfaces::erc721::IERC721; +``` + +### Dispatchers + +Contract dispatchers for interacting with deployed contracts: + +```rust +use openzeppelin_interfaces::erc20::IERC20Dispatcher; +use openzeppelin_interfaces::erc721::IERC721Dispatcher; +``` + +### ABI Traits + +Complete external ABI definition of a given component/contract: + +```rust +use openzeppelin_interfaces::erc20::ERC20ABI; +use openzeppelin_interfaces::erc721::ERC721ABI; +``` + +This modular approach provides a cleaner and more maintainable way to work with OpenZeppelin contracts, especially in larger projects with multiple dependencies. diff --git a/docs/content/contracts-cairo/alpha/introspection.mdx b/docs/content/contracts-cairo/alpha/introspection.mdx new file mode 100644 index 00000000..09fd89b4 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/introspection.mdx @@ -0,0 +1,137 @@ +--- +title: Introspection +--- + +To smooth interoperability, often standards require smart contracts to implement [introspection mechanisms](https://en.wikipedia.org/wiki/Type_introspection). + +In Ethereum, the [EIP165](https://eips.ethereum.org/EIPS/eip-165) standard defines how contracts should declare +their support for a given interface, and how other contracts may query this support. + +Starknet offers a similar mechanism for interface introspection defined by the [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) standard. + +## SRC5 + +Similar to its Ethereum counterpart, the [SRC5](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) standard requires contracts to implement the `supports_interface` function, +which can be used by others to query if a given interface is supported. + +### Usage + +To expose this functionality, the contract must implement the [SRC5Component](/contracts-cairo/alpha/api/introspection#SRC5Component), which defines the `supports_interface` function. +Here is an example contract: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.src5.register_interface(selector!("some_interface")); + } +} +``` + +### Interface + +```rust +#[starknet::interface] +pub trait ISRC5 { + /// Query if a contract implements an interface. + /// Receives the interface identifier as specified in SRC-5. + /// Returns `true` if the contract implements `interface_id`, `false` otherwise. + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +## Computing the interface ID + +The interface ID, as specified in the standard, is the [XOR](https://en.wikipedia.org/wiki/Exclusive_or) of all the +[Extended Function Selectors](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md#extended-function-selector) +of the interface. We strongly advise reading the SNIP to understand the specifics of computing these +extended function selectors. There are tools such as [src5-rs](https://github.com/ericnordelo/src5-rs) that can help with this process. + +## Registering interfaces + +For a contract to declare its support for a given interface, we recommend using the SRC5 component to register support upon contract deployment through a constructor either directly or indirectly (as an initializer) like this: + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_interfaces::accounts as interface; + use openzeppelin_introspection::src5::SRC5Component; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl InternalImpl = SRC5Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event + } + + #[constructor] + fn constructor(ref self: ContractState) { + // Register the contract's support for the ISRC6 interface + self.src5.register_interface(interface::ISRC6_ID); + } + + (...) +} +``` + +## Querying interfaces + +Use the `supports_interface` function to query a contract’s support for a given interface. + +```rust +#[starknet::contract] +mod MyContract { + use openzeppelin_interfaces::accounts as interface; + use openzeppelin_interfaces::introspection::ISRC5DispatcherTrait; + use openzeppelin_interfaces::introspection::ISRC5Dispatcher; + use starknet::ContractAddress; + + #[storage] + struct Storage {} + + #[external(v0)] + fn query_is_account(self: @ContractState, target: ContractAddress) -> bool { + let dispatcher = ISRC5Dispatcher { contract_address: target }; + dispatcher.supports_interface(interface::ISRC6_ID) + } +} +``` + + +If you are unsure whether a contract implements SRC5 or not, you can follow the process described in +[here](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md#how-to-detect-if-a-contract-implements-src-5). + diff --git a/docs/content/contracts-cairo/alpha/macros.mdx b/docs/content/contracts-cairo/alpha/macros.mdx new file mode 100644 index 00000000..13482da4 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/macros.mdx @@ -0,0 +1,15 @@ +--- +title: Macros +--- + +This crate provides a collection of macros that streamline and simplify development with the library. +To use them, you need to add the `openzeppelin_macros` crate as a dependency in your `Scarb.toml` file: + +```toml +openzeppelin_macros = "{{umbrella_version}}" +``` + +## Attribute macros + +* [with_components](./macros/with_components) +* [type_hash](./macros/type_hash) diff --git a/docs/content/contracts-cairo/alpha/macros/type_hash.mdx b/docs/content/contracts-cairo/alpha/macros/type_hash.mdx new file mode 100644 index 00000000..279090a2 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/macros/type_hash.mdx @@ -0,0 +1,193 @@ +--- +title: type_hash +--- + +This macro generates a SNIP-12-compatible type hash for a given struct or enum. + + +This macro is fully compatible with the SNIP-12 standard revision 1. + + +## Usage + +```rust +#[type_hash(name: "My Struct", debug: true)] +struct MyStruct { + #[snip12(name: "My Field")] + my_field: felt252, +} +``` + +This will generate a type hash for the struct. + +```rust +pub const MY_STRUCT_TYPE_HASH: felt252 = 0x1735aa9819941b96c651b740b792a96c854565eaff089b7e293d996828b88a8; +``` + +And because of the `debug` argument, it will generate the following code: + +```rust +pub fn __MY_STRUCT_encoded_type() { + println!("\"My Struct\"(\"My Field\":\"felt\")"); +} +``` + +## Basic types + +The list of supported basic types as defined in the SNIP-12 standard is: + +* felt252 +* shortstring +* ClassHash +* ContractAddress +* timestamp +* selector +* merkletree +* u128 +* i128 + +### Examples + +Struct with basic types and custom names and kinds: + +```rust +#[type_hash(name: "My Struct", debug: true)] +pub struct MyStruct { + #[snip12(name: "Simple Felt")] // Optional custom name + pub simple_felt: felt252, + #[snip12(name: "Class Hash")] + pub class_hash: ClassHash, + #[snip12(name: "Target Token")] + pub target: ContractAddress, + #[snip12(name: "Timestamp", kind: "timestamp")] + pub timestamp: u128, + #[snip12(name: "Selector", kind: "selector")] + pub selector: felt252, +} + +pub const MY_STRUCT_TYPE_HASH: felt252 + = 0x522e0c3dc5e13b0978f4645760a436b1e119fd335842523fee8fbae6057b8c; + +``` + +Enum with basic types and custom names and kinds: + +```rust +#[type_hash(name: "My Enum", debug: true)] +pub enum MyEnum { + #[snip12(name: "Simple Felt")] + SimpleFelt: felt252, + #[snip12(name: "Class Hash")] + ClassHash: ClassHash, + #[snip12(name: "Target Token")] + ContractAddress: ContractAddress, + #[snip12(name: "Timestamp", kind: "timestamp")] + Timestamp: u128, + #[snip12(name: "Selector", kind: "selector")] + Selector: felt252, +} + +pub const MY_ENUM_TYPE_HASH: felt252 + = 0x3f30aaa6cda9f699d4131940b10602b78b986feb88f28a19f3b48567cb4b566; +``` + +## Collection types + +The list of supported collection types as defined in the SNIP-12 standard is: + +* Array +* Tuple ***(Only supported for enums)*** +* Span ***(Treated as an array)*** + + +While Span is not directly supported by the SNIP-12 standard, it is treated as an array for the purposes of this macro, since +it is sometimes helpful to use `Span` instead of `Array` in order to save on gas. + + +### Examples + +Struct with collection types: + +```rust +#[type_hash(name: "My Struct", debug: true)] +pub struct MyStruct { + #[snip12(name: "Member 1")] + pub member1: Array, + #[snip12(name: "Member 2")] + pub member2: Span, + #[snip12(name: "Timestamps", kind: "Array")] + pub timestamps: Array, +} + +pub const MY_STRUCT_TYPE_HASH: felt252 + = 0x369cdec45d8c55e70986aed44da0e330375171ba6e25b58e741c0ce02fa8ac; +``` + +Enum with collection types: + +```rust +#[type_hash(name: "My Enum", debug: true)] +pub enum MyEnum { + #[snip12(name: "Member 1")] + Member1: Array, + #[snip12(name: "Member 2")] + Member2: Span, + #[snip12(name: "Timestamps", kind: "Array")] + Timestamps: Array, + #[snip12(name: "Name and Last Name", kind: "(shortstring, shortstring)")] + NameAndLastName: (felt252, felt252), +} + +pub const MY_ENUM_TYPE_HASH: felt252 + = 0x9e3e1ebad4448a8344b3318f9cfda5df237588fd8328e1c2968635f09c735d; +``` + +## Preset types + +The list of supported preset types as defined in the SNIP-12 standard is: + +* TokenAmount +* NftId +* u256 + +### Examples + +Struct with preset types: + +```rust +#[type_hash(name: "My Struct", debug: true)] +pub struct MyStruct { + #[snip12(name: "Token Amount")] + pub token_amount: TokenAmount, + #[snip12(name: "NFT ID")] + pub nft_id: NftId, + #[snip12(name: "Number")] + pub number: u256, +} + +pub const MY_STRUCT_TYPE_HASH: felt252 + = 0x19f63528d68c4f44b7d9003a5a6b7793f5bb6ffc8a22bdec82b413ddf4f9412; +``` + +Enum with preset types: + +```rust +#[type_hash(name: "My Enum", debug: true)] +pub enum MyEnum { + #[snip12(name: "Token Amount")] + TokenAmount: TokenAmount, + #[snip12(name: "NFT ID")] + NftId: NftId, + #[snip12(name: "Number")] + Number: u256, +} + +pub const MY_ENUM_TYPE_HASH: felt252 + = 0x39dd19c7e5c5f89e084b78a26200b712c6ae3265f2bae774471c588858421b7; +``` + +## User-defined types + +User-defined types are currently ***NOT SUPPORTED*** since the macro doesn’t have access to scope outside of the +target struct/enum. In the future it may be supported by extending the syntax to explicitly declare the custom type +definition. diff --git a/docs/content/contracts-cairo/alpha/macros/with_components.mdx b/docs/content/contracts-cairo/alpha/macros/with_components.mdx new file mode 100644 index 00000000..ba7a6dee --- /dev/null +++ b/docs/content/contracts-cairo/alpha/macros/with_components.mdx @@ -0,0 +1,133 @@ +--- +title: with_components +--- + +This macro simplifies the syntax for adding a set of components to a contract. It: + +* _Imports the corresponding components into the contract._ +* _Adds the corresponding `component!` macro entries._ +* _Adds the storage entries for each component to the Storage struct._ +* _Adds the event entries for each component to the Event struct, or creates the struct if it is missing._ +* _Brings the corresponding internal implementations into scope._ +* _Provides some diagnostics for each specific component to help the developer avoid common mistakes._ + + +Since the macro does not expose any external implementations, developers must make sure to specify explicitly +the ones required by the contract. + + +## Security considerations + +The macro was designed to be simple and effective while still being very hard to misuse. For this reason, the features +that it provides are limited, and things that might make the contract behave in unexpected ways must be +explicitly specified by the developer. It does not specify external implementations, so contracts won’t find +themselves in a situation where external functions are exposed without the developer’s knowledge. It brings +the internal implementations into scope so these functions are available by default, but if they are not used, +they won’t have any effect on the contract’s behavior. + +## Usage + +This is how a contract with multiple components looks when using the macro. + +```rust +#[with_components(Account, SRC5, SRC9, Upgradeable)] +#[starknet::contract(account)] +mod OutsideExecutionAccountUpgradeable { + use openzeppelin_interfaces::upgrades::IUpgradeable; + use starknet::{ClassHash, ContractAddress}; + + // External + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + #[abi(embed_v0)] + impl OutsideExecutionV2Impl = + SRC9Component::OutsideExecutionV2Impl; + + #[storage] + struct Storage {} + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + self.src9.initializer(); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.account.assert_only_self(); + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` + +This is how the same contract looks using regular syntax. + +```rust +#[starknet::contract(account)] +mod OutsideExecutionAccountUpgradeable { + use openzeppelin::account::AccountComponent; + use openzeppelin::account::extensions::SRC9Component; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::upgrades::UpgradeableComponent; + use openzeppelin::upgrades::interface::IUpgradeable; + use starknet::ClassHash; + + component!(path: AccountComponent, storage: account, event: AccountEvent); + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: SRC9Component, storage: src9, event: SRC9Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // External + #[abi(embed_v0)] + impl AccountMixinImpl = AccountComponent::AccountMixinImpl; + #[abi(embed_v0)] + impl OutsideExecutionV2Impl = + SRC9Component::OutsideExecutionV2Impl; + + // Internal + impl AccountInternalImpl = AccountComponent::InternalImpl; + impl OutsideExecutionInternalImpl = SRC9Component::InternalImpl; + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + account: AccountComponent::Storage, + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + src9: SRC9Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + AccountEvent: AccountComponent::Event, + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + SRC9Event: SRC9Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.account.initializer(public_key); + self.src9.initializer(); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.account.assert_only_self(); + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` diff --git a/docs/content/contracts-cairo/alpha/presets.mdx b/docs/content/contracts-cairo/alpha/presets.mdx new file mode 100644 index 00000000..1cb58bbd --- /dev/null +++ b/docs/content/contracts-cairo/alpha/presets.mdx @@ -0,0 +1,141 @@ +--- +title: Presets +--- + +Presets are ready-to-deploy contracts provided by the library. Since presets are intended to be very simple +and as generic as possible, there’s no support for custom or complex contracts such as `ERC20Pausable` or `ERC721Mintable`. + + +For contract customization and combination of modules you can use [Wizard for Cairo](https://wizard.openzeppelin.com), our code-generation tool. + + +## Available presets + +List of available presets and their corresponding [Sierra class hashes](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/). Like Contracts for Cairo, +use of preset contracts are subject to the terms of the +[MIT License](https://github.com/OpenZeppelin/cairo-contracts?tab=MIT-1-ov-file#readme). + + +Class hashes were computed using scarb `v{{class_hash_scarb_version}}` and the `scarb --release` profile. + + + +Before version 2.x, class hashes were computed using the `scarb --dev` profile. + + +| Name | Sierra Class Hash | +| --- | --- | +| [`AccountUpgradeable`](/contracts-cairo/alpha/api/account#AccountUpgradeable) | `{{AccountUpgradeableClassHash}}` | +| [`ERC20Upgradeable`](/contracts-cairo/alpha/api/erc20#ERC20Upgradeable) | `{{ERC20UpgradeableClassHash}}` | +| [`ERC721Upgradeable`](/contracts-cairo/alpha/api/erc721#ERC721Upgradeable) | `{{ERC721UpgradeableClassHash}}` | +| [`ERC1155Upgradeable`](/contracts-cairo/alpha/api/erc1155#ERC1155Upgradeable) | `{{ERC1155UpgradeableClassHash}}` | +| [`EthAccountUpgradeable`](/contracts-cairo/alpha/api/account#EthAccountUpgradeable) | `{{EthAccountUpgradeableClassHash}}` | +| [`UniversalDeployer`](/contracts-cairo/alpha/api/udc#UniversalDeployer) | `{{UniversalDeployerClassHash}}` | +| [`VestingWallet`](/contracts-cairo/alpha/api/finance#VestingWallet) | `{{VestingWalletClassHash}}` | + + +[starkli](https://book.starkli.rs/introduction) class-hash command can be used to compute the class hash from a Sierra artifact. + + +## Usage + +These preset contracts are ready-to-deploy which means they should already be declared on the Sepolia network. +Simply deploy the preset class hash and add the appropriate constructor arguments. +Deploying the ERC20Upgradeable preset with [starkli](https://book.starkli.rs/introduction), for example, will look like this: + +```bash +starkli deploy {ERC20Upgradeable-class-hash} \ + \ + --network="sepolia" +``` + +If a class hash has yet to be declared, copy/paste the preset contract code and declare it locally. +Start by [setting up a project](/contracts-cairo#set-up-your-project) and [installing the Contracts for Cairo library](/contracts-cairo#install-the-library). +Copy the target preset contract from the [presets directory](https://github.com/OpenZeppelin/cairo-contracts/blob/release-v{{umbrella_version}}/packages/presets/src) +and paste it in the new project’s `src/lib.cairo` like this: + +```rust +// src/lib.cairo + +#[starknet::contract] +mod ERC20Upgradeable { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_interfaces::upgrades::IUpgradeable; + use starknet::{ContractAddress, ClassHash}; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // ERC20 Mixin + #[abi(embed_v0)] + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + erc20: ERC20Component::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + ERC20Event: ERC20Component::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + fixed_supply: u256, + recipient: ContractAddress, + owner: ContractAddress + ) { + self.ownable.initializer(owner); + self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, fixed_supply); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable.assert_only_owner(); + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` + +Next, compile the contract. + +```bash +scarb build +``` + +Finally, declare the preset. + +```bash +starkli declare target/dev/my_project_ERC20Upgradeable.contract_class.json \ + --network="sepolia" +``` diff --git a/docs/content/contracts-cairo/alpha/security.mdx b/docs/content/contracts-cairo/alpha/security.mdx new file mode 100644 index 00000000..13c32d96 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/security.mdx @@ -0,0 +1,218 @@ +--- +title: Security +--- + +The following documentation provides context, reasoning, and examples of modules found under `openzeppelin_security`. + + +Expect these modules to evolve. + + +## Initializable + +The [Initializable](/contracts-cairo/alpha/api/security#InitializableComponent) component provides a simple mechanism that mimics +the functionality of a constructor. +More specifically, it enables logic to be performed once and only once which is useful to set up a contract’s initial state when a constructor cannot be used, for example when there are circular dependencies at construction time. + +### Usage + +You can use the component in your contracts like this: + +```rust +#[starknet::contract] +mod MyInitializableContract { + use openzeppelin_security::InitializableComponent; + + component!(path: InitializableComponent, storage: initializable, event: InitializableEvent); + + impl InternalImpl = InitializableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + initializable: InitializableComponent::Storage, + param: felt252 + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + InitializableEvent: InitializableComponent::Event + } + + fn initializer(ref self: ContractState, some_param: felt252) { + // Makes the method callable only once + self.initializable.initialize(); + + // Initialization logic + self.param.write(some_param); + } +} +``` + + +This Initializable pattern should only be used in one function. + + +### Interface + +The component provides the following external functions as part of the `InitializableImpl` implementation: + +```rust +#[starknet::interface] +pub trait InitializableABI { + fn is_initialized() -> bool; +} +``` + +## Pausable + +The [Pausable](/contracts-cairo/alpha/api/security#PausableComponent) component allows contracts to implement an emergency stop mechanism. +This can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug. + +To become pausable, the contract should include `pause` and `unpause` functions (which should be protected). +For methods that should be available only when paused or not, insert calls to `[assert_paused](/contracts-cairo/alpha/api/security#PausableComponent-assert_paused)` and `[assert_not_paused](/contracts-cairo/alpha/api/security#PausableComponent-assert_not_paused)` +respectively. + +### Usage + +For example (using the [Ownable](/contracts-cairo/alpha/api/access#OwnableComponent) component for access control): + +```rust +#[starknet::contract] +mod MyPausableContract { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_security::PausableComponent; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: PausableComponent, storage: pausable, event: PausableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // Pausable + #[abi(embed_v0)] + impl PausableImpl = PausableComponent::PausableImpl; + impl PausableInternalImpl = PausableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + pausable: PausableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + PausableEvent: PausableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + #[external(v0)] + fn pause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.pause(); + } + + #[external(v0)] + fn unpause(ref self: ContractState) { + self.ownable.assert_only_owner(); + self.pausable.unpause(); + } + + #[external(v0)] + fn when_not_paused(ref self: ContractState) { + self.pausable.assert_not_paused(); + // Do something + } + + #[external(v0)] + fn when_paused(ref self: ContractState) { + self.pausable.assert_paused(); + // Do something + } +} +``` + +### Interface + +The component provides the following external functions as part of the `PausableImpl` implementation: + +```rust +#[starknet::interface] +pub trait PausableABI { + fn is_paused() -> bool; +} +``` + +## Reentrancy Guard + +A [reentrancy attack](https://gus-tavo-guim.medium.com/reentrancy-attack-on-smart-contracts-how-to-identify-the-exploitable-and-an-example-of-an-attack-4470a2d8dfe4) occurs when the caller is able to obtain more resources than allowed by recursively calling a target’s function. + +### Usage + +Since Cairo does not support modifiers like Solidity, the [ReentrancyGuard](/contracts-cairo/alpha/api/security#ReentrancyGuardComponent) +component exposes two methods `[start](/contracts-cairo/alpha/api/security#ReentrancyGuardComponent-start)` and `[end](/contracts-cairo/alpha/api/security#ReentrancyGuardComponent-end)` to protect functions against reentrancy attacks. +The protected function must call `start` before the first function statement, and `end` before the return statement, as shown below: + +```rust +#[starknet::contract] +mod MyReentrancyContract { + use openzeppelin_security::ReentrancyGuardComponent; + + component!( + path: ReentrancyGuardComponent, storage: reentrancy_guard, event: ReentrancyGuardEvent + ); + + impl InternalImpl = ReentrancyGuardComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + reentrancy_guard: ReentrancyGuardComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ReentrancyGuardEvent: ReentrancyGuardComponent::Event + } + + #[external(v0)] + fn protected_function(ref self: ContractState) { + self.reentrancy_guard.start(); + + // Do something + + self.reentrancy_guard.end(); + } + + #[external(v0)] + fn another_protected_function(ref self: ContractState) { + self.reentrancy_guard.start(); + + // Do something + + self.reentrancy_guard.end(); + } +} +``` + + +The guard prevents the execution flow occurring inside `protected_function` +to call itself or `another_protected_function`, and vice versa. + diff --git a/docs/content/contracts-cairo/alpha/udc.mdx b/docs/content/contracts-cairo/alpha/udc.mdx new file mode 100644 index 00000000..36e358d8 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/udc.mdx @@ -0,0 +1,114 @@ +--- +title: Universal Deployer Contract +--- + +The Universal Deployer Contract (UDC) is a singleton smart contract that wraps the [deploy syscall](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#deploy) to expose it to any contract that doesn’t implement it, such as account contracts. You can think of it as a standardized generic factory for Starknet contracts. + +Since Starknet has no deployment transaction type, it offers a standardized way to deploy smart contracts by following the [Standard Deployer Interface](https://community.starknet.io/t/snip-deployer-contract-interface/2772) and emitting a [ContractDeployed](/contracts-cairo/alpha/api/udc#IUniversalDeployer-ContractDeployed) event. + +For details on the motivation and the decision making process, see the [Universal Deployer Contract proposal](https://community.starknet.io/t/universal-deployer-contract-proposal/1864). + +## UDC contract address + +The UDC is deployed at address `0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125` on Starknet sepolia and mainnet. + +## Interface + +```rust +#[starknet::interface] +pub trait IUniversalDeployer { + fn deploy_contract( + class_hash: ClassHash, + salt: felt252, + not_from_zero: bool, + calldata: Span + ) -> ContractAddress; +} +``` + +## Deploying a contract with the UDC + +First, [declare](https://docs.starknet.io/architecture-and-concepts/network-architecture/transactions/#declare-transaction) the target contract (if it’s not already declared). +Next, call the UDC’s `deploy_contract` method. +Here’s an implementation example in Cairo: + +```rust +use openzeppelin_utils::interfaces::{IUniversalDeployerDispatcher, IUniversalDeployerDispatcherTrait}; + +const UDC_ADDRESS: felt252 = 0x04...; + +fn deploy() -> ContractAddress { + let dispatcher = IUniversalDeployerDispatcher { + contract_address: UDC_ADDRESS.try_into().unwrap() + }; + + // Deployment parameters + let class_hash = class_hash_const::< + 0x5c478ee27f2112411f86f207605b2e2c58cdb647bac0df27f660ef2252359c6 + >(); + let salt = 1234567879; + let not_from_zero = true; + let calldata = array![]; + + // The UDC returns the deployed contract address + dispatcher.deploy_contract(class_hash, salt, not_from_zero, calldata.span()) +} +``` + +## Deployment types + +The Universal Deployer Contract offers two types of addresses to deploy: origin-dependent and origin-independent. +As the names suggest, the origin-dependent type includes the deployer’s address in the address calculation, +whereas, the origin-independent type does not. +The `not_from_zero` boolean parameter ultimately determines the type of deployment. + + + + +When deploying a contract that uses `get_caller_address` in the constructor calldata, remember that the UDC, not the account, deploys that contract. +Therefore, querying `get_caller_address` in a contract’s constructor returns the UDC’s address, _not the account’s address_. + + + +### Origin-dependent + +By making deployments dependent upon the origin address, users can reserve a whole address space to prevent someone else from taking ownership of the address. + +Only the owner of the origin address can deploy to those addresses. + +Achieving this type of deployment necessitates that the origin sets `not_from_zero` to `true` in the [deploy_contract](/contracts-cairo/alpha/api/udc#UniversalDeployer-deploy_contract) call. +Under the hood, the function passes a modified salt to the `deploy_syscall`, which is the hash of the origin’s address with the given salt. + +To deploy a unique contract address pass: + +```js +let deployed_addr = udc.deploy_contract(class_hash, salt, true, calldata.span()); +``` + +### Origin-independent + +Origin-independent contract deployments create contract addresses independent of the deployer and the UDC instance. +Instead, only the class hash, salt, and constructor arguments determine the address. +This type of deployment enables redeployments of accounts and known systems across multiple networks. +To deploy a reproducible deployment, set `not_from_zero` to `false`. + +```rust +let deployed_addr = udc.deploy_contract(class_hash, salt, false, calldata.span()); +``` + +## Version changes + + +See the [previous Universal Deployer API](https://docs.starknet.io/architecture-and-concepts/accounts/#using-the-universal-deployer-contract) for the initial spec. + + +The latest iteration of the UDC includes some notable changes to the API which include: + +* `deployContract` method is replaced with the snake_case [deploy_contract](/contracts-cairo/alpha/api/udc#UniversalDeployer-deploy_contract). +* `unique` parameter is replaced with `not_from_zero` in both the `deploy_contract` method and [ContractDeployed](/contracts-cairo/alpha/api/udc#IUniversalDeployer-ContractDeployed) event. + +## Precomputing contract addresses + +This library offers utility functions written in Cairo to precompute contract addresses. +They include the generic [calculate_contract_address_from_deploy_syscall](/contracts-cairo/alpha/api/utilities#deployments-calculate_contract_address_from_deploy_syscall) as well as the UDC-specific [calculate_contract_address_from_udc](/contracts-cairo/alpha/api/utilities#deployments-calculate_contract_address_from_udc). +Check out the [deployments](/contracts-cairo/alpha/api/utilities#deployments) for more information. diff --git a/docs/content/contracts-cairo/alpha/upgrades.mdx b/docs/content/contracts-cairo/alpha/upgrades.mdx new file mode 100644 index 00000000..01b39c6b --- /dev/null +++ b/docs/content/contracts-cairo/alpha/upgrades.mdx @@ -0,0 +1,127 @@ +--- +title: Upgrades +--- + +In different blockchains, multiple patterns have been developed for making a contract upgradeable including the widely adopted proxy patterns. + +Starknet has native upgradeability through a syscall that updates the contract source code, removing [the need for proxies](#proxies-in-starknet). + + +Make sure you follow [our security recommendations](#security) before upgrading. + + +## Replacing contract classes + +To better comprehend how upgradeability works in Starknet, it’s important to understand the difference between a contract and its contract class. + +[Contract Classes](https://docs.starknet.io/architecture-and-concepts/smart-contracts/contract-classes/) represent the source code of a program. All +contracts are associated to a class, and many contracts can be instances of the same one. Classes are usually represented by +a [class hash](https://docs.starknet.io/architecture-and-concepts/smart-contracts/class-hash/), and before a contract of a class can be deployed, +the class hash needs to be declared. + +### `replace_class_syscall` + +The `[replace_class](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#replace_class)` syscall allows a contract to +update its source code by replacing its class hash once deployed. + +```rust +/// Upgrades the contract source code to the new contract class. +fn upgrade(new_class_hash: ClassHash) { + let CLASS_HASH_CANNOT_BE_ZERO: felt252 = 0x1; + assert(!new_class_hash.is_zero(), CLASS_HASH_CANNOT_BE_ZERO); + starknet::replace_class_syscall(new_class_hash).unwrap_syscall(); +} +``` + + +If a contract is deployed without this mechanism, its class hash can still be replaced through [library calls](https://docs.starknet.io/architecture-and-concepts/smart-contracts/system-calls-cairo1/#library_call). + + +## `Upgradeable` component + +OpenZeppelin Contracts for Cairo provides [Upgradeable](https://github.com/OpenZeppelin/cairo-contracts/blob/release-v{{umbrella_version}}/packages/upgrades/src/upgradeable.cairo) to add upgradeability support to your contracts. + +### Usage + +Upgrades are often very sensitive operations, and some form of access control is usually required to +avoid unauthorized upgrades. The [Ownable](./access#ownership-and-ownable) module is used in this example. + + +We will be using the following module to implement the [IUpgradeable](/contracts-cairo/alpha/api/upgrades#IUpgradeable) interface described in the API Reference section. + + +```rust +#[starknet::contract] +mod UpgradeableContract { + use openzeppelin_access::ownable::OwnableComponent; + use openzeppelin_upgrades::UpgradeableComponent; + use openzeppelin_interfaces::upgrades::IUpgradeable; + use starknet::ClassHash; + use starknet::ContractAddress; + + component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); + component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + + // Ownable Mixin + #[abi(embed_v0)] + impl OwnableMixinImpl = OwnableComponent::OwnableMixinImpl; + impl OwnableInternalImpl = OwnableComponent::InternalImpl; + + // Upgradeable + impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: OwnableComponent::Storage, + #[substorage(v0)] + upgradeable: UpgradeableComponent::Storage + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + OwnableEvent: OwnableComponent::Event, + #[flat] + UpgradeableEvent: UpgradeableComponent::Event + } + + #[constructor] + fn constructor(ref self: ContractState, owner: ContractAddress) { + self.ownable.initializer(owner); + } + + #[abi(embed_v0)] + impl UpgradeableImpl of IUpgradeable { + fn upgrade(ref self: ContractState, new_class_hash: ClassHash) { + // This function can only be called by the owner + self.ownable.assert_only_owner(); + + // Replace the class hash upgrading the contract + self.upgradeable.upgrade(new_class_hash); + } + } +} +``` + +## Security + +Upgrades can be very sensitive operations, and security should always be top of mind while performing one. Please make sure you thoroughly review the changes and their consequences before upgrading. Some aspects to consider are: + +* API changes that might affect integration. For example, changing an external function’s arguments might break existing contracts or offchain systems calling your contract. +* Storage changes that might result in lost data (e.g. changing a storage slot name, making existing storage inaccessible). +* Collisions (e.g. mistakenly reusing the same storage slot from another component) are also possible, although less likely if best practices are followed, for example prepending storage variables with the component’s name (e.g. `ERC20_balances`). +* Always check for [backwards compatibility](./backwards-compatibility) before upgrading between versions of OpenZeppelin Contracts. + +## Proxies in Starknet + +Proxies enable different patterns such as upgrades and clones. But since Starknet achieves the same in different ways is that there’s no support to implement them. + +In the case of contract upgrades, it is achieved by simply changing the contract’s class hash. As of clones, contracts already are like clones of the class they implement. + +Implementing a proxy pattern in Starknet has an important limitation: there is no fallback mechanism to be used +for redirecting every potential function call to the implementation. This means that a generic proxy contract +can’t be implemented. Instead, a limited proxy contract can implement specific functions that forward +their execution to another contract class. +This can still be useful for example to upgrade the logic of some functions. diff --git a/docs/content/contracts-cairo/alpha/utils/constants.js b/docs/content/contracts-cairo/alpha/utils/constants.js new file mode 100644 index 00000000..d3ef174d --- /dev/null +++ b/docs/content/contracts-cairo/alpha/utils/constants.js @@ -0,0 +1,21 @@ +export const OPENZEPPELIN_INTERFACES_VERSION = "2.1.0-alpha.0"; +export const OPENZEPPELIN_UTILS_VERSION = "2.1.0-alpha.0"; +export const UMBRELLA_VERSION = "3.0.0-alpha.3"; +export const CLASS_HASH_SCARB_VERSION = "2.12.2"; + +export const CLASS_HASHES = { + AccountUpgradeableClassHash: + "0x072b2479c3bf45bfc2391b0a04bb0fb4806b93a61a8fede391081535cad65038", + ERC20UpgradeableClassHash: + "0x0435835a8002b39bf6eb827678b32a75ed3e0bec580ef71a7c29a068d1a96d24", + ERC721UpgradeableClassHash: + "0x062212af6bc24e478b5f1c611d2f626270e3ef5825330173afa3e02a0848bcaa", + ERC1155UpgradeableClassHash: + "0x01a312230aa2774b3271204bfd41e8633d3b2ab48f10f4b56be1ef17806599c4", + EthAccountUpgradeableClassHash: + "0x071fa21092599f6ffdaed5da83961d895668c668a488465251d88606c26a34b7", + UniversalDeployerClassHash: + "0x038a75a9c5a0203e5fa94bb181850a4e6a349fc3fcda6a0ccbcaeb5c2f50c7c3", + VestingWalletClassHash: + "0x03e7d05eb2325c2e10d219f6b70468aef9e915352ac31521ec2950ebf0e3acb4", +}; diff --git a/docs/content/contracts-cairo/alpha/utils/replacements.ts b/docs/content/contracts-cairo/alpha/utils/replacements.ts new file mode 100644 index 00000000..32791a2b --- /dev/null +++ b/docs/content/contracts-cairo/alpha/utils/replacements.ts @@ -0,0 +1,12 @@ +import { CLASS_HASHES, CLASS_HASH_SCARB_VERSION, OPENZEPPELIN_INTERFACES_VERSION, OPENZEPPELIN_UTILS_VERSION, UMBRELLA_VERSION } from "./constants"; + +export const REPLACEMENTS = { + include: ['**/content/contracts-cairo/alpha/**/*.mdx'], + replacements: { + umbrella_version: UMBRELLA_VERSION, + openzeppelin_interfaces_version: OPENZEPPELIN_INTERFACES_VERSION, + openzeppelin_utils_version: OPENZEPPELIN_UTILS_VERSION, + class_hash_scarb_version: CLASS_HASH_SCARB_VERSION, + ...CLASS_HASHES, + } +} diff --git a/docs/content/contracts-cairo/alpha/wizard.mdx b/docs/content/contracts-cairo/alpha/wizard.mdx new file mode 100644 index 00000000..7b07a3a3 --- /dev/null +++ b/docs/content/contracts-cairo/alpha/wizard.mdx @@ -0,0 +1,14 @@ +--- +title: Wizard for Cairo +--- + +Not sure where to start? Use the interactive generator below to bootstrap your +contract and learn about the components offered in OpenZeppelin Contracts for Cairo. + + +We strongly recommend checking the [Components](./components) section to understand how to extend from our library. + + +import { UMBRELLA_VERSION } from "./utils/constants.js"; + + diff --git a/docs/content/contracts-cairo/cairo-replacements.ts b/docs/content/contracts-cairo/cairo-replacements.ts new file mode 100644 index 00000000..c6f03ba3 --- /dev/null +++ b/docs/content/contracts-cairo/cairo-replacements.ts @@ -0,0 +1,4 @@ +import { REPLACEMENTS as alphaReplacements } from "./alpha/utils/replacements"; +import { REPLACEMENTS as twoXReplacements } from "./2.x/utils/replacements"; + +export const CAIRO_REPLACEMENTS = [alphaReplacements, twoXReplacements]; diff --git a/docs/content/contracts-cairo/index.mdx b/docs/content/contracts-cairo/index.mdx new file mode 100644 index 00000000..03a2ffad --- /dev/null +++ b/docs/content/contracts-cairo/index.mdx @@ -0,0 +1,89 @@ +--- +title: Contracts for Cairo +--- + +import { latestAlpha, latestStable } from "./latest-versions.js"; + +**OpenZeppelin Contracts for Cairo** provides modular, reusable, and audited smart-contract building blocks for Starknet. The library ships with ready-to-use components, presets, utilities, and tooling that help you ship production-grade Cairo applications faster. + +## Getting Started + + + + Explore the stable and audited documentation set, including installation, components, and production-ready guides. + + + Try the upcoming features, updated APIs, and experimental packages before they are audited, available in the alpha release track. + + + Bootstrap Cairo contracts with the interactive Wizard and learn how features compose across packages. + + + +## Core Features + + + + Reusable building blocks for composing Cairo contracts with mixins and modular architecture. + + + Ready-to-deploy contract presets for common Starknet scenarios. + + + Manage permissions and roles for Cairo contracts with flexible access-control patterns. + + + Harden your contracts with patterns and modules designed to reduce common vulnerabilities. + + + +## Token Standards + + + + Implement fungible tokens with hooks, minting, and allowance helpers adapted for Starknet. + + + Build non-fungible tokens with metadata, enumeration, and minting extensions. + + + Support multi-token collections that mix fungible and non-fungible assets. + + + Create tokenized vaults that integrate with Starknet DeFi protocols. + + + +## Advanced Features + + + + Work with smart accounts, multicalls, and account-upgrade flows tailored for Starknet. + + + Design upgradeable patterns and learn how to manage storage-safe contract evolution. + + + Deploy contracts deterministically using the Universal Deployer Contract. + + + Build Starknet-native governance flows with governor, timelock, multisig, and voting modules. + + + +## API Reference + + + + Inspect the full access-control interface, events, and helper methods. + + + Explore the ERC-20 token standard interface, events, and helper methods for fungible tokens. + + + Learn the low-level traits and components that power upgradeable contracts in Cairo. + + + Browse helper libraries, testing utilities, and dispatchers available to every package. + + diff --git a/docs/content/contracts-cairo/latest-versions.js b/docs/content/contracts-cairo/latest-versions.js new file mode 100644 index 00000000..ab44c70c --- /dev/null +++ b/docs/content/contracts-cairo/latest-versions.js @@ -0,0 +1,4 @@ +import { UMBRELLA_VERSION as latestAlphaVersion } from "./alpha/utils/constants.js"; + +export const latestStable = "2.x"; +export const latestAlpha = latestAlphaVersion; diff --git a/docs/content/contracts-compact/accessControl.mdx b/docs/content/contracts-compact/accessControl.mdx new file mode 100644 index 00000000..b7f4275f --- /dev/null +++ b/docs/content/contracts-compact/accessControl.mdx @@ -0,0 +1,231 @@ +--- +title: AccessControl +--- + +{/* links */} +[Role-Based Access Control (RBAC)]: https://en.wikipedia.org/wiki/Role-based_access_control +[principle of least privilege]: https://en.wikipedia.org/wiki/Principle_of_least_privilege + +[FungibleToken]: ./fungibleToken.mdx +[Ownable]: ./ownable.mdx +[Initializable]: ./security#initializable +[AccessControl]: api/accessControl + +[assertOnlyRole]: api/accessControl#AccessControl-assertOnlyRole +[grantRole]: api/accessControl#AccessControl-grantRole +[_grantRole]: api/accessControl#AccessControl-_grantRole +[_unsafeGrantRole]: api/accessControl#AccessControl-_unsafeGrantRole +[revokeRole]: api/accessControl#AccessControl-revokeRole +[_setRoleAdmin]: api/accessControl#AccessControl-_setRoleAdmin + +This module provides a role-based access control mechanism, +where roles can be used to represent a set of permissions providing the flexibility to create different levels of account authorization. + +Roles can be enforced using the [assertOnlyRole] circuit. +Separately, you will be able to define rules for how accounts can be granted a role, have it revoked, and more. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. +[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 `assertOnlyOwner`. +This check can be enforced through the [assertOnlyRole] circuit. +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 `AccessControl` + +The Compact contracts library provides `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` with [FungibleToken] to define a 'minter' role, +which allows accounts that have this role to create new tokens: + +```ts +pragma language_version >= {{compact_language_version}}; + +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" + prefix AccessControl_; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" + prefix FungibleToken_; + +export sealed ledger MINTER_ROLE: Bytes<32>; + +/** + * Initialize FungibleToken and MINTER_ROLE + */ +constructor( + name: Opaque<"string">, + symbol: Opaque<"string">, + decimals: Uint<8>, + minter: Either +) { + FungibleToken_initialize(name, symbol, decimals); + MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE")); + AccessControl__grantRole(MINTER_ROLE, minter); +} + +export circuit mint(recipient: Either, value: Uint<128>): [] { + AccessControl_assertOnlyRole(MINTER_ROLE); + FungibleToken__mint(recipient, value); +} + +``` + +Make sure you fully understand how [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 FungibleToken example by also defining a 'burner' role, which lets accounts destroy tokens. + +```ts +pragma language_version >= {{compact_language_version}}; + +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" + prefix AccessControl_; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" + prefix FungibleToken_; + +export sealed ledger MINTER_ROLE: Bytes<32>; +export sealed ledger BURNER_ROLE: Bytes<32>; + +/** + * Initialize FungibleToken and MINTER_ROLE + */ +constructor( + name: Opaque<"string">, + symbol: Opaque<"string">, + decimals: Uint<8>, + minter: Either, + burner: Either +) { + FungibleToken_initialize(name, symbol, decimals); + MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE")); + BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE")); + AccessControl__grantRole(MINTER_ROLE, minter); + AccessControl__grantRole(BURNER_ROLE, burner); +} + +export circuit mint(recipient: Either, value: Uint<128>): [] { + AccessControl_assertOnlyRole(MINTER_ROLE); + FungibleToken__mint(recipient, value); +} + +export circuit burn(recipient: Either, value: Uint<128>): [] { + AccessControl_assertOnlyRole(BURNER_ROLE); + FungibleToken__burn(recipient, value); +} + +``` + +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 [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 Roles + +The FungibleToken example above uses [_grantRole], +an internal circuit 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] circuits. +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. + +Let’s take a look at the FungibleToken example, this time taking advantage of the default admin role: + +```ts +pragma language_version >= {{compact_language_version}}; + +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" + prefix AccessControl_; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" + prefix FungibleToken_; + +export sealed ledger MINTER_ROLE: Bytes<32>; +export sealed ledger BURNER_ROLE: Bytes<32>; + +/** + * Initialize FungibleToken and MINTER_ROLE + */ +constructor( + name: Opaque<"string">, + symbol: Opaque<"string">, + decimals: Uint<8>, +) { + FungibleToken_initialize(name, symbol, decimals); + MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE")); + BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE")); + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + AccessControl__grantRole(AccessControl_DEFAULT_ADMIN_ROLE, left(ownPublicKey())); +} + +export circuit mint(recipient: Either, value: Uint<128>): [] { + AccessControl_assertOnlyRole(MINTER_ROLE); + FungibleToken__mint(recipient, value); +} + +export circuit burn(recipient: Either, value: Uint<128>): [] { + AccessControl_assertOnlyRole(BURNER_ROLE); + FungibleToken__burn(recipient, value); +} + +``` + +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 `ownPublicKey()`, +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 KYC, +where the list of role-bearers may not be known up-front, +or may be prohibitively expensive to include in a single transaction. + +### Experimental features + +This module offers an experimental circuit that allow access control permissions to be granted to contract addresses [_unsafeGrantRole]. + +Note that the circuit name is very explicit ("unsafe") with this experimental circuit. +Until contract-to-contract calls are supported, +there is no direct way for a contract to call permissioned circuits of other contracts or grant/revoke role permissions. + + +The unsafe circuits are planned to become deprecated once contract-to-contract calls become available. + diff --git a/docs/content/contracts-compact/api/accessControl.mdx b/docs/content/contracts-compact/api/accessControl.mdx new file mode 100644 index 00000000..37d55a26 --- /dev/null +++ b/docs/content/contracts-compact/api/accessControl.mdx @@ -0,0 +1,284 @@ +--- +title: AccessControl API +--- + +This page provides the full AccessControl module API. + +Roles are referred to by their `Bytes<32>` identifier. +These should be exposed in the top-level contract and be unique. +The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract: + +```typescript +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" + prefix AccessControl_; + +export sealed ledger MY_ROLE: Bytes<32>; + +constructor() { + MY_ROLE = persistentHash>(pad(32, "MY_ROLE")); +} +``` + +To restrict access to a circuit, use [assertOnlyRole](#AccessControl-assertOnlyRole): + + +```typescript +circuit foo(): [] { + assertOnlyRole(MY_ROLE); +} +``` + +Roles can be granted and revoked dynamically via the [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole) circuits. +Each role has an associated admin role, +and only accounts that have a role’s admin role can call [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole). + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, +which means that only accounts with this role will be able to grant or revoke other roles. +More complex role relationships can be created by using [_setRoleAdmin](#AccessControl-_setRoleAdmin). +To set a custom `DEFAULT_ADMIN_ROLE`, +implement the `Initializable` module and set `DEFAULT_ADMIN_ROLE` in the `initialize()` circuit. + + + The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role. + Extra precautions should be taken to secure accounts that have been granted it. + + + + For an overview of the module, read the [AccessControl guide](../accessControl). + + +## AccessControl [toc] [#AccessControl] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/AccessControl"; +``` + +--- + +### Ledger [toc] [#AccessControl-Ledger] +### Ledger [!toc] [#AccessControl-Ledger] + +#### _operatorRoles [toc] [#AccessControl-_operatorRoles] +#### [!toc] [#AccessControl-_operatorRoles] + + Mapping from a role identifier -> account -> its permissions. + + +#### _adminRoles [toc] [#AccessControl-_adminRoles] +#### [!toc] [#AccessControl-_adminRoles] + + Mapping from a role identifier to an admin role identifier. + + +#### DEFAULT_ADMIN_ROLE [toc] [#AccessControl-DEFAULT_ADMIN_ROLE] +#### [!toc] [#AccessControl-DEFAULT_ADMIN_ROLE] + + The default `Bytes<32>` value mimicking a constant. + + +### Witnesses [toc] [#AccessControl-Witnesses] +### Witnesses [!toc] [#AccessControl-Witnesses] + +None. + +### Circuits [toc] [#AccessControl-Circuits] +### Circuits [!toc] [#AccessControl-Circuits] + +#### hasRole [toc] [#AccessControl-hasRole] +#### [!toc] [#AccessControl-hasRole] + + Returns `true` if `account` has been granted `roleId`. + + +#### assertOnlyRole [toc] [#AccessControl-assertOnlyRole] +#### [!toc] [#AccessControl-assertOnlyRole] + + Reverts if caller is missing `roleId`. + + Requirements: + + * The caller must have `roleId`. + * The caller must not be a `ContractAddress`. + + +#### _checkRole [toc] [#AccessControl-_checkRole] +#### [!toc] [#AccessControl-_checkRole] + + Reverts if `account` is missing `roleId`. + + Requirements: + + * `account` must have `roleId`. + + +#### getRoleAdmin [toc] [#AccessControl-getRoleAdmin] +#### [!toc] [#AccessControl-getRoleAdmin] + + Returns the admin role that controls `roleId` or a byte array with all zero bytes if `roleId` doesn’t exist. + See [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole). + + To change a role's admin use [_setRoleAdmin](#AccessControl-_setRoleAdmin). + + +#### grantRole [toc] [#AccessControl-grantRole] +#### [!toc] [#AccessControl-grantRole] + + Grants `roleId` to `account`. + + + Granting roles to contract addresses is currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents permanently disabling access to a circuit. + + + Requirements: + + * `account` must not be a ContractAddress. + * The caller must have `roleId`’s admin role. + + +#### revokeRole [toc] [#AccessControl-revokeRole] +#### [!toc] [#AccessControl-revokeRole] + + Revokes `roleId` from `account`. + + Requirements: + + * The caller must have `roleId`’s admin role. + + +#### renounceRole [toc] [#AccessControl-renounceRole] +#### [!toc] [#AccessControl-renounceRole] + + Revokes `roleId` from the calling account. + + Roles are often managed via [grantRole](#AccessControl-grantRole) and [revokeRole](#AccessControl-revokeRole): + this circuit’s purpose is to provide a mechanism for accounts to lose their privileges + if they are compromised (such as when a trusted device is misplaced). + + + We do not provide functionality for smart contracts to renounce roles because self-executing transactions are not supported on Midnight at this time. + We may revisit this in future if this feature is made available in Compact. + + + Requirements: + + * The caller must be `callerConfirmation`. + * The caller must not be a `ContractAddress`. + + +#### _setRoleAdmin [toc] [#AccessControl-_setRoleAdmin] +#### [!toc] [#AccessControl-_setRoleAdmin] + + Sets `adminRole` as `roleId`’s admin role. + + +#### _grantRole [toc] [#AccessControl-_grantRole] +#### [!toc] [#AccessControl-_grantRole] + + Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. + + Internal circuit without access restriction. + + + Granting roles to contract addresses is currently disallowed in this circuit until contract-to-contract interactions are supported in Compact. + This restriction prevents permanently disabling access to a circuit. + + + Requirements: + + * `account` must not be a ContractAddress. + + +#### _unsafeGrantRole [toc] [#AccessControl-_unsafeGrantRole] +#### [!toc] [#AccessControl-_unsafeGrantRole] + + Unsafe variant of [_grantRole](#AccessControl-_grantRole). + + + Granting roles to contract addresses is considered unsafe because contract-to-contract calls are not currently supported. + Granting a role to a smart contract may render a circuit permanently inaccessible. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + +#### _revokeRole [toc] [#AccessControl-_revokeRole] +#### [!toc] [#AccessControl-_revokeRole] + + Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. + + Internal circuit without access restriction. + diff --git a/docs/content/contracts-compact/api/fungibleToken.mdx b/docs/content/contracts-compact/api/fungibleToken.mdx new file mode 100644 index 00000000..3778a0c0 --- /dev/null +++ b/docs/content/contracts-compact/api/fungibleToken.mdx @@ -0,0 +1,512 @@ +--- +title: FungibleToken API +--- + +This module provides the full FungibleToken module API. + + + For an overview of the module, read the [FungibleToken guide](../fungibleToken). + + +## FungibleToken [toc] [#FungibleToken] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"; +``` + +--- + +### Ledger [toc] [#FungibleToken-Ledger] +### Ledger [!toc] [#FungibleToken-Ledger] + +#### _balances [toc] [#FungibleToken-_balances] +#### [!toc] [#FungibleToken-_balances] + + Mapping from account addresses to their token balances. + + +#### _allowances [toc] [#FungibleToken-_allowances] +#### [!toc] [#FungibleToken-_allowances] + + Mapping from owner accounts to spender accounts to their allowances. + + +#### _totalSupply [toc] [#FungibleToken-_totalSupply] +#### [!toc] [#FungibleToken-_totalSupply] + + The total token supply. + + +#### _name [toc] [#FungibleToken-_name] +#### [!toc] [#FungibleToken-_name] + + The immutable token name. + + +#### _symbol [toc] [#FungibleToken-_symbol] +#### [!toc] [#FungibleToken-_symbol] + + The immutable token symbol. + + +#### _decimals [toc] [#FungibleToken-_decimals] +#### [!toc] [#FungibleToken-_decimals] + + The immutable token decimals. + + +### Witnesses [toc] [#FungibleToken-Witnesses] +### Witnesses [!toc] [#FungibleToken-Witnesses] + +None. + +### Circuits [toc] [#FungibleToken-Circuits] +### Circuits [!toc] [#FungibleToken-Circuits] + +#### initialize [toc] [#FungibleToken-initialize] +#### [!toc] [#FungibleToken-initialize] + + Initializes the contract by setting the name, symbol, and decimals. + + This MUST be called in the implementing contract’s constructor. + Failure to do so can lead to an irreparable contract. + + Requirements: + + * Contract is not initialized. + + +#### name [toc] [#FungibleToken-name] +#### [!toc] [#FungibleToken-name] + + Returns the token name. + + Requirements: + + * Contract is initialized. + + +#### symbol [toc] [#FungibleToken-symbol] +#### [!toc] [#FungibleToken-symbol] + + Returns the symbol of the token. + + Requirements: + + * Contract is initialized. + + +#### decimals [toc] [#FungibleToken-decimals] +#### [!toc] [#FungibleToken-decimals] + + Returns the number of decimals used to get its user representation. + + Requirements: + + * Contract is initialized. + + +#### totalSupply [toc] [#FungibleToken-totalSupply] +#### [!toc] [#FungibleToken-totalSupply] + + Returns the value of tokens in existence. + + Requirements: + + * Contract is initialized. + + +#### balanceOf [toc] [#FungibleToken-balanceOf] +#### [!toc] [#FungibleToken-balanceOf] + + Returns the value of tokens owned by `account`. + + Requirements: + + * Contract is initialized. + + +#### transfer [toc] [#FungibleToken-transfer] +#### [!toc] [#FungibleToken-transfer] + + Moves a `value` amount of tokens from the caller’s account to `to`. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * Contract is initialized. + * `to` is not a ContractAddress. + * `to` is not the zero address. + * The caller has a balance of at least `value`. + + +#### _unsafeTransfer [toc] [#FungibleToken-_unsafeTransfer] +#### [!toc] [#FungibleToken-_unsafeTransfer] + + Unsafe variant of [transfer](#FungibleToken-transfer) which allows transfers to contract addresses. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `to` is not the zero address. + * The caller has a balance of at least `value`. + + +#### allowance [toc] [#FungibleToken-allowance] +#### [!toc] [#FungibleToken-allowance] + + Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through [transferFrom](#FungibleToken-transferFrom). + This value changes when [approve](#FungibleToken-approve) or [transferFrom](#FungibleToken-transferFrom) are called. + + Requirements: + + * Contract is initialized. + + +#### approve [toc] [#FungibleToken-approve] +#### [!toc] [#FungibleToken-approve] + + Sets a `value` amount of tokens as allowance of `spender` over the caller’s tokens. + + Requirements: + + * Contract is initialized. + * `spender` is not the zero address. + + +#### transferFrom [toc] [#FungibleToken-transferFrom] +#### [!toc] [#FungibleToken-transferFrom] + + Moves `value` tokens from `from` to `to` using the allowance mechanism. + `value` is then deducted from the caller’s allowance. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * Contract is initialized. + * `from` is not the zero address. + * `from` must have a balance of at least `value`. + * `to` is not the zero address. + * `to` is not a ContractAddress. + * The caller has an allowance of `from`’s tokens of at least `value`. + + +#### _unsafeTransferFrom [toc] [#FungibleToken-_unsafeTransferFrom] +#### [!toc] [#FungibleToken-_unsafeTransferFrom] + + Unsafe variant of [transferFrom](#FungibleToken-transferFrom) which allows transfers to contract addresses. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `from` is not the zero address. + * `from` must have a balance of at least `value`. + * `to` is not the zero address. + * The caller has an allowance of `from`’s tokens of at least `value`. + + +#### _transfer [toc] [#FungibleToken-_transfer] +#### [!toc] [#FungibleToken-_transfer] + + Moves a `value` amount of tokens from `from` to `to`. + This circuit is equivalent to [transfer](#FungibleToken-transfer), and can be used to e.g. + implement automatic token fees, slashing mechanisms, etc. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * Contract is initialized. + * `from` is not the zero address. + * `from` must have at least a balance of `value`. + * `to` must not be the zero address. + * `to` must not be a ContractAddress. + + +#### _unsafeUncheckedTransfer [toc] [#FungibleToken-_unsafeUncheckedTransfer] +#### [!toc] [#FungibleToken-_unsafeUncheckedTransfer] + + Unsafe variant of [_transfer](#FungibleToken-_transfer) which allows transfers to contract addresses. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `from` is not the zero address. + * `to` is not the zero address. + + +#### _update [toc] [#FungibleToken-_update] +#### [!toc] [#FungibleToken-_update] + + Transfers a `value` amount of tokens from `from` to `to`, + or alternatively mints (or burns) if `from` (or `to`) is the zero address. + + Requirements: + + * Contract is initialized. + + +#### _mint [toc] [#FungibleToken-_mint] +#### [!toc] [#FungibleToken-_mint] + + Creates a `value` amount of tokens and assigns them to `account`, by transferring it from the zero address. + Relies on the `update` mechanism. + + Requirements: + + * Contract is initialized. + * `to` is not a ContractAddress. + * `account` is not the zero address. + + +#### _unsafeMint [toc] [#FungibleToken-_unsafeMint] +#### [!toc] [#FungibleToken-_unsafeMint] + + Unsafe variant of [_mint](#FungibleToken-_mint) which allows transfers to contract addresses. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `account` is not the zero address. + + +#### _burn [toc] [#FungibleToken-_burn] +#### [!toc] [#FungibleToken-_burn] + + Destroys a `value` amount of tokens from `account`, lowering the total supply. + Relies on the `_update` mechanism. + + Requirements: + + * Contract is initialized. + * `account` is not the zero address. + * `account` must have at least a balance of `value`. + + +#### _approve [toc] [#FungibleToken-_approve] +#### [!toc] [#FungibleToken-_approve] + + Sets `value` as the allowance of `spender` over the `owner`’s tokens. + This circuit is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain subsystems, etc. + + Requirements: + + * Contract is initialized. + * `owner` is not the zero address. + * `spender` is not the zero address. + + +#### _spendAllowance [toc] [#FungibleToken-_spendAllowance] +#### [!toc] [#FungibleToken-_spendAllowance] + + Updates `owner`’s allowance for `spender` based on spent `value`. + Does not update the allowance value in case of infinite allowance. + + Requirements: + + * Contract is initialized. + * `spender` must have at least an allowance of `value` from `owner`. + diff --git a/docs/content/contracts-compact/api/multitoken.mdx b/docs/content/contracts-compact/api/multitoken.mdx new file mode 100644 index 00000000..3f0cc9e6 --- /dev/null +++ b/docs/content/contracts-compact/api/multitoken.mdx @@ -0,0 +1,373 @@ +--- +title: MultiToken API +--- + +This module provides the full MultiToken module API. + + + For an overview of the module, read the [MultiToken guide](../multitoken). + + +## MultiToken [toc] [#MultiToken] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/MultiToken"; +``` + +--- + +### Ledger [toc] [#MultiToken-Ledger] +### Ledger [!toc] [#MultiToken-Ledger] + +#### _balances [toc] [#MultiToken-_balances] +#### [!toc] [#MultiToken-_balances] + + Mapping from token ID to account balances. + + +#### _operatorApprovals [toc] [#MultiToken-_operatorApprovals] +#### [!toc] [#MultiToken-_operatorApprovals] + + Mapping from account to operator approvals. + + +#### _uri [toc] [#MultiToken-_uri] +#### [!toc] [#MultiToken-_uri] + + Base URI for computing token URIs. + + +### Witnesses [toc] [#MultiToken-Witnesses] +### Witnesses [!toc] [#MultiToken-Witnesses] + +None. + +### Circuits [toc] [#MultiToken-Circuits] +### Circuits [!toc] [#MultiToken-Circuits] + +#### initialize [toc] [#MultiToken-initialize] +#### [!toc] [#MultiToken-initialize] + + Initializes the contract by setting the base URI for all tokens. + + This MUST be called in the implementing contract’s constructor. + Failure to do so can lead to an irreparable contract. + + Requirements: + + * Contract is not initialized. + + +#### uri [toc] [#MultiToken-uri] +#### [!toc] [#MultiToken-uri] + + This implementation returns the same URI for **all** token types. + It relies on the token type ID substitution mechanism defined in the EIP: [ERC1155-Metadata](https://eips.ethereum.org/EIPS/eip-1155#metadata). + Clients calling this function must replace the `\id\` substring with the actual token type ID. + + Requirements: + + * Contract is initialized. + + +#### balanceOf [toc] [#MultiToken-balanceOf] +#### [!toc] [#MultiToken-balanceOf] + + Returns the amount of `id` tokens owned by `account`. + + Requirements: + + * Contract is initialized. + + +#### setApprovalForAll [toc] [#MultiToken-setApprovalForAll] +#### [!toc] [#MultiToken-setApprovalForAll] + + Enables or disables approval for `operator` to manage all of the caller’s assets. + + Requirements: + + * Contract is initialized. + * `operator` is not the zero address. + + +#### isApprovedForAll [toc] [#MultiToken-isApprovedForAll] +#### [!toc] [#MultiToken-isApprovedForAll] + + Queries if `operator` is an authorized operator for `owner`. + + Requirements: + + * Contract is initialized. + + +#### transferFrom [toc] [#MultiToken-transferFrom] +#### [!toc] [#MultiToken-transferFrom] + + Transfers ownership of `value` amount of `id` tokens from `from` to `to`. + The caller must be `from` or approved to transfer on their behalf. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * Contract is initialized. + * `to` is not a ContractAddress. + * `to` is not the zero address. + * `from` is not the zero address. + * Caller must be `from` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll). + * `from` must have an `id` balance of at least `value`. + + +#### _transfer [toc] [#MultiToken-_transfer] +#### [!toc] [#MultiToken-_transfer] + + Transfers ownership of `value` amount of `id` tokens from `from` to `to`. + Does not impose restrictions on the caller, making it suitable for composition in higher-level contract logic. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * Contract is initialized. + * `to` is not a ContractAddress. + * `to` is not the zero address. + * `from` is not the zero address. + * `from` must have an `id` balance of at least `value`. + + +#### _update [toc] [#MultiToken-_update] +#### [!toc] [#MultiToken-_update] + + Transfers a value amount of tokens of type id from from to to. + This circuit will mint (or burn) if `from` (or `to`) is the zero address. + + Requirements: + + * Contract is initialized. + * If `from` is not zero, the balance of `id` of `from` must be >= `value`. + + +#### _unsafeTransferFrom [toc] [#MultiToken-_unsafeTransferFrom] +#### [!toc] [#MultiToken-_unsafeTransferFrom] + + Unsafe variant of [transferFrom](#MultiToken-transferFrom) which allows transfers to contract addresses. + The caller must be `from` or approved to transfer on their behalf. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `to` is not the zero address. + * `from` is not the zero address. + * Caller must be `from` or approved via [setApprovalForAll](#MultiToken-setApprovalForAll). + * `from` must have an `id` balance of at least `value`. + + +#### _unsafeTransfer [toc] [#MultiToken-_unsafeTransfer] +#### [!toc] [#MultiToken-_unsafeTransfer] + + Unsafe variant of [_transfer](#MultiToken-_transfer) which allows transfers to contract addresses. + Does not impose restrictions on the caller, making it suitable as a low-level building block for advanced contract logic. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `from` is not the zero address. + * `to` is not the zero address. + * `from` must have an `id` balance of at least `value`. + + +#### _setURI [toc] [#MultiToken-_setURI] +#### [!toc] [#MultiToken-_setURI] + + Sets a new URI for all token types, by relying on the token type ID substitution mechanism defined in the MultiToken standard. + See https://eips.ethereum.org/EIPS/eip-1155#metadata. + + By this mechanism, any occurrence of the `\id\` substring + in either the URI or any of the values in the JSON file at said URI will be replaced by clients with the token type ID. + + For example, the `https://token-cdn-domain/\id\.json` URI would be interpreted by clients as + `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` for token type ID 0x4cce0. + + Requirements: + + * Contract is initialized. + + +#### _mint [toc] [#MultiToken-_mint] +#### [!toc] [#MultiToken-_mint] + + Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * Contract is initialized. + * `to` is not the zero address. + * `to` is not a ContractAddress + + +#### _unsafeMint [toc] [#MultiToken-_unsafeMint] +#### [!toc] [#MultiToken-_unsafeMint] + + Unsafe variant of `_mint` which allows transfers to contract addresses. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `to` is not the zero address. + + +#### _burn [toc] [#MultiToken-_burn] +#### [!toc] [#MultiToken-_burn] + + Destroys a `value` amount of tokens of type `token_id` from `from`. + + Requirements: + + * Contract is initialized. + * `from` is not the zero address. + * `from` must have an `id` balance of at least `value`. + + +#### _setApprovalForAll [toc] [#MultiToken-_setApprovalForAll] +#### [!toc] [#MultiToken-_setApprovalForAll] + + Enables or disables approval for `operator` to manage all of the caller’s assets. + This circuit does not check for access permissions but can be useful as a building block for more complex contract logic. + + Requirements: + + * Contract is initialized. + * `operator` is not the zero address. + diff --git a/docs/content/contracts-compact/api/nonFungibleToken.mdx b/docs/content/contracts-compact/api/nonFungibleToken.mdx new file mode 100644 index 00000000..ad6e7724 --- /dev/null +++ b/docs/content/contracts-compact/api/nonFungibleToken.mdx @@ -0,0 +1,597 @@ +--- +title: NonFungibleToken API +--- + +This module provides the full NonFungibleToken module API. + + + For an overview of the module, read the [NonFungibleToken guide](../nonFungibleToken). + + +## NonFungibleToken [toc] [#NonFungibleToken] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken"; +``` + +--- + +### Ledger [toc] [#NonFungibleToken-Ledger] +### Ledger [!toc] [#NonFungibleToken-Ledger] + +#### _name [toc] [#NonFungibleToken-_name] +#### [!toc] [#NonFungibleToken-_name] + + The immutable token name. + + +#### _symbol [toc] [#NonFungibleToken-_symbol] +#### [!toc] [#NonFungibleToken-_symbol] + + The immutable token symbol. + + +#### _owners [toc] [#NonFungibleToken-_owners] +#### [!toc] [#NonFungibleToken-_owners] + + Mapping from token IDs to their owner addresses. + + +#### _balances [toc] [#NonFungibleToken-_balances] +#### [!toc] [#NonFungibleToken-_balances] + + Mapping from account addresses to their token balances. + + +#### _tokenApprovals [toc] [#NonFungibleToken-_tokenApprovals] +#### [!toc] [#NonFungibleToken-_tokenApprovals] + + Mapping from token IDs to approved addresses. + + +#### _operatorApprovals [toc] [#NonFungibleToken-_operatorApprovals] +#### [!toc] [#NonFungibleToken-_operatorApprovals] + + Mapping from owner addresses to operator approvals. + + +#### _tokenURIs [toc] [#NonFungibleToken-_tokenURIs] +#### [!toc] [#NonFungibleToken-_tokenURIs] + + Mapping from token IDs to their metadata URIs. + + +### Witnesses [toc] [#NonFungibleToken-Witnesses] +### Witnesses [!toc] [#NonFungibleToken-Witnesses] + +None. + +### Circuits [toc] [#NonFungibleToken-Circuits] +### Circuits [!toc] [#NonFungibleToken-Circuits] + +#### initialize [toc] [#NonFungibleToken-initialize] +#### [!toc] [#NonFungibleToken-initialize] + + Initializes the contract by setting the name and symbol. + + This MUST be called in the implementing contract’s constructor. + Failure to do so can lead to an irreparable contract. + + Requirements: + + * Contract is not initialized. + + +#### balanceOf [toc] [#NonFungibleToken-balanceOf] +#### [!toc] [#NonFungibleToken-balanceOf] + + Returns the number of tokens in `owner`'s account. + + Requirements: + + * Contract is initialized. + + +#### ownerOf [toc] [#NonFungibleToken-ownerOf] +#### [!toc] [#NonFungibleToken-ownerOf] + + Returns the owner of the `tokenId` token. + + Requirements: + + * The contract is initialized. + * The `tokenId` must exist. + + +#### name [toc] [#NonFungibleToken-name] +#### [!toc] [#NonFungibleToken-name] + + Returns the token name. + + Requirements: + + * Contract is initialized. + + +#### symbol [toc] [#NonFungibleToken-symbol] +#### [!toc] [#NonFungibleToken-symbol] + + Returns the symbol of the token. + + Requirements: + + * Contract is initialized. + + +#### tokenURI [toc] [#NonFungibleToken-tokenURI] +#### [!toc] [#NonFungibleToken-tokenURI] + + Returns the token URI for the given `tokenId`. + Returns an empty string if a tokenURI does not exist. + + Requirements: + + * The contract is initialized. + * The `tokenId` must exist. + + + Native strings and string operations aren’t supported within the Compact language, + e.g. concatenating a base URI + token ID is not possible like in other NFT implementations. + Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs. + It’s up to the implementation to decide on how to handle this. + + + +#### _setTokenURI [toc] [#NonFungibleToken-_setTokenURI] +#### [!toc] [#NonFungibleToken-_setTokenURI] + + Sets the the URI as `tokenURI` for the given `tokenId`. + + Requirements: + + * The contract is initialized. + * The `tokenId` must exist. + + +#### approve [toc] [#NonFungibleToken-approve] +#### [!toc] [#NonFungibleToken-approve] + + Gives permission to `to` to transfer `tokenId` token to another account. + The approval is cleared when the token is transferred. + + Only a single account can be approved at a time, so approving the zero address clears previous approvals. + + Requirements: + + * The contract is initialized. + * The caller must either own the token or be an approved operator. + * `tokenId` must exist. + + +#### getApproved [toc] [#NonFungibleToken-getApproved] +#### [!toc] [#NonFungibleToken-getApproved] + + Returns the account approved for `tokenId` token. + + Requirements: + + * The contract is initialized. + * `tokenId` must exist. + + +#### setApprovalForAll [toc] [#NonFungibleToken-setApprovalForAll] +#### [!toc] [#NonFungibleToken-setApprovalForAll] + + Approve or remove `operator` as an operator for the caller. + Operators can call [transferFrom](#NonFungibleToken-transferFrom) for any token owned by the caller. + + Requirements: + + * The contract is initialized. + * The `operator` cannot be the zero address. + + +#### isApprovedForAll [toc] [#NonFungibleToken-isApprovedForAll] +#### [!toc] [#NonFungibleToken-isApprovedForAll] + + Returns if the `operator` is allowed to manage all of the assets of `owner`. + + Requirements: + + * The contract must have been initialized. + + +#### transferFrom [toc] [#NonFungibleToken-transferFrom] +#### [!toc] [#NonFungibleToken-transferFrom] + + Transfers `tokenId` token from `from` to `to`. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * The contract is initialized. + * `from` is not the zero address. + * `to` is not the zero address. + * `to` is not a ContractAddress. + * `tokenId` token must be owned by `from`. + * If the caller is not `from`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll). + + +#### _unsafeTransferFrom [toc] [#NonFungibleToken-_unsafeTransferFrom] +#### [!toc] [#NonFungibleToken-_unsafeTransferFrom] + + Unsafe variant of [transferFrom](#NonFungibleToken-transferFrom) which allows transfers to contract addresses. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * The contract is initialized. + * `from` is not the zero address. + * `to` is not the zero address. + * `tokenId` token must be owned by `from`. + * If the caller is not `from`, it must be approved to move this token by either [approve](#NonFungibleToken-approve) or [setApprovalForAll](#NonFungibleToken-setApprovalForAll). + + +#### _ownerOf [toc] [#NonFungibleToken-_ownerOf] +#### [!toc] [#NonFungibleToken-_ownerOf] + + Returns the owner of the `tokenId`. Does NOT revert if token doesn’t exist + + Requirements: + + * The contract is initialized. + + +#### _getApproved [toc] [#NonFungibleToken-_getApproved] +#### [!toc] [#NonFungibleToken-_getApproved] + + Returns the approved address for `tokenId`. + Returns the zero address if `tokenId` is not minted. + + Requirements: + + * The contract is initialized. + + +#### _isAuthorized [toc] [#NonFungibleToken-_isAuthorized] +#### [!toc] [#NonFungibleToken-_isAuthorized] + + Returns whether `spender` is allowed to manage `owner`'s tokens, + or `tokenId` in particular (ignoring whether it is owned by `owner`). + + Requirements: + + * The contract is initialized. + + + This function assumes that `owner` is the actual owner of `tokenId` and does not verify this assumption. + + + +#### _checkAuthorized [toc] [#NonFungibleToken-_checkAuthorized] +#### [!toc] [#NonFungibleToken-_checkAuthorized] + + Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. + + Requirements: + + * The contract is initialized. + * `spender` has approval from `owner` for `tokenId` OR `spender` has approval to manage all of `owner`'s assets. + + + This function assumes that `owner` is the actual owner of `tokenId` and does not verify this assumption. + + + +#### _update [toc] [#NonFungibleToken-_update] +#### [!toc] [#NonFungibleToken-_update] + + Transfers `tokenId` from its current owner to `to`, + or alternatively mints (or burns) if the current owner (or `to`) is the zero address. + Returns the owner of the `tokenId` before the update. + + Requirements: + + * The contract is initialized. + * If `auth` is non 0, then this function will check that `auth` is either the owner of the token, + or approved to operate on the token (by the owner). + + +#### _mint [toc] [#NonFungibleToken-_mint] +#### [!toc] [#NonFungibleToken-_mint] + + Mints `tokenId` and transfers it to `to`. + + Requirements: + + * The contract is initialized. + * `tokenId` must not exist. + * `to` is not the zero address. + * `to` is not a ContractAddress. + + +#### _unsafeMint [toc] [#NonFungibleToken-_unsafeMint] +#### [!toc] [#NonFungibleToken-_unsafeMint] + + Unsafe variant of [_mint](#NonFungibleToken-_mint) which allows transfers to contract addresses. + + Requirements: + + * Contract is initialized. + * `tokenId` must not exist. + * `to` is not the zero address. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + +#### _burn [toc] [#NonFungibleToken-_burn] +#### [!toc] [#NonFungibleToken-_burn] + + Destroys `tokenId`. + The approval is cleared when the token is burned. + This circuit does not check if the sender is authorized to operate on the token. + + Requirements: + + * The contract is initialized. + * `tokenId` must exist. + + +#### _transfer [toc] [#NonFungibleToken-_transfer] +#### [!toc] [#NonFungibleToken-_transfer] + + Transfers `tokenId` from `from` to `to`. As opposed to [transferFrom](#NonFungibleToken-transferFrom), + this imposes no restrictions on `ownPublicKey()`. + + + Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. + + + Requirements: + + * The contract is initialized. + * `to` is not the zero address. + * `to` is not a ContractAddress. + * `tokenId` token must be owned by `from`. + + +#### _unsafeTransfer [toc] [#NonFungibleToken-_unsafeTransfer] +#### [!toc] [#NonFungibleToken-__unsafeTransfer] + + Unsafe variant of [_transfer](#NonFungibleToken-_transfer) which allows transfers to contract addresses. + + Transfers `tokenId` from `from` to `to`. As opposed to [_unsafeTransferFrom](#NonFungibleToken-_unsafeTransferFrom), this imposes no restrictions on `ownPublicKey()`. It does NOT check if the recipient is a `ContractAddress`. + + + Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Tokens sent to a contract address may become irretrievable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * `to` is not the zero address. + * `tokenId` token must be owned by `from`. + + +#### _approve [toc] [#NonFungibleToken-_approve] +#### [!toc] [#NonFungibleToken-_approve] + + Approve `to` to operate on `tokenId`. + + Requirements: + + * The contract is initialized. + * If `auth` is non 0, then this function will check that `auth` is either the owner of the token, + or approved to operate on the token (by the owner). + + +#### _setApprovalForAll [toc] [#NonFungibleToken-_setApprovalForAll] +#### [!toc] [#NonFungibleToken-_setApprovalForAll] + + Approve `operator` to operate on all of `owner` tokens + + Requirements: + + * The contract is initialized. + * `operator` is not the zero address. + + +#### _requireOwned [toc] [#NonFungibleToken-_requireOwned] +#### [!toc] [#NonFungibleToken-_requireOwned] + + Reverts if the `tokenId` doesn’t have a current owner (it hasn’t been minted, or it has been burned). + Returns the owner. + + Requirements: + + * The contract is initialized. + * `tokenId` must exist. + diff --git a/docs/content/contracts-compact/api/ownable.mdx b/docs/content/contracts-compact/api/ownable.mdx new file mode 100644 index 00000000..ab892383 --- /dev/null +++ b/docs/content/contracts-compact/api/ownable.mdx @@ -0,0 +1,397 @@ +--- +title: Ownable API +--- + +This module provides the full Ownable module API. + + +For an overview of the module, read the [Ownable guide](../ownable). + + +## Ownable [toc] [#Ownable] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable"; +``` + +--- + +### Ledger [toc] [#Ownable-Ledger] +### Ledger [!toc] [#Ownable-Ledger] + +#### _owner [toc] [#Ownable-_owner] +#### [!toc] [#Ownable-_owner] + + Either a `ZswapCoinPublicKey` or `ContractAddress` representing the owner. + + +### Witnesses [toc] [#Ownable-Witnesses] +### Witnesses [!toc] [#Ownable-Witnesses] + +None. + +### Circuits [toc] [#Ownable-Circuits] +### Circuits [!toc] [#Ownable-Circuits] + +#### initialize [toc] [#Ownable-initialize] +#### [!toc] [#Ownable-initialize] + + Initializes the contract by setting the `initialOwner`. + This must be called in the contract’s constructor. + + Requirements: + + * Contract is not already initialized. + * `initialOwner` is not a ContractAddress. + * `initialOwner` is not the zero address. + + +#### owner [toc] [#Ownable-owner] +#### [!toc] [#Ownable-owner] + + Returns the current contract owner. + + Requirements: + + * Contract is initialized. + + +#### transferOwnership [toc] [#Ownable-transferOwnership] +#### [!toc] [#Ownable-transferOwnership] + + Transfers ownership of the contract to `newOwner`. + + + Ownership transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents permanently disabling access to a circuit. + + + Requirements: + + * Contract is initialized. + * The caller is the current contract owner. + * `newOwner` is not a ContractAddress. + * `newOwner` is not the zero address. + + +#### _unsafeTransferOwnership [toc] [#Ownable-_unsafeTransferOwnership] +#### [!toc] [#Ownable-_unsafeTransferOwnership] + + Unsafe variant of [`transferOwnership`](#Ownable-transferOwnership). + + + Ownership transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Ownership privileges sent to a contract address may become uncallable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + * The caller is the current contract owner. + * `newOwner` is not the zero address. + + +#### renounceOwnership [toc] [#Ownable-renounceOwnership] +#### [!toc] [#Ownable-renounceOwnership] + + Leaves the contract without an owner. + It will not be possible to call [`assertOnlyOwner`](#Ownable-assertOnlyOwner) circuits anymore. + Can only be called by the current owner. + + Requirements: + + * Contract is initialized. + * The caller is the current contract owner. + + +#### assertOnlyOwner [toc] [#Ownable-assertOnlyOwner] +#### [!toc] [#Ownable-assertOnlyOwner] + + Throws if called by any account other than the owner. + Use this to restrict access of specific circuits to the owner. + + Requirements: + + * Contract is initialized. + * The caller is the current contract owner. + + +#### _transferOwnership [toc] [#Ownable-_transferOwnership] +#### [!toc] [#Ownable-_transferOwnership] + + Transfers ownership of the contract to a `newOwner` without enforcing permission checks on the caller. + + + Ownership transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. + This restriction prevents permanently disabling access to a circuit. + + + Requirements: + + * Contract is initialized. + * `newOwner` is not a ContractAddress. + + +#### _unsafeUncheckedTransferOwnership [toc] [#Ownable-_unsafeUncheckedTransferOwnership] +#### [!toc] [#Ownable-_unsafeUncheckedTransferOwnership] + + Unsafe variant of [`_transferOwnership`](#Ownable-_transferOwnership). + + + Ownership transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. + Ownership privileges sent to a contract address may become uncallable. + Once contract-to-contract calls are supported, this circuit may be deprecated. + + + Requirements: + + * Contract is initialized. + + +--- + +## ZOwnablePK [toc] [#ZOwnablePK] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK"; +``` + +--- + +### Ledger [toc] [#ZOwnablePK-ledger] +### Ledger [!toc] [#ZOwnablePK-ledger] + +#### _ownerCommitment [toc] [#ZOwnablePK-_ownerCommitment] +#### [!toc] [#ZOwnablePK-_ownerCommitment] + + Stores the current hashed commitment representing the owner. + This commitment is derived from the public identifier (e.g., `SHA256(pk, nonce)`), + the `instanceSalt`, the transfer `counter`, and a domain separator. + + A commitment of `default>` (i.e. zero) indicates the contract is unowned. + + +#### _counter [toc] [#ZOwnablePK-_counter] +#### [!toc] [#ZOwnablePK-_counter] + + Internal transfer counter used to prevent commitment reuse. + + Increments by 1 on every successful ownership transfer. + Combined with `id` and `instanceSalt` to compute unique owner commitments over time. + + +#### _instanceSalt [toc] [#ZOwnablePK-_instanceSalt] +#### [!toc] [#ZOwnablePK-_instanceSalt] + + A per-instance value provided at initialization used to namespace commitments for this contract instance. + + This salt prevents commitment collisions across contracts that might otherwise use the same owner identifiers or domain parameters. + It is immutable after initialization. + + +### Witnesses [toc] [#ZOwnablePK-witnesses] +### Witnesses [!toc] [#ZOwnablePK-witnesses] + +#### wit_secretNonce [toc] [#ZOwnablePK-wit_secretNonce] +#### [!toc] [#ZOwnablePK-wit_secretNonce] + + A private per-user nonce used in deriving the shielded owner identifier. + + Combined with the user's public key as `SHA256(pk, nonce)` to produce an obfuscated, unlinkable identity commitment. + Users are encouraged to rotate this value on ownership changes. + + +### Circuits [toc] [#ZOwnablePK-circuits] +### Circuits [!toc] [#ZOwnablePK-circuits] + +#### initialize [toc] [#ZOwnablePK-initialize] +#### [!toc] [#ZOwnablePK-initialize] + + Initializes the contract by setting the initial owner via `ownerId` + and storing the `instanceSalt` that acts as a privacy additive + for preventing duplicate commitments among other contracts implementing ZOwnablePK. + + + The `ownerId` must be calculated prior to contract deployment using the SHA256 hashing algorithm. + Using any other algorithm will result in a permanent loss of contract access. + See [_computeOwnerId](#ZOwnablePK-_computeOwnerId). + + + +#### owner [toc] [#ZOwnablePK-owner] +#### [!toc] [#ZOwnablePK-owner] + + Returns the current commitment representing the contract owner. + The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. + + +#### transferOwnership [toc] [#ZOwnablePK-transferOwnership] +#### [!toc] [#ZOwnablePK-transferOwnership] + + Transfers ownership to `newOwnerId`. + `newOwnerId` must be precalculated and given to the current owner off chain. + + + The caller must be the current owner and `newOwnerId` must not be empty. + + + +#### renounceOwnership [toc] [#ZOwnablePK-renounceOwnership] +#### [!toc] [#ZOwnablePK-renounceOwnership] + + Leaves the contract without an owner. + It will not be possible to call `assertOnlyOwner` circuits anymore. + Can only be called by the current owner. + + +#### assertOnlyOwner [toc] [#ZOwnablePK-assertOnlyOwner] +#### [!toc] [#ZOwnablePK-assertOnlyOwner] + + Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match + the stored owner commitment. + Use this to only allow the owner to call specific circuits. + + +#### _computeOwnerCommitment [toc] [#ZOwnablePK-_computeOwnerCommitment] +#### [!toc] [#ZOwnablePK-_computeOwnerCommitment] + + Computes the owner commitment from the given `id` and `counter`. + + The commitment derivation follows: + `commitment = SHA256(id, instanceSalt, counter, "ZOwnablePK:shield:")` + + Where: + - `id`: The unique identifier `SHA256(pk, nonce)`. + - `instanceSalt`: Per-deployment salt to prevent collisions. + - `counter`: Incremented with each transfer for uniqueness. + - Domain separator: `"ZOwnablePK:shield:"` padded to 32 bytes. + + +#### _computeOwnerId [toc] [#ZOwnablePK-_computeOwnerId] +#### [!toc] [#ZOwnablePK-_computeOwnerId] + + Computes the unique identifier (`id`) of the owner from their public key and a secret nonce. + + The ID derivation follows: `id = SHA256(pk, nonce)` + + + Currently only supports `ZswapCoinPublicKey`. Contract address owners are not yet supported. + We recommend using an Air-Gapped Public Key for strongest security guarantees. + + + +#### _transferOwnership [toc] [#ZOwnablePK-_transferOwnership] +#### [!toc] [#ZOwnablePK-_transferOwnership] + + Transfers ownership to owner id `newOwnerId` without enforcing permission checks on the caller. + + This is an internal function that increments the counter and updates the owner commitment. + diff --git a/docs/content/contracts-compact/api/security.mdx b/docs/content/contracts-compact/api/security.mdx new file mode 100644 index 00000000..e6f4dcd3 --- /dev/null +++ b/docs/content/contracts-compact/api/security.mdx @@ -0,0 +1,193 @@ +--- +title: Security +--- + +This package provides the API for all Security modules. + + + For an overview of the module, read the [Security guide](../security). + + +## Initializable [toc] [#Initializable] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable"; +``` + +--- + +### Ledger [toc] [#Initializable-Ledger] +### Ledger [!toc] [#Initializable-Ledger] + +#### _isInitialized [toc] [#Initializable-_isInitialized] +#### [!toc] [#Initializable-_isInitialized] + + Boolean indicating if initialized. + + +### Witnesses [toc] [#Initializable-Witnesses] +### Witnesses [!toc] [#Initializable-Witnesses] + +None. + +### Circuits [toc] [#Initializable-Circuits] +### Circuits [!toc] [#Initializable-Circuits] + +#### initialize [toc] [#Initializable-initialize] +#### [!toc] [#Initializable-initialize] + + Initializes the state thus ensuring the calling circuit can only be called once. + + Requirements: + + * Contract must not be initialized. + + +#### assertInitialized [toc] [#Initializable-assertInitialized] +#### [!toc] [#Initializable-assertInitialized] + + Asserts that the contract has been initialized, throwing an error if not. + + Requirements: + + * Contract must not be initialized. + + +#### assertNotInitialized [toc] [#Initializable-assertNotInitialized] +#### [!toc] [#Initializable-assertNotInitialized] + + Asserts that the contract has not been initialized, throwing an error if it has. + + Requirements: + + * Contract must not be initialized. + + +--- + +## Pausable [toc] [#Pausable] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Pausable"; +``` + +--- + +### Ledger [toc] [#Pausable-Ledger] +### Ledger [!toc] [#Pausable-Ledger] + +#### _isPaused [toc] [#Pausable-_isPaused] +#### [!toc] [#Pausable-_isPaused] + + Boolean indicating if paused. + + +### Witnesses [toc] [#Pausable-Witnesses] +### Witnesses [!toc] [#Pausable-Witnesses] + +None. + +### Circuits [toc] [#Pausable-Circuits] +### Circuits [!toc] [#Pausable-Circuits] + +#### isPaused [toc] [#Pausable-isPaused] +#### [!toc] [#Pausable-isPaused] + + Returns true if the contract is paused, and false otherwise. + + +#### assertPaused [toc] [#Pausable-assertPaused] +#### [!toc] [#Pausable-assertPaused] + + Makes a circuit only callable when the contract is paused. + + Requirements: + + * Contract must be paused. + + +#### assertNotPaused [toc] [#Pausable-assertNotPaused] +#### [!toc] [#Pausable-assertNotPaused] + + Makes a circuit only callable when the contract is not paused. + + Requirements: + + * Contract must not be paused. + + +#### _pause [toc] [#Pausable-_pause] +#### [!toc] [#Pausable-_pause] + + Triggers a stopped state. + + Requirements: + + * Contract must not be paused. + + +#### _unpause [toc] [#Pausable-_unpause] +#### [!toc] [#Pausable-_unpause] + + Lifts the pause on the contract. + + Requirements: + + * Contract must be paused. + diff --git a/docs/content/contracts-compact/api/utils.mdx b/docs/content/contracts-compact/api/utils.mdx new file mode 100644 index 00000000..81a36c96 --- /dev/null +++ b/docs/content/contracts-compact/api/utils.mdx @@ -0,0 +1,97 @@ +--- +title: Utils +--- + +This package provides the API for all Utils modules. + + + For an overview of the module, read the [Utils guide](/contracts-compact/utils). + + +## Utils [toc] [#Utils] + + +```ts +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable"; +``` + +--- + + + There’s no easy way to get the constraints of circuits at this time so the constraints of the circuits listed below have been omitted. + + +### Ledger [toc] [#Utils-Ledger] +### Ledger [!toc] [#Utils-Ledger] + +None. + +### Witnesses [toc] [#Utils-Witnesses] +### Witnesses [!toc] [#Utils-Witnesses] + +None. + +### Circuits [toc] [#Utils-Circuits] +### Circuits [!toc] [#Utils-Circuits] + +#### isKeyOrAddressZero [toc] [#Utils-isKeyOrAddressZero] +#### [!toc] [#Utils-isKeyOrAddressZero] + + Returns whether `keyOrAddress` is the zero address. + + + Midnight's burn address is represented as `left(default)` in Compact, + so we've chosen to represent the zero address as this structure as well. + + + +#### isKeyZero [toc] [#Utils-isKeyZero] +#### [!toc] [#Utils-isKeyZero] + + Returns whether `key` is the zero address. + + +#### isKeyOrAddressEqual [toc] [#Utils-isKeyOrAddressEqual] +#### [!toc] [#Utils-isKeyOrAddressEqual] + + Returns whether `keyOrAddress` is equal to `other`. + Assumes that a `ZswapCoinPublicKey` and a `ContractAddress` can never be equal. + + +#### isContractAddress [toc] [#Utils-isContractAddress] +#### [!toc] [#Utils-isContractAddress] + + Returns whether `keyOrAddress` is a `ContractAddress` type. + + +#### emptyString [toc] [#Utils-emptyString] +#### [!toc] [#Utils-emptyString] + + A helper function that returns the empty string: `""`. + diff --git a/docs/modules/ROOT/pages/extensibility.adoc b/docs/content/contracts-compact/extensibility.mdx similarity index 57% rename from docs/modules/ROOT/pages/extensibility.adoc rename to docs/content/contracts-compact/extensibility.mdx index 4b706840..b8673cfe 100644 --- a/docs/modules/ROOT/pages/extensibility.adoc +++ b/docs/content/contracts-compact/extensibility.mdx @@ -1,11 +1,15 @@ -# Extensibility +--- +title: Extensibility +--- -[id="the_module_contract_pattern"] ## The Module/Contract Pattern -We use the term *modular composition by delegation* to describe the practice of having contracts call into module-defined circuits to implement behavior. Rather than inheriting or overriding functionality, a contract delegates responsibility to the module by explicitly invoking its exported circuits. +We use the term **modular composition by delegation** to describe the practice of having contracts call into module-defined circuits to implement behavior. +Rather than inheriting or overriding functionality, +a contract delegates responsibility to the module by explicitly invoking its exported circuits. -The idea is that there are two types of compact files: modules and contracts. To minimize risk, boilerplate, and avoid naming clashes, we follow these rules: +The idea is that there are two types of compact files: modules and contracts. +To minimize risk, boilerplate, and avoid naming clashes, we follow these rules: ### Modules @@ -17,45 +21,50 @@ Modules expose functionality through three circuit types: Modules must: -- Export only `public` and `external` circuits. -- Prefix `public` circuits with `_` (e.g., `FungibleToken._mint`). -- Avoid `_` prefix for `external` circuits (e.g., `FungibleToken.transfer`). -- Avoid defining or calling constructors or `initialize()` directly. -- Optionally define an `initialize()` circuit for internal setup—but execution must be delegated to the contract. +* Export only `public` and `external` circuits. +* Prefix `public` circuits with `_` (e.g., `FungibleToken._mint`). +* Avoid `_` prefix for `external` circuits (e.g., `FungibleToken.transfer`). +* Avoid defining or calling constructors or `initialize()` directly. +* Optionally define an `initialize()` circuit for internal setup—but execution must be delegated to the contract. -**Note**: Compact files must contain only one top-level module and all logic must be defined *inside* the module declaration. + +Compact files must contain only one top-level module and all logic must be defined **inside** the module declaration. + ### Contracts -Contracts compose behavior by explicitly invoking the relevant circuits from imported modules. Therefore, contracts: +Contracts compose behavior by explicitly invoking the relevant circuits from imported modules. +Therefore, contracts: -- Can import from modules. -- Should add prefix to imports (`import "FungibleToken" prefix FungibleToken_;`). -- Should re-expose external module circuits through wrapper circuits to control naming and layering. Avoid raw re-exports to prevent name clashes. -- Should implement constructor that calls `initialize` from imported modules. -- Must not call initializers outside of the constructor. +* Can import from modules. +* Should add prefix to imports (`import "FungibleToken" prefix FungibleToken_;`). +* Should re-expose external module circuits through wrapper circuits to control naming and layering. +Avoid raw re-exports to prevent name clashes. +* Should implement constructor that calls `initialize` from imported modules. +* Must not call initializers outside of the constructor. -This pattern balances modularity with local control, avoids tight coupling, and works within Compact’s language constraints. As Compact matures, this pattern will likely evolve as well. +This pattern balances modularity with local control, avoids tight coupling, and works within Compact’s language constraints. +As Compact matures, this pattern will likely evolve as well. ### Example contract implementing modules ```ts -/** FungibleTokenMintablePausableOwnableContract */ +// FungibleTokenMintablePausableOwnableContract -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" - prefix FungibleToken_; -import "./node_modules/@openzeppelin-compact/contracts/src/security/Pausable" - prefix Pausable_; -import "./node_modules/@openzeppelin-compact/contracts/src/access/Ownable" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable" prefix Ownable_; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Pausable" + prefix Pausable_; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" + prefix FungibleToken_; constructor( - _name: Opaque<"string">, - _symbol: Opaque<"string">, - _decimals: Uint<8>, + _name: Maybe>, + _symbol: Maybe>, + _decimals:Uint<8>, _owner: Either ) { FungibleToken_initialize(_name, _symbol, _decimals); @@ -97,7 +106,7 @@ export circuit allowance( export circuit transfer( to: Either, - value: Uint<128>, + value: Uint<128> ): Boolean { Pausable_assertNotPaused(); return FungibleToken_transfer(to, value); @@ -106,7 +115,7 @@ export circuit transfer( export circuit transferFrom( from: Either, to: Either, - value: Uint<128>, + value: Uint<128> ): Boolean { Pausable_assertNotPaused(); return FungibleToken_transferFrom(from, to, value); @@ -114,7 +123,7 @@ export circuit transferFrom( export circuit approve( spender: Either, - value: Uint<128>, + value: Uint<128> ): Boolean { Pausable_assertNotPaused(); return FungibleToken_approve(spender, value); @@ -124,11 +133,11 @@ export circuit approve( export circuit mint( account: Either, - value: Uint<128>, + value: Uint<128> ): [] { Pausable_assertNotPaused(); Ownable_assertOnlyOwner(); - return FungibleToken__mint(account, value); + FungibleToken__mint(account, value); } /** IPausable */ @@ -139,12 +148,12 @@ export circuit isPaused(): Boolean { export circuit pause(): [] { Ownable_assertOnlyOwner(); - return Pausable__pause(); + Pausable__pause(); } export circuit unpause(): [] { Ownable_assertOnlyOwner(); - return Pausable__unpause(); + Pausable__unpause(); } /** IOwnable */ @@ -156,10 +165,11 @@ export circuit owner(): Either { export circuit transferOwnership( newOwner: Either ): [] { - return Ownable_transferOwnership(newOwner); + Ownable_transferOwnership(newOwner); } export circuit renounceOwnership(): [] { - return Ownable_renounceOwnership(); + Ownable_renounceOwnership(); } -``` \ No newline at end of file + +``` diff --git a/docs/modules/ROOT/pages/fungibleToken.adoc b/docs/content/contracts-compact/fungibleToken.mdx similarity index 58% rename from docs/modules/ROOT/pages/fungibleToken.adoc rename to docs/content/contracts-compact/fungibleToken.mdx index 6b28287a..134a5e24 100644 --- a/docs/modules/ROOT/pages/fungibleToken.adoc +++ b/docs/content/contracts-compact/fungibleToken.mdx @@ -1,68 +1,75 @@ -:fungible-tokens: https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens[fungible tokens] -:eip-20: https://eips.ethereum.org/EIPS/eip-20[EIP-20] +--- +title: FungibleToken +--- -= FungibleToken +{/* links */} +[fungible tokens]: ../contracts/5.x/tokens.mdx#different-kinds-of-tokens +[EIP-20]: https://eips.ethereum.org/EIPS/eip-20 +[Module/Contract Pattern]: ./extensibility.mdx#the_module_contract_pattern +[_mint]: api/fungibleToken#FungibleToken-_mint -FungibleToken is a specification for {fungible-tokens}, +FungibleToken is a specification for [fungible tokens], a type of token where all the units are exactly equal to each other. -This module is an approximation of {eip-20} written in the Compact programming language for the Midnight network. +This module is an approximation of [EIP-20] written in the Compact programming language for the Midnight network. -== ERC20 Compatbility +## ERC20 Compatibility Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard. Some features and behaviors are either not possible, not possible yet, or changed because of the vastly different tech stack and Compact language constraints. -**Notable changes** +***Notable changes*** -- **Uint<128> as value type** - Since 256-bit unsigned integers are not supported, the library uses the Compact type `Uint<128>`. +* ***`Uint<128>` as value type*** - Since 256-bit unsigned integers are not supported, +the library uses the Compact type `Uint<128>`. -**Features and specifications NOT supported** +***Features and specifications NOT supported*** -- **Events** - Midnight does not currently support events, but this is planned on being supported in the future. -- **Uint256 type** - There's ongoing research on ways to support uint256 in the future. -- **Interface** - Compact currently does not have a way to define a contract interface. +* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future. +* ***Uint256 type*** - There’s ongoing research on ways to support uint256 in the future. +* ***Interface*** - Compact currently does not have a way to define a contract interface. This library offers modules of contracts with free floating circuits; nevertheless, there are no means of enforcing that all circuits are provided. -== Contract-to-contract calls +## Contract-to-contract calls Contract-to-contract calls are currently not supported in the Compact language. Due to this limitation, the current iteration of FungibleToken disallows transfers and mints to the `ContractAddress` type. Transferring tokens to a contract may result in those tokens being locked forever. The FungibleToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts. -WARNING: The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning + +The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning `transfer`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type. + -== Usage - -:extensibility-pattern: xref:extensibility.adoc#the_module_contract_pattern[Module/Contract Pattern] -:fungible-mint: xref:/api/fungibleToken.adoc#FungibleTokenModule-_mint[_mint] +## Usage Import the FungibleToken module into the implementing contract. -It's recommended to prefix the module with `FungibleToken_` to avoid circuit signature clashes. +It’s recommended to prefix the module with `FungibleToken_` to avoid circuit signature clashes. ```typescript -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" prefix FungibleToken_; constructor( name: Opaque<"string">, symbol: Opaque<"string">, - decimals: Uint<8>, + decimals: Uint<8> ) { FungibleToken_initialize(name, symbol, decimals); } + ``` -Next, expose the ciruits that users may call in the contract. -This library enables extensibility by following the rules of the {extensibility-pattern}. +Next, expose the circuits that users may call in the contract. + +This library enables extensibility by following the rules of the [Module/Contract Pattern]. Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts. -Exposing {fungible-mint} without some sort of access control, for example, would allow ANYONE to mint tokens. +Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens. ```typescript export circuit name(): Opaque<"string"> { @@ -80,15 +87,13 @@ export circuit decimals(): Uint<8> { (...) ``` -The following example is a simple token contract with a fixed supply that's minted to the passed recipient upon construction. +The following example is a simple token contract with a fixed supply that’s minted to the passed recipient upon construction. ```typescript -// FungibleTokenFixedSupply.compact - -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" prefix FungibleToken_; constructor( @@ -118,23 +123,18 @@ export circuit totalSupply(): Uint<128> { return FungibleToken_totalSupply(); } -export circuit balanceOf( - account: Either, -): Uint<128> { +export circuit balanceOf(account: Either): Uint<128> { return FungibleToken_balanceOf(account); } export circuit allowance( owner: Either, - spender: Either, + spender: Either ): Uint<128> { return FungibleToken_allowance(owner, spender); } -export circuit transfer( - to: Either, - value: Uint<128>, -): Boolean { +export circuit transfer(to: Either, value: Uint<128>): Boolean { return FungibleToken_transfer(to, value); } @@ -146,10 +146,8 @@ export circuit transferFrom( return FungibleToken_transferFrom(from, to, value); } -export circuit approve( - spender: Either, - value: Uint<128>, -): Boolean { +export circuit approve(spender: Either, value: Uint<128>): Boolean { return FungibleToken_approve(spender, value); } + ``` diff --git a/docs/modules/ROOT/pages/index.adoc b/docs/content/contracts-compact/index.mdx similarity index 69% rename from docs/modules/ROOT/pages/index.adoc rename to docs/content/contracts-compact/index.mdx index 9598f2d5..e13d0664 100644 --- a/docs/modules/ROOT/pages/index.adoc +++ b/docs/content/contracts-compact/index.mdx @@ -1,30 +1,35 @@ -:midnight: https://midnight.network/[Midnight] -:nvm: https://github.com/nvm-sh/nvm[nvm] -:yarn: https://yarnpkg.com/getting-started/install[yarn] -:turbo: https://turborepo.com/docs/getting-started/installation[turbo] -:compact-dev-tools: https://docs.midnight.network/blog/compact-developer-tools[Compact Developer Tools] +--- +title: Contracts for Compact +--- -= Contracts for Compact +{/* links */} +[Midnight]: https://midnight.network/ +[nvm]: https://github.com/nvm-sh/nvm +[yarn]: https://yarnpkg.com/getting-started/install +[compact-dev-tools]: https://docs.midnight.network/blog/compact-developer-tools -*A library for secure smart contract development* written in Compact for {midnight}. +**A library for secure smart contract development** written in Compact for [Midnight]. This library consists of modules to build custom smart contracts. -WARNING: This repo contains highly experimental code. Expect rapid iteration. *Use at your own risk.* + +This repo contains highly experimental code. +Expect rapid iteration. **Use at your own risk.** + -== Usage +## Usage -Make sure you have {nvm} and {yarn} installed on your machine. +Make sure you have [nvm] and [yarn] installed on your machine. -Follow Midnight's {compact-dev-tools} installation guide and confirm that `compact` is in the `PATH` env variable. +Follow Midnight's [Compact Developer Tools][compact-dev-tools] installation guide and confirm that `compact` is in the `PATH` env variable. ```bash $ compact compile --version -Compactc version: 0.26.0 -0.26.0 +Compactc version: {{compact_compiler_version}} +{{compact_compiler_version}} ``` -=== Installation +### Installation Create a directory for your project. @@ -48,18 +53,20 @@ yarn && \ SKIP_ZK=true yarn compact ``` -=== Write a custom contract using library modules +### Write a custom contract using library modules In the root of `my-project`, create a custom contract using OpenZeppelin Compact modules. Import the modules through `compact-contracts/node_modules/@openzeppelin-compact/contracts/...`. Import modules through `node_modules` rather than directly to avoid state conflicts between shared dependencies. -NOTE: Installing the library will be easier once it's available as an NPM package. + +Installing the library will be easier once it's available as an NPM package. + ```typescript // MyContract.compact -pragma language_version >= 0.16.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable" @@ -103,7 +110,7 @@ export circuit unpause(): [] { (...) ``` -=== Compile the contract +### Compile the contract In the project root, compile the contract using Compact's dev tools. diff --git a/docs/modules/ROOT/pages/multitoken.adoc b/docs/content/contracts-compact/multitoken.mdx similarity index 58% rename from docs/modules/ROOT/pages/multitoken.adoc rename to docs/content/contracts-compact/multitoken.mdx index ab993294..c55bef7b 100644 --- a/docs/modules/ROOT/pages/multitoken.adoc +++ b/docs/content/contracts-compact/multitoken.mdx @@ -1,68 +1,77 @@ -:eip-1155: https://eips.ethereum.org/EIPS/eip-1155[EIP-1155] -:erc165: https://eips.ethereum.org/EIPS/eip-165[ERC165] +--- +title: MultiToken +--- -= MultiToken +{/* links */} +[EIP-1155]: https://eips.ethereum.org/EIPS/eip-1155 +[ERC165]: https://eips.ethereum.org/EIPS/eip-165 +[Module/Contract Pattern]: ./extensibility.mdx#the_module_contract_pattern +[_mint]: ./api/multitoken#MultiToken-_mint MultiToken is a specification for contracts that manage multiple token types. -This module is an approximation of {eip-1155} written in the Compact programming language for the Midnight network. +This module is an approximation of [EIP-1155] written in the Compact programming language for the Midnight network. -== ERC1155 Compatbility +## ERC1155 Compatibility Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard. -Some features and behaviors are either not possible, not possible yet, or changed because of the vastly different tech stack -and Compact language constraints. +Some features and behaviors are either not possible, not possible yet, +or changed because of the vastly different tech stack and Compact language constraints. **Notable changes** -- **Uint<128> as value and id type** - Since 256-bit unsigned integers are not supported, the library uses the Compact type `Uint<128>`. +* **`Uint<128>` as value and id type** - Since 256-bit unsigned integers are not supported, +the library uses the Compact type `Uint<128>`. **Features and specifications NOT supported** -- **Events** - Midnight does not currently support events, but this is planned on being supported in the future. -- **Uint256 type** - There's ongoing research on ways to support uint256 in the future. -- **Interface** - Compact currently does not have a way to define a contract interface. -This library offers modules of contracts with free floating circuits; nevertheless, there's no means of enforcing that all circuits are provided. -- **Batch mint, burn, transfer** - Without support for dynamic arrays, batching transfers is difficult to do without a hacky solution. +* **Events** - Midnight does not currently support events, but this is planned on being supported in the future. +* **Uint256 type** - There’s ongoing research on ways to support uint256 in the future. +* **Interface** - Compact currently does not have a way to define a contract interface. +This library offers modules of contracts with free floating circuits; +nevertheless, there’s no means of enforcing that all circuits are provided. +* **Batch mint, burn, transfer** - Without support for dynamic arrays, +batching transfers is difficult to do without a hacky solution. For instance, we could change the `to` and `from` parameters to be vectors. This would change the signature and would be both difficult to use and easy to misuse. -- **Querying batched balances** - This can be somewhat supported. -The issue, without dynamic arrays, is that the module circuit must use Vector for accounts and ids; +* **Querying batched balances** - This can be somewhat supported. +The issue, without dynamic arrays, is that the module circuit must use `Vector` for accounts and ids; therefore, the implementing contract must explicitly define the number of balances to query in the circuit i.e. -> ```ts -> balanceOfBatch_10( -> accounts: Vector<10, Either>, -> ids: Vector<10, Uint<128>> -> ): Vector<10, Uint<128>> -> ``` -> Since this module does not offer mint or transfer batching, balance batching is also not included at this time. +```ts +balanceOfBatch_10( + accounts: Vector<10, Either>, + ids: Vector<10, Uint<128>> +): Vector<10, Uint<128>> +``` + +Since this module does not offer mint or transfer batching, balance batching is also not included at this time. -- **Introspection** - Compact currently cannot support contract-to-contract queries for introspection. -{erc165} (or an equivalent thereof) is NOT included in the contract. -- **Safe transfers** - The lack of an introspection mechanism means safe transfers of any kind can not be supported. +* **Introspection** - Compact currently cannot support contract-to-contract queries for introspection. +[ERC165] (or an equivalent thereof) is NOT included in the contract. +* **Safe transfers** - The lack of an introspection mechanism means safe transfers of any kind can not be supported. -== Contract-to-contract calls +## Contract-to-contract calls Contract-to-contract calls are currently not supported in the Compact language. Due to this limitation, the current iteration of MultiToken disallows transfers and mints to the `ContractAddress` type. Transferring tokens to a contract may result in those tokens being locked forever. The MultiToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts. -WARNING: The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning + +The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning `transferFrom`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type. + -== Usage -:extensibility-pattern: xref:extensibility.adoc#the_module_contract_pattern[Module/Contract Pattern] -:multitoken-mint: xref:/api/multitoken.adoc#MultiTokenModule-_mint[_mint] +## Usage Import the MultiToken module into the implementing contract. -It's recommended to prefix the module with `MultiToken_` to avoid circuit signature clashes. +It’s recommended to prefix the module with `MultiToken_` to avoid circuit signature clashes. ```typescript -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/MultiToken" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/MultiToken" prefix MultiToken_; constructor( @@ -70,21 +79,21 @@ constructor( ) { MultiToken_initialize(uri); } + ``` -Next, expose the ciruits that users may call in the contract. -This library enables extensibility by following the rules of the {extensibility-pattern}. +Next, expose the circuits that users may call in the contract. + +This library enables extensibility by following the rules of the [Module/Contract Pattern]. Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts. -Exposing {multitoken-mint} without some sort of access control, for example, would allow ANYONE to mint tokens. +Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens. ```typescript export circuit uri(id: Uint<128>): Opaque<"string"> { return MultiToken_uri(); } -export circuit balanceOf( - account: Either -): Uint<128> { +export circuit balanceOf(account: Either): Uint<128> { return MultiToken_balanceOf(account); } @@ -96,10 +105,10 @@ The following example is a simple multi-token contract that creates both a fixed ```typescript // MultiTokenTwoTokenTypes.compact -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/MultiToken" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/MultiToken" prefix MultiToken_; constructor( @@ -123,10 +132,7 @@ export circuit uri(id: Uint<128>): Opaque<"string"> { return MultiToken_uri(id); } -export circuit balanceOf( - account: Either, - id: Uint<128>, -): Uint<128> { +export circuit balanceOf(account: Either, id: Uint<128>): Uint<128> { return MultiToken_balanceOf(account, id); } @@ -152,4 +158,5 @@ export circuit transferFrom( ): [] { return MultiToken_transferFrom(from, to, id, value); } + ``` diff --git a/docs/modules/ROOT/pages/nonFungibleToken.adoc b/docs/content/contracts-compact/nonFungibleToken.mdx similarity index 53% rename from docs/modules/ROOT/pages/nonFungibleToken.adoc rename to docs/content/contracts-compact/nonFungibleToken.mdx index 8e9795f4..50c2f031 100644 --- a/docs/modules/ROOT/pages/nonFungibleToken.adoc +++ b/docs/content/contracts-compact/nonFungibleToken.mdx @@ -1,72 +1,80 @@ -:non-fungible-tokens: https://docs.openzeppelin.com/contracts/5.x/tokens#different-kinds-of-tokens[non-fungible tokens] -:eip-721: https://eips.ethereum.org/EIPS/eip-721[EIP-721] - -= NonFungibleToken - -FungibleToken is a specification for {non-fungible-tokens}, +--- +title: NonFungibleToken +--- + +{/* links */} +[non-fungible tokens]: ../contracts/5.x/tokens.mdx#different-kinds-of-tokens +[EIP-721]: https://eips.ethereum.org/EIPS/eip-721 +[ERC-165]: https://eips.ethereum.org/EIPS/eip-165 +[Module/Contract Pattern]: ./extensibility#the-modulecontract-pattern +[_mint]: ./api/nonFungibleToken#NonFungibleToken-_mint + +NonFungibleToken is a specification for [non-fungible tokens], a type of token where all the units are unique and distinct from each other. -This module is an approximation of {eip-721} written in the Compact programming language for the Midnight network. +This module is an approximation of [EIP-721] written in the Compact programming language for the Midnight network. -== ERC721 Compatbility +## ERC721 Compatibility Even though Midnight is not EVM-compatible, this implementation attempts to be an approximation of the standard. Some features and behaviors are either not possible, not possible yet, or changed because of the vastly different tech stack and Compact language constraints. -**Notable changes** +***Notable changes*** -- **Uint<128> tokenIds** - Since 256-bit unsigned integers are not supported, the library uses the Compact type `Uint<128>`. -- **No _baseURI() support** - Native strings and string operations are not supported within the Compact language, so concatenating a base URI + token ID is not possible like in other NFT implementations. Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs. It's up to the implementation to decide on how to handle this. +* ***`Uint<128>` tokenIds*** - Since 256-bit unsigned integers are not supported, +the library uses the Compact type `Uint<128>`. +* ***No _baseURI() support*** - Native strings and string operations are not supported within the Compact language, +so concatenating a base URI + token ID is not possible like in other NFT implementations. +Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs. +It’s up to the implementation to decide on how to handle this. -**Features and specifications NOT supported** +***Features and specifications NOT supported*** -- **Events** - Midnight does not currently support events, but this is planned on being supported in the future. -- **Uint256 type** - There's ongoing research on ways to support uint256 in the future. -- **Interface** - Compact currently does not have a way to define a contract interface. +* ***Events*** - Midnight does not currently support events, but this is planned on being supported in the future. +* ***Uint256 type*** - There’s ongoing research on ways to support uint256 in the future. +* ***Interface*** - Compact currently does not have a way to define a contract interface. This library offers modules of contracts with free floating circuits; nevertheless, there are no means of enforcing that all circuits are provided. -- **ERC-165 Standard** - Since Compact doesn't provide a way to define a contract interace, -it's not possible to implement an https://eips.ethereum.org/EIPS/eip-165[ERC-165] like interface standard at this time. -- **Safe Transfers** - It's not possible to implement safe transfers without an https://eips.ethereum.org/EIPS/eip-165[ERC-165] like +* ***ERC-165 Standard*** - Since Compact doesn't provide a way to define a contract interface, +it’s not possible to implement an [ERC-165] like interface standard at this time. +* ***Safe Transfers*** - It’s not possible to implement safe transfers without an [ERC-165]-like interface standard at this time. -== Contract-to-contract calls +## Contract-to-contract calls Contract-to-contract calls are currently not supported in the Compact language. Due to this limitation, the current iteration of NonFungibleToken disallows transfers and mints to the `ContractAddress` type. Transferring tokens to a contract may result in those tokens being locked forever. The NonFungibleToken module, however, does provide `unsafe` circuit variants for users who wish to experiment with sending tokens to contracts. -WARNING: The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning + +The `unsafe` circuits will eventually be deprecated after Compact supports contract-to-contract calls—meaning `_transfer`, `_mint`, etc. are planned to eventually allow the recipients to be of the `ContractAddress` type. + -== Usage - -:extensibility-pattern: xref:extensibility.adoc#the_module_contract_pattern[Module/Contract Pattern] -:nonfungible-mint: xref:/api/nonFungibleToken.adoc#NonFungibleTokenModule-_mint[_mint] +## Usage Import the NonFungibleToken module into the implementing contract. -It's recommended to prefix the module with `NonFungibleToken_` to avoid circuit signature clashes. +It’s recommended to prefix the module with `NonFungibleToken_` to avoid circuit signature clashes. ```typescript -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken" prefix NonFungibleToken_; -constructor( - name: Opaque<"string">, - symbol: Opaque<"string">, -) { +constructor(name: Opaque<"string">, symbol: Opaque<"string">) { NonFungibleToken_initialize(name, symbol); } + ``` -Next, expose the ciruits that users may call in the contract. -This library enables extensibility by following the rules of the {extensibility-pattern}. +Next, expose the circuits that users may call in the contract. + +This library enables extensibility by following the rules of the [Module/Contract Pattern]. Note that circuits with a preceding underscore (`_likeThis`) are meant to be building blocks for implementing contracts. -Exposing {nonfungible-mint} without some sort of access control, for example, would allow ANYONE to mint tokens. +Exposing [_mint] without some sort of access control, for example, would allow ANYONE to mint tokens. ```typescript export circuit name(): Opaque<"string"> { @@ -85,10 +93,10 @@ The following example is a simple non-fungible token contract that mints an NFT ```typescript // SimpleNonFungibleToken.compact -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken" +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken" prefix NonFungibleToken_; constructor( @@ -103,15 +111,11 @@ constructor( NonFungibleToken__setTokenURI(tokenId, tokenURI); } -export circuit balanceOf( - owner: Either -): Uint<128> { +export circuit balanceOf(owner: Either): Uint<128> { return NonFungibleToken_balanceOf(owner); } -export circuit ownerOf( - tokenId: Uint<128>, -): Either { +export circuit ownerOf(tokenId: Uint<128>): Either { return NonFungibleToken_ownerOf(tokenId); } @@ -127,16 +131,11 @@ export circuit tokenURI(tokenId: Uint<128>): Opaque<"string"> { return NonFungibleToken_tokenURI(tokenId); } -export circuit approve( - to: Either, - tokenId: Uint<128>, -): [] { +export circuit approve(to: Either, tokenId: Uint<128>): [] { NonFungibleToken_approve(to, tokenId); } -export circuit getApproved( - tokenId: Uint<128>, -): Either { +export circuit getApproved(tokenId: Uint<128>): Either { return NonFungibleToken_getApproved(tokenId); } @@ -156,9 +155,10 @@ export circuit isApprovedForAll( export circuit transferFrom( from: Either, - to: Either - tokenId: Uint<128>, + to: Either, + tokenId: Uint<128> ): [] { NonFungibleToken_transferFrom(from, to, tokenId); } -``` \ No newline at end of file + +``` diff --git a/docs/content/contracts-compact/ownable.mdx b/docs/content/contracts-compact/ownable.mdx new file mode 100644 index 00000000..d441f96d --- /dev/null +++ b/docs/content/contracts-compact/ownable.mdx @@ -0,0 +1,404 @@ +--- +title: Ownable +--- + +{/* links */} +[initialize]: api/ownable#Ownable-initialize +[transferOwnership]: api/ownable#Ownable-transferOwnership +[_transferOwnership]: api/ownable#Ownable-_transferOwnership +[_unsafeTransferOwnership]: api/ownable#Ownable-_unsafeTransferOwnership +[_unsafeUncheckedTransferOwnership]: api/ownable#Ownable-_unsafeUncheckedTransferOwnership +[_computeOwnerId]: api/ownable#ZOwnablePK-_computeOwnerId +[assertOnlyOwner]: api/ownable#ZOwnablePK-assertOnlyOwner + +[ed25519]: https://ed25519.cr.yp.to/ +[rfc6979]: https://datatracker.ietf.org/doc/html/rfc6979 + +## Access Control Defined + +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 steals your whole system. +OpenZeppelin Contracts for Compact provides a variety of access control modules to suit your application and privacy needs. + +## 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 for Compact provides an Ownable module for implementing ownership in your contracts. +The initial owner must be set by using the [initialize] circuit during construction. +This can later be changed with [transferOwnership]. + +### Ownership transfers + +Ownership can only be transferred to `ZswapCoinPublicKeys` through the main transfer circuits ([transferOwnership] and [_transferOwnership]). + +In other words, ownership transfers to contract addresses are disallowed through these circuits. +This is because Compact currently does not support contract-to-contract calls which means if a contract is granted ownership, +the owner contract cannot directly call the protected circuit. + +### Experimental features + +This module offers experimental circuits that allow ownership to be granted to contract addresses ([_unsafeTransferOwnership] and [_unsafeUncheckedTransferOwnership]). + +Note that the circuit names are very explicit ("unsafe") with these experimental circuits. +Until contract-to-contract calls are supported, +there is no direct way for a contract to call circuits of other contracts or transfer ownership back to a user. + + +The unsafe circuits are planned to become deprecated once contract-to-contract calls become available. + + +### Usage + +Import the Ownable module into the implementing contract. +It’s recommended to prefix the module with `Ownable_` to avoid circuit signature clashes. + +```ts +pragma language_version >= {{compact_language_version}}; + +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable" + prefix Ownable_; + +constructor( + initialOwner: Either +) { + Ownable_initialize(initialOwner); +} +``` + +To protect a circuit so that only the contract owner may call it, +insert the `assertOnlyOwner` circuit in the beginning of the circuit body like this: + +```ts +export circuit mySensitiveCircuit(): [] { + Ownable_assertOnlyOwner(); + + // Do something +} +``` + +Contracts may expose [transferOwnership] to allow the owner to transfer ownership. + +```ts +export circuit transferOwnership(newOwner: Either): [] { + Ownable_transferOwnership(newOwner); +} +``` + +Here’s a complete contract showcasing how to integrate the Ownable module and protect sensitive circuits. + +```ts +// SimpleOwnable.compact + +pragma language_version >= {{compact_language_version}}; + +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable" + prefix Ownable_; + +/** + * Set `initialOwner` as the owner of the contract. + */ +constructor(initialOwner: Either) { + Ownable_initialize(initialOwner); +} + +/** + * The current owner of the contact. + */ +export circuit owner(): Either { + return Ownable_owner(); +} + +/** + * Transfers ownership of the contract. + * Can only be called by the current owner. + */ +export circuit transferOwnership(newOwner: Either): [] { + Ownable_transferOwnership(newOwner); +} + +/** + * Leaves the contract without an owner. + * Can only be called by the current owner. + * Renouncing ownership means `mySensitiveCircuit` can never be called again. + */ +export circuit renounceOwnership(): [] { + Ownable_renounceOwnership(); +} + +/** + * This is the protected circuit that only the current owner can call. + */ +export circuit mySensitiveCircuit(): [] { + // Protects the circuit + Ownable_assertOnlyOwner(); + + // Do something +} +``` + + + For more complex logic, + contracts may transfer ownership to another user irrespective of the caller by leveraging [_transferOwnership]. + This is generally more useful when contract addresses are the owner or when a contract has a unique deployment process. + + +## Shielded Ownership and `ZOwnablePK` + +Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight. +While traditional ownership patterns expose the owner’s identity on-chain, +many applications require administrative control without revealing who holds that authority. + +### Privacy-First Ownership + +The most common approach to access control in traditional smart contracts is ownership: +there’s an account that is the owner of a contract and can perform administrative tasks. +However, this approach reveals the owner’s identity to all observers, creating privacy and security risks. +In privacy-sensitive applications—such as confidential voting systems, private treasuries, +or anonymous governance—revealing the administrator’s identity may compromise the entire system’s confidentiality. +This library provides the `ZOwnablePK` module that implements shielded ownership—administrative control without identity disclosure. +The owner’s public key is never revealed on-chain; +instead, the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity. + +### Commitment Scheme + +The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy, +unlinkability, and collision resistance across deployments and ownership transfers. + +#### Owner ID Computation + +The foundation of the system is the owner identifier, computed as: + +```typescript +id = SHA256(pk, nonce) +``` + +Where `pk` is the owner’s public key and `nonce` is a secret value that may be either randomly generated for maximum privacy +or deterministically derived for recoverability. +This identifier serves as a privacy-preserving alternative to exposing the raw public key, +ensuring the owner’s identity remains confidential. + +#### Owner Commitment Computation + +The final ownership commitment stored on-chain is computed as: + +```typescript +commitment = SHA256(id, instanceSalt, counter, pad(32, "ZOwnablePK:shield:")) +``` + +This multi-element hash provides several security properties: + +* `id`: The privacy-preserving owner identifier described above. +* `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances, +even when the same owner and nonce are used. +* `counter`: Incremented with each ownership transfer to ensure unlinkability—each transfer produces a completely different commitment +even with the same underlying owner. +* `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions with other commitment schemes +and enables safe protocol extensions. + +#### Security Properties + +This commitment scheme ensures that: + +* Public keys are never revealed on-chain. +* Observers cannot correlate past and future ownership. +* Cross-contract collisions are prevented through instance-specific salting. + +### Nonce Generation Strategies + +The choice of nonce generation strategy represents a fundamental trade-off between simplicity/security and recoverability. +Both approaches are valid, and the best choice depends on your specific threat model and operational requirements. + +#### Random Nonce + +Generating a cryptographically strong random nonce provides the strongest privacy guarantees: + +```typescript +const randomNonce = crypto.getRandomValues(new Uint8Array(32)); +const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce); +``` + +This approach is easy to generate and ensures maximum unlinkability—even with sophisticated analysis, +observers cannot correlate ownership across different contracts or time periods. +However, it requires secure backup of both the private key and the nonce. +**Loss of either component results in permanent, irrecoverable loss of ownership.** + +#### Deterministic Nonce + +Deriving the nonce deterministically enables recovery through derivation schemes. Some examples: + +* `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure. +* `H(publicKey + userPassphrase + context)` - requires both public key and passphrase. +* `H(signature + context) where signature = sign(context)` - leverages wallet without exposing private key. + + + When using signature-based nonce derivation, + ensure the wallet/library uses deterministic signatures ([ed25519] or [rfc6979] for ECDSA). + Non-deterministic signatures will generate different nonces on each signing, making recovery impossible. + Test the implementation by signing the same message twice then verify that the signatures match. + + +**Context-Dependent Derivations:** + +* Include contract address, deployment timestamp, user ID, etc. +* Trade-off: more context is more unique but harder to recreate. + + + Approaches that avoid private key exposure (public key + passphrase, signature-based) + are generally recommended for operational security. + + +Deriving the nonce deterministically from an [Air-Gapped Public Key](#air-gapped-public-key-agpk) +and user passphrase provides a balance of security and recoverability: + +```typescript +// Example: Scrypt-based derivation +import { scryptSync } from 'node:crypto'; + +const deterministicNonce = scryptSync( + userPassphrase + publicKey + ":ZOwnablePK:nonce:v1", + 32, + { N: 16384, r: 8, p: 1 } // Standard scrypt parameters +); +const recoverableOwnerId = ZOwnablePK._computeOwnerId(publicKey, deterministicNonce); +``` + +**Security Considerations** + +The `ZOwnablePK` module remains agnostic to nonce generation methods, +placing the security/convenience decision entirely with the user. +Key considerations include: + +* **Backup requirements**: Random nonces require additional secure storage. +* **Recovery scenarios**: Deterministic nonces enable recovery. +* **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments. +* **Rotation costs**: Changing nonces requires ownership transfer transactions with associated DUST costs. + +Users should carefully evaluate their threat model, operational requirements, and privacy needs when selecting a nonce generation strategy, +as this choice cannot be easily changed without transferring ownership. + +### Air-Gapped Public Key (AGPK) + +For maximum privacy guarantees, +users should employ an Air-Gapped Public Key (AGPK) exclusively for contract ownership and administrative circuits. +An AGPK is a public key that maintains complete isolation from all other on-chain activities, +similar to how air-gapped systems are isolated from networks to prevent data leakage. + +#### The Privacy Enhancement + +While `ZOwnablePK` provides cryptographic privacy through its commitment scheme, +operational security practices like using an AGPK provide an additional layer of protection against correlation attacks. +Even with the strongest cryptographic commitments, +reusing a public key across different on-chain activities can potentially compromise privacy through transaction pattern analysis. + +#### AGPK Principles + +An Air-Gapped Public Key must adhere to strict isolation principles: + +* **Never used before**: The private key material (including any seed, parent key, or entropy source from which this key is derived) +has never generated any public key that appears in any on-chain transaction, across any blockchain network. +The key material must be cryptographically pure. +* **Never used elsewhere**: From the moment of AGPK generation until its destruction, +the private key material is used exclusively for this contract’s administrative functions (i.e. [assertOnlyOwner]). +No other public keys may ever be derived from or generated with the same key material. +* **Never used again**: Users commit to destroying all copies of the private key material upon ownership renunciation or transfer. +This relies entirely on user discipline and cannot be externally verified or enforced. + +**Best Practices Recommendation** + +While neither required nor enforced by the `ZOwnablePK` module, +an Air-Gapped Public Key provides strong operational privacy hygiene for shielded contract administration. +Users should evaluate their threat model and privacy requirements when deciding whether to implement AGPK practices. + + + The effectiveness of an AGPK depends entirely on abiding by the AGPK principles. + A single transaction using the key outside the administrative context compromises all privacy benefits. + + +### Usage + +Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits. +It’s recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes. + +```typescript +// MyZOwnablePKContract.compact + +pragma language_version >= {{compact_language_version}}; + +import CompactStandardLibrary; +import "./compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK" + prefix ZOwnablePK_; + +constructor( + initOwnerCommitment: Bytes<32>, + instanceSalt: Bytes<32>, +) { + ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); +} + +export circuit owner(): Bytes<32> { + return ZOwnablePK_owner(); +} + +export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { + return ZOwnablePK_transferOwnership(disclose(newOwnerCommitment)); +} + +export circuit renounceOwnership(): [] { + return ZOwnablePK_renounceOwnership(); +} +``` + +Similar to the [Ownable](#usage) module, +circuits can be protected so that only the contract owner may them by adding `assertOnlyOwner` as the first line in the circuit body like this: + +```typescript +export circuit mySensitiveCircuit(): [] { + ZOwnablePK_assertOnlyOwner(); + + // Do something +} +``` + +This covers the basics for creating a contract, but before deploying the contract, +the owner’s id must be derived for the commitment scheme because it’s required to deploy the contract. + +First, the owner needs to generate a secret nonce that’s stored in the owner’s private state. +See [Nonce Generation Strategies](#nonce-generation-strategies). + +Once the owner has the secret nonce generated, they can insert their public key and nonce into the following: + +```typescript +import { + CompactTypeBytes, + CompactTypeVector, + persistentHash, +} from '@midnight-ntwrk/compact-runtime'; +import { getRandomValues } from 'node:crypto'; + +// Owner ID +const generateId = ( + pk: Uint8Array, + nonce: Uint8Array, +): Uint8Array => { + const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); + return persistentHash(rt_type, [pk, nonce]); +}; + +// Instance salt for the constructor +const generateInstanceSalt = (): Uint8Array => { + return getRandomValues(new Uint8Array(32)); +} +``` + + + Another way to get the user ID is to expose [_computeOwnerId] in the contract and call this circuit off chain through a contract simulator. + Be on the lookout for future tooling that makes this process easier. + + diff --git a/docs/modules/ROOT/pages/security.adoc b/docs/content/contracts-compact/security.mdx similarity index 64% rename from docs/modules/ROOT/pages/security.adoc rename to docs/content/contracts-compact/security.mdx index 963a8dca..af857de7 100644 --- a/docs/modules/ROOT/pages/security.adoc +++ b/docs/content/contracts-compact/security.mdx @@ -1,26 +1,30 @@ -= Security +--- +title: Security +--- The following documentation provides context, reasoning, and examples of modules found in the Security directory. -== Initializable +## Initializable The Initializable module provides a simple mechanism that mimics the functionality of a constructor. -More specifically, it enables logic to be performed once and only once which is useful to set up a contract’s initial state when a constructor cannot be used, for example when there are circular dependencies at construction time. +More specifically, it enables logic to be performed once and only once to set up a contract's initial state. +This is useful when a constructor cannot be used, +for example when there are circular dependencies at construction time. Many modules also use the initializable pattern which ensures that implementing contracts: - Don't allow circuit calls until the contract is initialized. - Can only initialize the contract once. -=== Usage +### Usage -```typescript +```ts // CustomContractStateSetup.compact -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import './node_modules/@openzeppelin-compact/contracts/src/security/Initializable'; +import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable'; export ledger _fieldAfterDeployment: Field; @@ -43,31 +47,34 @@ export circuit checkFieldAfterDeployment(): Field { } ``` -== Pausable +## Pausable -:ownable: xref:ownable.adoc[Ownable] -:assertPaused: xref:api/utils.adoc#PausableModule-assertPaused[assertPaused] -:assertNotPaused: xref:api/utils.adoc#PausableModule-assertNotPaused[assertNotPaused] +[Ownable]: ./ownable +[assertPaused]: ./api/security#Pausable-assertPaused +[assertNotPaused]: ./api/security#Pausable-assertNotPaused The Pausable module allows contracts to implement an emergency stop mechanism. -This can be useful for scenarios such as preventing trades until the end of an evaluation period or having an emergency switch to freeze all transactions in the event of a large bug. +This can be useful for scenarios such as preventing trades until the end of an evaluation period +or having an emergency switch to freeze all transactions in the event of a large bug. To become pausable, the contract should include `pause` and `unpause` circuits (which should be protected). For circuits that should be available only when paused or not, -insert calls to {assertPaused} and {assertNotPaused} respectively. +insert calls to [assertPaused] and [assertNotPaused] respectively. -=== Usage +### Usage -For example (using the {ownable} module for access control): +For example (using the [Ownable] module for access control): -```typescript +```ts // OwnablePausable.compact -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import './node_modules/@openzeppelin-compact/contracts/src/security/Initializable' prefix Initializable_; -import './node_modules/@openzeppelin-compact/contracts/src/access/Ownable' prefix Ownable_; +import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/security/Initializable' + prefix Initializable_; +import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/access/Ownable' + prefix Ownable_; constructor(initOwner: Either) { Ownable_initialize(initOwner); diff --git a/docs/modules/ROOT/pages/utils.adoc b/docs/content/contracts-compact/utils.mdx similarity index 61% rename from docs/modules/ROOT/pages/utils.adoc rename to docs/content/contracts-compact/utils.mdx index d5d0d6af..66b41195 100644 --- a/docs/modules/ROOT/pages/utils.adoc +++ b/docs/content/contracts-compact/utils.mdx @@ -1,20 +1,17 @@ -= Utils - -The following documentation provides context, reasoning, and examples of modules found in the Utils directory. - -== Utils +--- +title: Utils +--- The Utils module provides miscellaneous circuits and common utilities for Compact contract development. -=== Usage +### Usage ```typescript -// UtilsExample.compact - -pragma language_version >= 0.18.0; +pragma language_version >= {{compact_language_version}}; import CompactStandardLibrary; -import './node_modules/@openzeppelin-compact/contracts/src/utils/Utils'; +import './compact-contracts/node_modules/@openzeppelin-compact/contracts/src/utils/Utils' + prefix Utils_; export circuit performActionWhenEqual( a: Either, diff --git a/docs/content/contracts-compact/utils/constants.js b/docs/content/contracts-compact/utils/constants.js new file mode 100644 index 00000000..a204daa8 --- /dev/null +++ b/docs/content/contracts-compact/utils/constants.js @@ -0,0 +1,2 @@ +export const COMPACT_COMPILER_VERSION = "0.24.0"; +export const COMPACT_LANGUAGE_VERSION = "0.16.0"; diff --git a/docs/content/contracts-compact/utils/replacements.ts b/docs/content/contracts-compact/utils/replacements.ts new file mode 100644 index 00000000..6072127f --- /dev/null +++ b/docs/content/contracts-compact/utils/replacements.ts @@ -0,0 +1,9 @@ +import { COMPACT_COMPILER_VERSION, COMPACT_LANGUAGE_VERSION } from "./constants"; + +export const REPLACEMENTS = { + include: ['**/content/contracts-compact/**/*.mdx'], + replacements: { + compact_compiler_version: COMPACT_COMPILER_VERSION, + compact_language_version: COMPACT_LANGUAGE_VERSION + } +} diff --git a/docs/modules/ROOT/pages/zkCircuits101.adoc b/docs/content/contracts-compact/zkCircuits101.mdx similarity index 50% rename from docs/modules/ROOT/pages/zkCircuits101.adoc rename to docs/content/contracts-compact/zkCircuits101.mdx index bc3bbebc..5e954d15 100644 --- a/docs/modules/ROOT/pages/zkCircuits101.adoc +++ b/docs/content/contracts-compact/zkCircuits101.mdx @@ -1,17 +1,36 @@ -= ZK Circuits 101 +--- +title: ZK Circuits 101 +--- + +{/* links */} +[the math behind the scenes needs it that way]: https://zkhack.dev/whiteboard/module-three/ + +## What are ZK Circuits? Compact code is compiled into arithmetic circuits, which are mathematical representations of the contract's logic. -These circuits are made up of arithmetic gates. The gates enforce the rules and constraints defined in the Compact program. +These circuits are made up of arithmetic gates. +The gates enforce the rules and constraints defined in the Compact program. At a high level, ZK circuits can be thought of as a magic puzzle board that proves a series of steps was followed correctly - like a recipe. The puzzle board is laid out in a grid like a giant sheet of graph paper with a certain number of rows. Each row is a space where a step or rule that needs to be followed is written. These steps/rules correspond to the gates that make up the arithmetic circuit. -The size of the board is called the **domain size** which is referred to as `k` in the documentation. It’s always a power of 2 (like 256, 512, 1024, etc.), because https://zkhack.dev/whiteboard/module-three/[the math behind the scenes needs it that way]. -Now, just because the board has 1024 rows doesn’t mean it uses all of them. Maybe the recipe takes only 563 steps, so only 563 rows are filled and the rest are left blank. These filled-in rows are called **used rows**. +## Circuit Size and Domain + +The size of the board is called the **domain size** which is referred to as `k` in the documentation. +It’s always a power of 2 (like 256, 512, 1024, etc.), +because [the math behind the scenes needs it that way]. +Now, just because the board has 1024 rows doesn’t mean it uses all of them. +Maybe the recipe takes only 563 steps, so only 563 rows are filled and the rest are left blank. +These filled-in rows are called **used rows**. So "k = 10, rows = 563" in the API Reference documentation that means "this circuit has a size of 2^10 = 1024 rows and only uses 563 rows". -Why is this important? Well, when writing ZK circuits the size and number of rules to follow as should be as small as possible. -The number of rules in a zero-knowledge circuit directly impacts both the prover time (how long it takes to generate a proof) and, to a lesser extent, the proof size and verifier time. -Larger circuits with more rules require more computation to generate a proof, which can make proof generation slower and more resource-intensive. -This is especially relevant for privacy-preserving blockchains like Midnight, where proof generation is often the most computationally expensive part of a transaction. +## Why Circuit Size Matters + +Why is this important? Well, when writing ZK circuits the size and number of rules to follow should be as small as possible. +The number of rules in a zero-knowledge circuit directly impacts both the prover time +(how long it takes to generate a proof) and, to a lesser extent, the proof size and verifier time. +Larger circuits with more rules require more computation to generate a proof, +which can make proof generation slower and more resource-intensive. +This is especially relevant for privacy-preserving blockchains like Midnight, +where proof generation is often the most computationally expensive part of a transaction. diff --git a/docs/content/contracts-stylus/0.1.0/access-control.mdx b/docs/content/contracts-stylus/0.1.0/access-control.mdx new file mode 100644 index 00000000..319fd386 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/access-control.mdx @@ -0,0 +1,172 @@ +--- +title: 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 [steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7). + +## 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 for Stylus provides [`Ownable`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html) for implementing ownership in your contracts. + +```rust +use openzeppelin_stylus::access::ownable::Ownable; + +sol_storage! { + #[entrypoint] + struct OwnableExample { + #[borrow] + Ownable ownable; + } +} + +#[public] +#[inherit(Ownable)] +impl MyContract { + fn normal_thing(&self) { + // anyone can call this normal_thing() + } + + pub fn special_thing( + &mut self, + ) -> Result<(), Vec> { + self.ownable.only_owner()?; + + // only the owner can call special_thing()! + + Ok(()) + } +} +``` + +At deployment, the [`owner`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.owner) of an `Ownable` contract is set to the provided `initial_owner` parameter. + +Ownable also lets you: + +* [`transfer_ownership`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.transfer_ownership) from the owner account to a new one, and +* [`renounce_ownership`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.renounce_ownership) for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `only_owner` will no longer be callable! + + +Note that **a contract can also be the owner of another one**! This opens the door to using, for example, a [Gnosis Safe](https://gnosis-safe.io), an [Aragon DAO](https://aragon.org), 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 [MakerDAO](https://makerdao.com), use systems similar to this one. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. [_Role-Based Access Control (RBAC)_](https://en.wikipedia.org/wiki/Role-based_access_control) 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 `only_owner`. This check can be enforced through the `only_role` 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 `AccessControl` + +OpenZeppelin Contracts provides [`AccessControl`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/control/struct.AccessControl.html) 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 [ERC-20 token](/contracts-stylus/0.1.0/erc20) to define a 'minter' role, which allows accounts that have it create new tokens. Note that the example is unassuming of the way you construct your contract. + +```rust +sol_storage! { + #[entrypoint] + struct Example { + #[borrow] + Erc20 erc20; + #[borrow] + AccessControl access; + } +} + +// `keccak256("MINTER_ROLE")` +pub const MINTER_ROLE: [u8; 32] = [ + 159, 45, 240, 254, 210, 199, 118, 72, 222, 88, 96, 164, 204, 80, 140, 208, + 129, 140, 133, 184, 184, 161, 171, 76, 238, 239, 141, 152, 28, 137, 86, + 166, +]; + +#[public] +#[inherit(Erc20, AccessControl)] +impl Example { + pub const MINTER_ROLE: [u8; 32] = MINTER_ROLE; + + pub fn mint(&mut self, to: Address, amount: U256) -> Result<(), Vec> { + if self.access.has_role(Example::MINTER_ROLE, msg::sender()) { + return Err(Vec::new()); + } + self.erc20._mint(to, amount)?; + Ok(()) + } +} +``` + + +Make sure you fully understand how [`AccessControl`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/access/control/struct.AccessControl.html) 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 `only_role` modifier: + +```rust +sol_storage! { + #[entrypoint] + struct Example { + #[borrow] + Erc20 erc20; + #[borrow] + AccessControl access; + } +} + +// `keccak256("MINTER_ROLE")` +pub const MINTER_ROLE: [u8; 32] = [ + 159, 45, 240, 254, 210, 199, 118, 72, 222, 88, 96, 164, 204, 80, 140, 208, + 129, 140, 133, 184, 184, 161, 171, 76, 238, 239, 141, 152, 28, 137, 86, + 166, +]; + +// `keccak256("BURNER_ROLE")` +pub const BURNER_ROLE: [u8; 32] = [ + 60, 17, 209, 108, 186, 255, 208, 29, 246, 156, 225, 196, 4, 246, 52, 14, + 224, 87, 73, 143, 95, 0, 36, 97, 144, 234, 84, 34, 5, 118, 168, 72, +]; + +#[public] +#[inherit(Erc20, AccessControl)] +impl Example { + pub const MINTER_ROLE: [u8; 32] = MINTER_ROLE; + pub const BURNER_ROLE: [u8; 32] = BURNER_ROLE; + + pub fn mint(&mut self, to: Address, amount: U256) -> Result<(), Vec> { + self.access.only_role(Example::MINTER_ROLE.into())?; + self.erc20._mint(to, amount)?; + Ok(()) + } + + pub fn mint(&mut self, from: Address, amount: U256) -> Result<(), Vec> { + self.access.only_role(Example::BURNER_ROLE.into())?; + self.erc20._burn(to, amount)?; + Ok(()) + } +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 Roles + +The ERC-20 token example above uses `_grant_role`, 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 `has_role` 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 `grant_role` and `revoke_role` 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 `_set_role_admin` is used to select a new admin role. + +Note that, by default, no accounts are granted the 'minter' or 'burner' roles. We assume you use a constructor to set the default admin role as the role of the deployer, or have a different mechanism where you make sure that you are able to grant 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 `grant_role` to give minting or burning permission, and `revoke_role` 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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. diff --git a/docs/content/contracts-stylus/0.1.0/crypto.mdx b/docs/content/contracts-stylus/0.1.0/crypto.mdx new file mode 100644 index 00000000..e6765b88 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/crypto.mdx @@ -0,0 +1,29 @@ +--- +title: Crypto +--- + +The OpenZeppelin Rust Contracts provide a crate for common cryptographic procedures in a blockchain environment. The following documents the available functionality. + +## 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. + + +OpenZeppelin Contracts provides a [JavaScript library](https://github.com/OpenZeppelin/merkle-tree) for building trees off-chain and generating proofs. + + +[`MerkleProof`](https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html) provides: + +* [`verify`](https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify) - can prove that some value is part of a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). +* [`verify_multi_proof`](https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof) - can prove multiple values are part of a Merkle tree. + +```rust +pub fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { + let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); + Verifier::::verify(&proof, *root, *leaf) +} +``` + +Note that these functions use `keccak256` as the hashing algorithm, but our library also provides generic counterparts: [`verify_with_builder`](https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_with_builder) and [`verify_multi_proof_with_builder`](https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof_with_builder). + +We also provide an adapter [`hash`](https://docs.rs/openzeppelin-crypto/0.1.0/openzeppelin_crypto/hash/index.html) module to use your own hashers in conjunction with them that resembles Rust’s standard library’s API. diff --git a/docs/content/contracts-stylus/0.1.0/deploy.mdx b/docs/content/contracts-stylus/0.1.0/deploy.mdx new file mode 100644 index 00000000..8d02511f --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/deploy.mdx @@ -0,0 +1,72 @@ +--- +title: Deploying Contracts +--- + +To deploy a contract written in Rust using the Stylus SDK, the Arbitrum Stylus team created `cargo-stylus`. For now, this CLI tool doesn’t support deploying contracts with constructors. + +We implemented [`koba`](https://github.com/OpenZeppelin/koba), a solution you can use today to write constructors in Solidity for your Rust smart contracts. + + +Deploying `oppenzeppelin-stylus` contracts must be done using `koba`. Using `cargo-stylus` directly may initialize storage with unexpected values. + + +This solution is meant to be temporary. In the near future, we expect support for constructors to be implemented in `cargo-stylus` or the Stylus VM itself. + +## Constructors + +Deployment transactions in Ethereum are composed of three sections: + +* A `prelude` - The bytecode prefix whose execution gets triggered by the deployment transaction. +* A `runtime` - The bytecode of the smart contract stored on-chain. +* Constructor arguments - ABI-encoded arguments received by the constructor. + +The prelude section is a [smart contract constructor](https://docs.soliditylang.org/en/latest/contracts.html#constructors) compiled to bytecode. The runtime is the rest of the smart contract. All three sections combined are called `**binary**`. + +Deployment transactions with an input of only compressed wasm are not yet supported in Stylus. That is, only the `runtime` is actual webassembly. + +Moreover, the prelude of deployment transactions using `cargo-stylus` is [hard-coded](https://github.com/OffchainLabs/cargo-stylus/blob/be9faca7720b534de7ec210fa5a071eae79824ec/check/src/deploy.rs#L102-L114). + +`koba` solves this by putting together the Solidity constructor, the compiled webassembly and the abi-encoded constructor arguments. It can be used both as a CLI tool or as a library in Rust projects. For a complete example of using `koba` as a library, see the [basic token example](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/examples/basic/README.md). For an example of deploying a contract using the command line see [koba’s README](https://github.com/OpenZeppelin/koba#koba-deploy). + +## Usage + +For a contract like this: + +```rust +sol_storage! { + #[entrypoint] + pub struct Counter { + uint256 number; + } +} + +#[public] +impl Counter { + pub fn number(&self) -> U256 { + self.number.get() + } + + pub fn increment(&mut self) { + let number = self.number.get(); + self.set_number(number + U256::from(1)); + } +} +``` + +and a constructor like this: + +```solidity +contract Counter { + uint256 private _number; + + constructor() { + _number = 5; + } +} +``` + +The following command will deploy your contract: + +```bash +$ koba deploy --sol --wasm --args -e --private-key +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc20-burnable.mdx b/docs/content/contracts-stylus/0.1.0/erc20-burnable.mdx new file mode 100644 index 00000000..172b80bd --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc20-burnable.mdx @@ -0,0 +1,44 @@ +--- +title: ERC-20 Burnable +--- + +Extension of [ERC-20](/contracts-stylus/0.1.0/erc20) that allows token holders to destroy both their own tokens and those that they have an allowance for, in a way that can be recognized off-chain (via event analysis). + +## Usage + +In order to make [`ERC-20 Burnable`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/burnable/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::IErc20Burnable, + Erc20, IErc20, + }, +}; + +sol_storage! { + #[entrypoint] + struct Erc20Example { + #[borrow] + Erc20 erc20; + } +} + +#[public] +#[inherit(Erc20)] +impl Erc20Example { + pub fn burn(&mut self, value: U256) -> Result<(), Vec> { + // ... + self.erc20.burn(value).map_err(|e| e.into()) + } + + pub fn burn_from( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Vec> { + // ... + self.erc20.burn_from(account, value).map_err(|e| e.into()) + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc20-capped.mdx b/docs/content/contracts-stylus/0.1.0/erc20-capped.mdx new file mode 100644 index 00000000..4fbd6d29 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc20-capped.mdx @@ -0,0 +1,88 @@ +--- +title: ERC-20 Capped +--- + +Extension of [ERC-20](/contracts-stylus/0.1.0/erc20) that adds a cap to the supply of tokens. + +## Usage + +In order to make [`ERC-20 Capped`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/capped/index.html) methods supervising the supply of tokens, you need to add them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::{capped, Capped}, + Erc20, IErc20, + } +}; + +sol_storage! { + #[entrypoint] + struct Erc20Example { + #[borrow] + Erc20 erc20; + #[borrow] + Capped capped; + } +} + +#[public] +#[inherit(Erc20, Capped)] +impl Erc20Example { + // Add token minting feature. + // + // Make sure to handle `Capped` properly. You should not call + // [`Erc20::_update`] to mint tokens -- it will the break `Capped` + // mechanism. + pub fn mint( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Vec> { + self.pausable.when_not_paused()?; + let max_supply = self.capped.cap(); + + // Overflow check required. + let supply = self + .erc20 + .total_supply() + .checked_add(value) + .expect("new supply should not exceed `U256::MAX`"); + + if supply > max_supply { + return Err(capped::Error::ExceededCap( + capped::ERC20ExceededCap { + increased_supply: supply, + cap: max_supply, + }, + ))?; + } + + self.erc20._mint(account, value)?; + Ok(()) + } +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc20Example { + // ... + + uint256 private _cap; + + error ERC20InvalidCap(uint256 cap); + + constructor(uint256 cap_) { + // ... + if (cap_ == 0) { + revert ERC20InvalidCap(0); + } + + _cap = cap_; + + // ... + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc20-metadata.mdx b/docs/content/contracts-stylus/0.1.0/erc20-metadata.mdx new file mode 100644 index 00000000..611cc667 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc20-metadata.mdx @@ -0,0 +1,54 @@ +--- +title: ERC-20 Metadata +--- + +Extension of [ERC-20](/contracts-stylus/0.1.0/erc20) that adds the optional metadata functions from the ERC20 standard. + +## Usage + +In order to make [`ERC-20 Metadata`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/metadata/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::Erc20Metadata, + Erc20, + }, +}; + +sol_storage! { + #[entrypoint] + struct Erc20Example { + #[borrow] + Erc20 erc20; + #[borrow] + Erc20Metadata metadata; + } +} + +#[public] +#[inherit(Erc20, Erc20Metadata, Capped, Pausable)] +impl Erc20Example { + // ... +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc20Example { + // ... + + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_) { + // ... + + _name = name_; + _symbol = symbol_; + + // ... + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc20-pausable.mdx b/docs/content/contracts-stylus/0.1.0/erc20-pausable.mdx new file mode 100644 index 00000000..1c7b7a50 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc20-pausable.mdx @@ -0,0 +1,99 @@ +--- +title: ERC-20 Pausable +--- + +ERC20 token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug. + +## Usage + +In order to make your ERC20 token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/pausable/index.html) contract and apply its mechanisms to ERC20 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::Erc20, + utils::Pausable, +}; + +sol_storage! { + #[entrypoint] + struct Erc20Example { + #[borrow] + Erc20 erc20; + #[borrow] + Pausable pausable; + } +} + +#[public] +#[inherit(Erc20, Pausable)] +impl Erc20Example { + pub fn burn(&mut self, value: U256) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20.burn(value).map_err(|e| e.into()) + } + + pub fn burn_from( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20.burn_from(account, value).map_err(|e| e.into()) + } + + pub fn mint( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20._mint(account, value)?; + Ok(()) + } + + + pub fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20.transfer(to, value).map_err(|e| e.into()) + } + + pub fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20.transfer_from(from, to, value).map_err(|e| e.into()) + } + +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc20Example { + bool private _paused; + + constructor() { + _paused = false; + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc20-permit.mdx b/docs/content/contracts-stylus/0.1.0/erc20-permit.mdx new file mode 100644 index 00000000..e4ad4822 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc20-permit.mdx @@ -0,0 +1,37 @@ +--- +title: ERC-20 Permit +--- + +Adds the permit method, which can be used to change an account’s ERC20 allowance (see [`IErc20::allowance`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.allowance)) by presenting a message signed by the account. By not relying on [`IErc20::approve`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.approve), the token holder account doesn’t need to send a transaction, and thus is not required to hold Ether at all. + +## Usage + +In order to have [`ERC-20 Permit`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/permit/index.html) token, you need to use only this contract without [ERC-20](/contracts-stylus/0.1.0/erc20) as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::extensions::Erc20Permit, utils::cryptography::eip712::IEip712, +}; + +sol_storage! { + #[entrypoint] + struct Erc20PermitExample { + #[borrow] + Erc20Permit erc20_permit; + } + + struct Eip712 {} +} + +// Define `NAME` and `VERSION` for your contract. +impl IEip712 for Eip712 { + const NAME: &'static str = "ERC-20 Permit Example"; + const VERSION: &'static str = "1"; +} + +#[public] +#[inherit(Erc20Permit)] +impl Erc20PermitExample { + // ... +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc20.mdx b/docs/content/contracts-stylus/0.1.0/erc20.mdx new file mode 100644 index 00000000..6e09922b --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc20.mdx @@ -0,0 +1,79 @@ +--- +title: ERC-20 +--- + +An ERC-20 token contract keeps track of [_fungible_ tokens](/contracts-stylus/0.1.0/tokens#different-kinds-of-tokens): any token is exactly equal to any other token; no token has a special right 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 provide many ERC20-related contracts for Arbitrum Stylus. +On the [`API reference`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/struct.Erc20.html) you’ll find detailed information on their properties and usage. + +## 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. + +```rust +sol_storage! { + #[entrypoint] + struct GLDToken { + #[borrow] + Erc20 erc20; + #[borrow] + Erc20Metadata metadata; + } +} + +#[public] +#[inherit(Erc20, Erc20Metadata)] +impl GLDToken {} +``` + +Our contracts are often used via stylus-sdk [inheritance](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#inheritance-inherit-and-borrow), and here we’re reusing [`ERC20`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/struct.Erc20.html) for both the basic standard implementation and with optional extensions. + +## 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, ERC20 provides a [`decimals`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc20/extensions/metadata/trait.IErc20Metadata.html#tymethod.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 an exceptional reason not to. +When minting tokens or transferring them around, you will be actually sending the number `GLD * (10 ** decimals)`. + + +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. For example, to use `16` decimals, you would do: + +```rust +pub fn decimals(&self) -> u8 { + 16 +} +``` + +So if you want to send `5` tokens using a token contract with `18` decimals, the method to call will actually be: + +```rust +token.transfer(recipient, 5 * uint!(10_U256).pow(uint!(18_U256))); +``` + +## Extensions +Additionally, there are multiple custom extensions, including: + +* [ERC-20 Burnable](/contracts-stylus/0.1.0/erc20-burnable): destruction of own tokens. +* [ERC-20 Capped](/contracts-stylus/0.1.0/erc20-capped): enforcement of a cap to the total supply when minting tokens. +* [ERC-20 Metadata](/contracts-stylus/0.1.0/erc20-metadata): the extended ERC20 interface including the name, symbol, and decimals functions. +* [ERC-20 Pausable](/contracts-stylus/0.1.0/erc20-pausable): ability to pause token transfers. +* [ERC-20 Permit](/contracts-stylus/0.1.0/erc20-permit): gasless approval of tokens (standardized as [`EIP-2612`](https://eips.ethereum.org/EIPS/eip-2612)). diff --git a/docs/content/contracts-stylus/0.1.0/erc721-burnable.mdx b/docs/content/contracts-stylus/0.1.0/erc721-burnable.mdx new file mode 100644 index 00000000..7f2fb12e --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721-burnable.mdx @@ -0,0 +1,35 @@ +--- +title: ERC-721 Burnable +--- + +[ERC-721](/contracts-stylus/0.1.0/erc721) Token that can be burned (destroyed). + +## Usage + +In order to make [`ERC-721 Burnable`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/burnable/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + extensions::IErc721Burnable, + Erc721, IErc721, + }, +}; + +sol_storage! { + #[entrypoint] + struct Erc721Example { + #[borrow] + Erc721 erc721; + } +} + +#[public] +#[inherit(Erc721)] +impl Erc721Example { + pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { + // ... + self.erc721.burn(token_id).map_err(|e| e.into()) + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc721-consecutive.mdx b/docs/content/contracts-stylus/0.1.0/erc721-consecutive.mdx new file mode 100644 index 00000000..dc6acf55 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721-consecutive.mdx @@ -0,0 +1,165 @@ +--- +title: ERC-721 Consecutive +--- + +Consecutive extension for [ERC-721](/contracts-stylus/0.1.0/erc721) is useful for efficiently minting multiple tokens in a single transaction. This can significantly reduce gas costs and improve performance when creating a large number of tokens at once. + +## Usage + +In order to make [`ERC-721 Consecutive`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/consecutive/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::token::erc721::extensions::consecutive::{ + Erc721Consecutive, Error, +}; + +sol_storage! { + #[entrypoint] + struct Erc721ConsecutiveExample { + #[borrow] + Erc721Consecutive erc721_consecutive; + } +} + +#[public] +#[inherit(Erc721Consecutive)] +impl Erc721ConsecutiveExample { + pub fn burn(&mut self, token_id: U256) -> Result<(), Error> { + self.erc721_consecutive._burn(token_id) + } + + pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { + self.erc721_consecutive._mint(to, token_id) + } +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc721ConsecutiveExample { + mapping(uint256 tokenId => address) private _owners; + mapping(address owner => uint256) private _balances; + mapping(uint256 tokenId => address) private _tokenApprovals; + mapping(address owner => mapping(address operator => bool)) private _operatorApprovals; + + Checkpoint160[] private _checkpoints; // _sequentialOwnership + mapping(uint256 bucket => uint256) private _data; // _sequentialBurn + uint96 private _firstConsecutiveId; + uint96 private _maxBatchSize; + + error ERC721InvalidReceiver(address receiver); + error ERC721ForbiddenBatchMint(); + error ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch); + error ERC721ForbiddenMint(); + error ERC721ForbiddenBatchBurn(); + error CheckpointUnorderedInsertion(); + + event ConsecutiveTransfer( + uint256 indexed fromTokenId, + uint256 toTokenId, + address indexed fromAddress, + address indexed toAddress + ); + + struct Checkpoint160 { + uint96 _key; + uint160 _value; + } + + constructor( + address[] memory receivers, + uint96[] memory amounts, + uint96 firstConsecutiveId, + uint96 maxBatchSize) + { + _firstConsecutiveId = firstConsecutiveId; + _maxBatchSize = maxBatchSize; + for (uint256 i = 0; i < receivers.length; i) { + _mintConsecutive(receivers[i], amounts[i]); + } + } + + function latestCheckpoint() internal view returns (bool exists, uint96 _key, uint160 _value) { + uint256 pos = _checkpoints.length; + if (pos == 0) { + return (false, 0, 0); + } else { + Checkpoint160 storage ckpt = _checkpoints[pos - 1]; + return (true, ckpt._key, ckpt._value); + } + } + + function push(uint96 key, uint160 value) internal returns (uint160, uint160) { + return _insert(key, value); + } + + function _insert(uint96 key, uint160 value) private returns (uint160, uint160) { + uint256 pos = _checkpoints.length; + + if (pos > 0) { + Checkpoint160 storage last = _checkpoints[pos - 1]; + uint96 lastKey = last._key; + uint160 lastValue = last._value; + + // Checkpoint keys must be non-decreasing. + if (lastKey > key) { + revert CheckpointUnorderedInsertion(); + } + + // Update or push new checkpoint. + if (lastKey == key) { + _checkpoints[pos - 1]._value = value; + } else { + _checkpoints.push(Checkpoint160({_key: key, _value: value})); + } + return (lastValue, value); + } else { + _checkpoints.push(Checkpoint160({_key: key, _value: value})); + return (0, value); + } + } + + function _mintConsecutive(address to, uint96 batchSize) internal virtual returns (uint96) { + uint96 next = _nextConsecutiveId(); + + // minting a batch of size 0 is a no-op. + if (batchSize > 0) { + if (address(this).code.length > 0) { + revert ERC721ForbiddenBatchMint(); + } + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + + uint256 maxBatchSize = _maxBatchSize; + if (batchSize > maxBatchSize) { + revert ERC721ExceededMaxBatchMint(batchSize, maxBatchSize); + } + + // push an ownership checkpoint & emit event. + uint96 last = next + batchSize - 1; + push(last, uint160(to)); + + // The invariant required by this function is preserved because the new sequentialOwnership checkpoint + // is attributing ownership of `batchSize` new tokens to account `to`. + _increaseBalance(to, batchSize); + + emit ConsecutiveTransfer(next, last, address(0), to); + } + + return next; + } + + function _nextConsecutiveId() private view returns (uint96) { + (bool exists, uint96 latestId,) = latestCheckpoint(); + return exists ? latestId + 1 : _firstConsecutiveId; + } + + function _increaseBalance(address account, uint128 value) internal virtual { + unchecked { + _balances[account] += value; + } + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc721-enumerable.mdx b/docs/content/contracts-stylus/0.1.0/erc721-enumerable.mdx new file mode 100644 index 00000000..ffd26232 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721-enumerable.mdx @@ -0,0 +1,142 @@ +--- +title: ERC-721 Enumerable +--- + +The OpenZeppelin [ERC-721](/contracts-stylus/0.1.0/erc721) Enumerable extension is used to provide additional functionality to the standard ERC-721 token. Specifically, it allows for enumeration of all the token IDs in the contract as well as all the token IDs owned by each account. This is useful for applications that need to list or iterate over tokens, such as marketplaces or wallets. + +## Usage + +In order to make an [ERC-721](/contracts-stylus/0.1.0/erc721) token with [Enumerable](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/enumerable/index.html) flavour, +you need to create a specified contract as follows: + +```rust +use openzeppelin_stylus::token::erc721::{ + extensions::{Erc721Enumerable, IErc721Burnable}, + Erc721, IErc721, +}; + +sol_storage! { + #[entrypoint] + struct Erc721Example { + #[borrow] + Erc721 erc721; + #[borrow] + Erc721Enumerable enumerable; + } +} + +#[public] +#[inherit(Erc721, Erc721Enumerable)] +impl Erc721Example { + pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { + // Retrieve the owner. + let owner = self.erc721.owner_of(token_id)?; + + self.erc721.burn(token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + owner, + token_id, + &self.erc721, + )?; + self.enumerable._remove_token_from_all_tokens_enumeration(token_id); + + Ok(()) + } + + pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Vec> { + self.erc721._mint(to, token_id)?; + + // Update the extension's state. + self.enumerable._add_token_to_all_tokens_enumeration(token_id); + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + pub fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Vec> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.safe_transfer_from(from, to, token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + #[selector(name = "safeTransferFrom")] + pub fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Vec> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.safe_transfer_from_with_data(from, to, token_id, data)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + pub fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Vec> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.transfer_from(from, to, token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc721-metadata.mdx b/docs/content/contracts-stylus/0.1.0/erc721-metadata.mdx new file mode 100644 index 00000000..a3f556e9 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721-metadata.mdx @@ -0,0 +1,60 @@ +--- +title: ERC-721 Metadata +--- + +Extension of [ERC-721](/contracts-stylus/0.1.0/erc721) that adds the optional metadata functions from the ERC721 standard. + +## Usage + +In order to make [`ERC-721 Metadata`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + extensions::Erc721Metadata, + Erc721, + }, +}; + +sol_storage! { + #[entrypoint] + struct Erc721Example { + #[borrow] + Erc721 erc721; + #[borrow] + Erc721Metadata metadata; + } +} + +#[public] +#[inherit(Erc721, Erc721Metadata)] +impl Erc721Example { + // ... + + #[selector(name = "tokenURI")] + pub fn token_uri(&self, token_id: U256) -> Result> { + Ok(self.metadata.token_uri(token_id, &self.erc721)?) + } +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). +Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc721Example { + // ... + + string private _name; + string private _symbol; + string private _baseUri; + + constructor(string memory name_, string memory symbol_, string memory baseUri_) { + // ... + _name = name_; + _symbol = symbol_; + _baseUri = baseUri_; + // ... + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc721-pausable.mdx b/docs/content/contracts-stylus/0.1.0/erc721-pausable.mdx new file mode 100644 index 00000000..12d50576 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721-pausable.mdx @@ -0,0 +1,101 @@ +--- +title: ERC-721 Pausable +--- + +ERC721 token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers, e.g. caused by a bug. + +## Usage + +In order to make your ERC721 token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/utils/pausable/index.html) contract and apply its mechanisms to ERC721 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::Erc721, + utils::Pausable, +}; + +sol_storage! { + #[entrypoint] + struct Erc721Example { + #[borrow] + Erc721 erc721; + #[borrow] + Pausable pausable; + } +} + +#[public] +#[inherit(Erc721, Pausable)] +impl Erc721Example { + pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.burn(token_id)?; + Ok(()) + } + + pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721._mint(to, token_id)?; + Ok(()) + } + + pub fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.safe_transfer_from(from, to, token_id)?; + Ok(()) + } + + #[selector(name = "safeTransferFrom")] + pub fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.safe_transfer_from_with_data(from, to, token_id, data)?; + Ok(()) + } + + pub fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Vec> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.transfer_from(from, to, token_id)?; + Ok(()) + } +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc721Example { + bool private _paused; + + constructor() { + _paused = false; + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc721-uri-storage.mdx b/docs/content/contracts-stylus/0.1.0/erc721-uri-storage.mdx new file mode 100644 index 00000000..2085b57f --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721-uri-storage.mdx @@ -0,0 +1,83 @@ +--- +title: ERC-721 Uri Storage +--- + +The OpenZeppelin [ERC-721](/contracts-stylus/0.1.0/erc721) URI Storage extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which can point to metadata about the token, such as images, descriptions, and other attributes. +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-721](/contracts-stylus/0.1.0/erc721) token with [URI Storage](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/uri_storage/index.html) flavour, +your token should also use [`ERC-721 Metadata`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html) extension to provide additional metadata for each token. +You need to create a specified contract as follows: + +```rust +use openzeppelin_stylus::token::erc721::{ + extensions::{ + Erc721Metadata, Erc721UriStorage, + IErc721Burnable, IErc721Metadata, + }, + Erc721, IErc721, +}; + +sol_storage! { + #[entrypoint] + struct Erc721MetadataExample { + #[borrow] + Erc721 erc721; + #[borrow] + Erc721Metadata metadata; + Erc721UriStorage uri_storage; + } +} + +#[public] +#[inherit(Erc721, Erc721Metadata, Erc721UriStorage)] +impl Erc721MetadataExample { + pub fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Vec> { + Ok(self.erc721._mint(to, token_id)?) + } + + pub fn burn(&mut self, token_id: U256) -> Result<(), Vec> { + Ok(self.erc721.burn(token_id)?) + } + + #[selector(name = "tokenURI")] + pub fn token_uri(&self, token_id: U256) -> Result> { + Ok(self.uri_storage.token_uri( + token_id, + &self.erc721, + &self.metadata, + )?) + } + + #[selector(name = "setTokenURI")] + pub fn set_token_uri(&mut self, token_id: U256, token_uri: String) { + self.uri_storage._set_token_uri(token_id, token_uri) + } +} +``` + +Additionally, you need to ensure proper initialization during [contract deployment](/contracts-stylus/0.1.0/deploy). +Make sure to include the following code in your Solidity Constructor: + +```solidity +contract Erc721Example { + // ... + + string private _name; + string private _symbol; + string private _baseUri; + + mapping(uint256 => string) _tokenUris; + + constructor(string memory name_, string memory symbol_, string memory baseUri_) { + // ... + _name = name_; + _symbol = symbol_; + _baseUri = baseUri_; + // ... + } +} +``` diff --git a/docs/content/contracts-stylus/0.1.0/erc721.mdx b/docs/content/contracts-stylus/0.1.0/erc721.mdx new file mode 100644 index 00000000..a83ccf26 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/erc721.mdx @@ -0,0 +1,74 @@ +--- +title: ERC-721 +--- + +We’ve discussed how you can make a _fungible_ token using [ERC-20](/contracts-stylus/0.1.0/erc20), 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 [_non-fungible_ tokens](/contracts-stylus/0.1.0/tokens#different-kinds-of-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 [`API reference`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/struct.Erc721.html) 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 any account can call `awardItem` to mint items. +To restrict what accounts can be minted per item. +We can use an [Access Control](/contracts-stylus/0.1.0/access-control) extension. + +Here’s what a contract for tokenized items might look like: + +```rust +sol_storage! { + #[entrypoint] + struct GameItem { + #[borrow] + Erc721 erc721; + #[borrow] + Metadata metadata; + uint256 _next_token_id; + } +} + +#[public] +#[inherit(Erc721, Metadata)] +impl GameItem { + pub fn award_item( + &mut self, + player: Address, + ) -> Result> { + let token_id = self._next_token_id.get() + uint!(1_U256); + self._next_token_id.set(token_id); + + self.erc721._mint(player, token_id)?; + + Ok(token_id) + } +} +``` + +The [`Erc721Metadata`](https://docs.rs/openzeppelin-stylus/0.1.0/openzeppelin_stylus/token/erc721/extensions/metadata/struct.Erc721Metadata.html) contract is an extension contract of ERC-721. +It extends the contract itself with the name, symbol and base uri for the token. + +Also note that, unlike ERC-20, ERC-721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +For more information about erc721 schema, check out the [ERC-721 specification](https://eips.ethereum.org/EIPS/eip-721). + + +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! + + +## Extensions + +Additionally, there are multiple custom extensions, including: + +* [ERC-721 Burnable](/contracts-stylus/0.1.0/erc721-burnable): A way for token holders to burn their own tokens. +* [ERC-721 Consecutive](/contracts-stylus/0.1.0/erc721-consecutive): An implementation of [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) for minting batches of tokens during construction, in accordance with ERC721. +* [ERC-721 Enumerable](/contracts-stylus/0.1.0/erc721-enumerable): Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* [ERC-721 Metadata](/contracts-stylus/0.1.0/erc721-metadata): Optional extension that adds name, symbol, and token URI, almost always included. +* [ERC-721 Pausable](/contracts-stylus/0.1.0/erc721-pausable): A primitive to pause contract operation. +* [ERC-721 Uri Storage](/contracts-stylus/0.1.0/erc721-uri-storage): A more flexible but more expensive way of storing metadata. diff --git a/docs/content/contracts-stylus/0.1.0/index.mdx b/docs/content/contracts-stylus/0.1.0/index.mdx new file mode 100644 index 00000000..00b2f0fd --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/index.mdx @@ -0,0 +1,9 @@ +--- +title: Contracts for Stylus +--- + +**A library for secure smart contract development** written in Rust for [Stylus](https://docs.arbitrum.io/stylus/stylus-gentle-introduction). + + +You can track our roadmap in our [Github Project](https://github.com/orgs/OpenZeppelin/projects/35). + diff --git a/docs/content/contracts-stylus/0.1.0/tokens.mdx b/docs/content/contracts-stylus/0.1.0/tokens.mdx new file mode 100644 index 00000000..388bed83 --- /dev/null +++ b/docs/content/contracts-stylus/0.1.0/tokens.mdx @@ -0,0 +1,30 @@ +--- +title: 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 + +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 + +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 [fungibility](https://en.wikipedia.org/wiki/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: + +* [ERC-20](/contracts-stylus/0.1.0/erc20): the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. +* [ERC-721](/contracts-stylus/0.1.0/erc721): the de-facto solution for non-fungible tokens, often used for collectibles and games. diff --git a/docs/content/contracts-stylus/0.2.0/access-control.mdx b/docs/content/contracts-stylus/0.2.0/access-control.mdx new file mode 100644 index 00000000..e4f98f19 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/access-control.mdx @@ -0,0 +1,331 @@ +--- +title: 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 [steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7). + +## 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 for Stylus provides [`Ownable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/access/ownable/struct.Ownable.html) for implementing ownership in your contracts. + +```rust +use openzeppelin_stylus::access::ownable::{self, Ownable}; + +#[derive(SolidityError, Debug)] +enum Error { + UnauthorizedAccount(ownable::OwnableUnauthorizedAccount), + InvalidOwner(ownable::OwnableInvalidOwner), + // other custom errors... +} + +impl From for Error { + fn from(value: ownable::Error) -> Self { + match value { + ownable::Error::UnauthorizedAccount(e) => { + Error::UnauthorizedAccount(e) + } + ownable::Error::InvalidOwner(e) => Error::InvalidOwner(e), + } + } +} + +#[entrypoint] +#[storage] +struct OwnableExample { + ownable: Ownable, +} + +#[public] +#[implements(IOwnable)] +impl MyContract { + fn normal_thing(&self) { + // anyone can call this normal_thing() + } + + fn special_thing(&mut self) -> Result<(), Error> { + self.ownable.only_owner()?; + + // only the owner can call special_thing()! + + Ok(()) + } +} + +#[public] +impl IOwnable for MyContract { + type Error = Error; + + fn owner(&self) -> Address { + self.ownable.owner() + } + + fn transfer_ownership( + &mut self, + new_owner: Address, + ) -> Result<(), Self::Error> { + Ok(self.ownable.transfer_ownership(new_owner)?) + } + + fn renounce_ownership(&mut self) -> Result<(), Self::Error> { + Ok(self.ownable.renounce_ownership()?) + } +} +``` + +At deployment, the [`owner`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.owner) of an `Ownable` contract is set to the provided `initial_owner` parameter. + +Ownable also lets you: + +* [`transfer_ownership`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.transfer_ownership) from the owner account to a new one, and +* [`renounce_ownership`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.renounce_ownership) for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `only_owner` will no longer be callable! + + +Note that **a contract can also be the owner of another one**! This opens the door to using, for example, a [Gnosis Safe](https://gnosis-safe.io), an [Aragon DAO](https://aragon.org), 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 [MakerDAO](https://makerdao.com), use systems similar to this one. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. [_Role-Based Access Control (RBAC)_](https://en.wikipedia.org/wiki/Role-based_access_control) 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 `only_owner`. This check can be enforced through the `only_role` 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 `AccessControl` + +OpenZeppelin Contracts provides [`AccessControl`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/access/control/struct.AccessControl.html) 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 [ERC-20 token](/contracts-stylus/0.2.0/erc20) to define a 'minter' role, which allows accounts that have it create new tokens. Note that the example is unassuming of the way you construct your contract. + +```rust +use openzeppelin_stylus::{ + access::control::{self, AccessControl, IAccessControl}, + token::erc20::{self, Erc20, IErc20}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + UnauthorizedAccount(control::AccessControlUnauthorizedAccount), + BadConfirmation(control::AccessControlBadConfirmation), + InsufficientBalance(erc20::ERC20InsufficientBalance), + InvalidSender(erc20::ERC20InvalidSender), + InvalidReceiver(erc20::ERC20InvalidReceiver), + InsufficientAllowance(erc20::ERC20InsufficientAllowance), + InvalidSpender(erc20::ERC20InvalidSpender), + InvalidApprover(erc20::ERC20InvalidApprover), +} + +impl From for Error { + fn from(value: control::Error) -> Self { + match value { + control::Error::UnauthorizedAccount(e) => { + Error::UnauthorizedAccount(e) + } + control::Error::BadConfirmation(e) => Error::BadConfirmation(e), + } + } +} + +impl From for Error { + fn from(value: erc20::Error) -> Self { + match value { + erc20::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc20::Error::InvalidSender(e) => Error::InvalidSender(e), + erc20::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc20::Error::InsufficientAllowance(e) => { + Error::InsufficientAllowance(e) + } + erc20::Error::InvalidSpender(e) => Error::InvalidSpender(e), + erc20::Error::InvalidApprover(e) => Error::InvalidApprover(e), + } + } +} + +#[entrypoint] +#[storage] +struct Example { + erc20: Erc20, + access: AccessControl, +} + +const MINTER_ROLE: [u8; 32] = + keccak_const::Keccak256::new().update(b"MINTER_ROLE").finalize(); + +#[public] +#[implements(IErc20, IAccessControl)] +impl Example { + fn mint(&mut self, to: Address, amount: U256) -> Result<(), Error> { + self.access.only_role(MINTER_ROLE.into())?; + self.erc20._mint(to, amount)?; + Ok(()) + } +} + +#[public] +impl IErc20 for Example { + type Error = Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} + +#[public] +impl IAccessControl for Example { + type Error = Error; + + fn has_role(&self, role: B256, account: Address) -> bool { + self.access.has_role(role, account) + } + + fn only_role(&self, role: B256) -> Result<(), Self::Error> { + Ok(self.access.only_role(role)?) + } + + fn get_role_admin(&self, role: B256) -> B256 { + self.access.get_role_admin(role) + } + + fn grant_role( + &mut self, + role: B256, + account: Address, + ) -> Result<(), Self::Error> { + Ok(self.access.grant_role(role, account)?) + } + + fn revoke_role( + &mut self, + role: B256, + account: Address, + ) -> Result<(), Self::Error> { + Ok(self.access.revoke_role(role, account)?) + } + + fn renounce_role( + &mut self, + role: B256, + confirmation: Address, + ) -> Result<(), Self::Error> { + Ok(self.access.renounce_role(role, confirmation)?) + } +} +``` + + +Make sure you fully understand how [`AccessControl`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/access/control/struct.AccessControl.html) 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 `only_role` modifier: + +```rust +use openzeppelin_stylus::{ + access::control::{self, AccessControl, IAccessControl}, + token::erc20::{self, Erc20, IErc20}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + AccessControl(control::Error), + Erc20(erc20::Error), +} + +#[entrypoint] +#[storage] +struct Example { + erc20: Erc20, + access: AccessControl, +} + +const MINTER_ROLE: [u8; 32] = + keccak_const::Keccak256::new().update(b"MINTER_ROLE").finalize(); + +const BURNER_ROLE: [u8; 32] = + keccak_const::Keccak256::new().update(b"BURNER_ROLE").finalize(); + +#[public] +#[implements(IErc20, IAccessControl)] +impl Example { + fn mint(&mut self, to: Address, amount: U256) -> Result<(), Error> { + self.access.only_role(MINTER_ROLE.into())?; + self.erc20._mint(to, amount)?; + Ok(()) + } + + fn burn(&mut self, from: Address, amount: U256) -> Result<(), Error> { + self.access.only_role(BURNER_ROLE.into())?; + self.erc20._burn(from, amount)?; + Ok(()) + } +} + +#[public] +impl IErc20 for Example { + // ... +} + +#[public] +impl IAccessControl for Example { + // ... +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 Roles + +The ERC-20 token example above uses `_grant_role`, 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 `has_role` 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 `grant_role` and `revoke_role` 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 `_set_role_admin` is used to select a new admin role. + +Note that, by default, no accounts are granted the 'minter' or 'burner' roles. We assume you use a constructor to set the default admin role as the role of the deployer, or have a different mechanism where you make sure that you are able to grant 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 `grant_role` to give minting or burning permission, and `revoke_role` 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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. diff --git a/docs/content/contracts-stylus/0.2.0/common.mdx b/docs/content/contracts-stylus/0.2.0/common.mdx new file mode 100644 index 00000000..395233ba --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/common.mdx @@ -0,0 +1,8 @@ +--- +title: Common (Tokens) +--- + +Contracts that are common to multiple token standards. + +## ERC-2981 +* **[ERC-2981](https://eips.ethereum.org/EIPS/eip-2981)**: standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal support for royalty payments across all NFT marketplaces and ecosystem participants. diff --git a/docs/content/contracts-stylus/0.2.0/crypto.mdx b/docs/content/contracts-stylus/0.2.0/crypto.mdx new file mode 100644 index 00000000..0ced4876 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/crypto.mdx @@ -0,0 +1,29 @@ +--- +title: Crypto +--- + +The OpenZeppelin Rust Contracts provide a crate for common cryptographic procedures in a blockchain environment. The following documents the available functionality. + +## 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. + + +OpenZeppelin Contracts provides a [JavaScript library](https://github.com/OpenZeppelin/merkle-tree) for building trees off-chain and generating proofs. + + +[`MerkleProof`](https://docs.rs/openzeppelin-crypto/0.2.0/openzeppelin_crypto/merkle/struct.Verifier.html) provides: + +* [`verify`](https://docs.rs/openzeppelin-crypto/0.2.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify) - can prove that some value is part of a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). +* [`verify_multi_proof`](https://docs.rs/openzeppelin-crypto/0.2.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof) - can prove multiple values are part of a Merkle tree. + +```rust +fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { + let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); + Verifier::::verify(&proof, *root, *leaf) +} +``` + +Note that these functions use `keccak256` as the hashing algorithm, but our library also provides generic counterparts: [`verify_with_builder`](https://docs.rs/openzeppelin-crypto/0.2.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_with_builder) and [`verify_multi_proof_with_builder`](https://docs.rs/openzeppelin-crypto/0.2.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof_with_builder). + +We also provide an adapter [`hash`](https://docs.rs/openzeppelin-crypto/0.2.0/openzeppelin_crypto/hash/index.html) module to use your own hashers in conjunction with them that resembles Rust’s standard library’s API. diff --git a/docs/content/contracts-stylus/0.2.0/erc1155-burnable.mdx b/docs/content/contracts-stylus/0.2.0/erc1155-burnable.mdx new file mode 100644 index 00000000..83db497d --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc1155-burnable.mdx @@ -0,0 +1,134 @@ +--- +title: ERC-1155 Burnable +--- + +Extension of [ERC-1155](/contracts-stylus/0.2.0/erc1155) that allows token holders to destroy both their +own tokens and those that they have been approved to use. + +## Usage + +In order to make [`ERC-1155 Burnable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc1155/extensions/burnable/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc1155::{self, extensions::IErc1155Burnable, Erc1155, IErc1155}, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155Example { + erc1155: Erc1155, +} + +#[public] +#[implements(IErc1155, IErc1155Burnable, IErc165)] +impl Erc1155Example { + fn mint( + &mut self, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155._mint(to, token_id, amount, &data) + } + + fn mint_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155._mint_batch(to, token_ids, amounts, &data) + } +} + +#[public] +impl IErc1155Burnable for Erc1155Example { + type Error = erc1155::Error; + + fn burn( + &mut self, + account: Address, + token_id: U256, + value: U256, + ) -> Result<(), erc1155::Error> { + // ... + self.erc1155.burn(account, token_id, value)?; + // ... + Ok(()) + } + + fn burn_batch( + &mut self, + account: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), erc1155::Error> { + // ... + self.erc1155.burn_batch(account, token_ids, values)?; + // ... + Ok(()) + } +} + +#[public] +impl IErc1155 for Erc1155Example { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc165 for Erc1155Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc1155.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc1155-metadata-uri.mdx b/docs/content/contracts-stylus/0.2.0/erc1155-metadata-uri.mdx new file mode 100644 index 00000000..d882c557 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc1155-metadata-uri.mdx @@ -0,0 +1,107 @@ +--- +title: ERC-1155 Metadata URI +--- + +The OpenZeppelin [ERC-1155](/contracts-stylus/0.2.0/erc1155) Metadata URI extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which points to a JSON file that conforms to the [ERC-1155 Metadata URI JSON Schema](https://eips.ethereum.org/EIPS/eip-1155#erc-1155-metadata-uri-json-schema). +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-1155](/contracts-stylus/0.2.0/erc1155) token with [Metadata URI](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc1155/extensions/metadata_uri/index.html) flavour, +you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::{ + erc1155, + erc1155::{ + extensions::{Erc1155MetadataUri, IErc1155MetadataUri}, + Erc1155, IErc1155, + }, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155MetadataUriExample { + erc1155: Erc1155, + metadata_uri: Erc1155MetadataUri, +} + +#[public] +#[implements(IErc1155, IErc1155MetadataUri, IErc165)] +impl Erc1155MetadataUriExample { + #[constructor] + fn constructor(&mut self, uri: String) { + self.metadata_uri.constructor(uri); + } +} + +#[public] +impl IErc1155 for Erc1155MetadataUriExample { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc1155MetadataUri for Erc1155MetadataUriExample { + fn uri(&self, token_id: U256) -> String { + self.metadata_uri.uri(token_id) + } +} + +#[public] +impl IErc165 for Erc1155MetadataUriExample { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc1155.supports_interface(interface_id) + || self.metadata_uri.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc1155-pausable.mdx b/docs/content/contracts-stylus/0.2.0/erc1155-pausable.mdx new file mode 100644 index 00000000..2e962984 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc1155-pausable.mdx @@ -0,0 +1,176 @@ +--- +title: ERC-1155 Pausable +--- + +[ERC-1155](/contracts-stylus/0.2.0/erc1155) token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug. + +## Usage + +In order to make your [ERC-1155](/contracts-stylus/0.2.0/erc1155) token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/pausable/index.html) contract and apply its mechanisms to ERC1155 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc1155::{self, Erc1155, IErc1155}, + utils::{introspection::erc165::IErc165, pausable, IPausable, Pausable}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + InsufficientBalance(erc1155::ERC1155InsufficientBalance), + InvalidSender(erc1155::ERC1155InvalidSender), + InvalidReceiver(erc1155::ERC1155InvalidReceiver), + InvalidReceiverWithReason(erc1155::InvalidReceiverWithReason), + MissingApprovalForAll(erc1155::ERC1155MissingApprovalForAll), + InvalidApprover(erc1155::ERC1155InvalidApprover), + InvalidOperator(erc1155::ERC1155InvalidOperator), + InvalidArrayLength(erc1155::ERC1155InvalidArrayLength), + EnforcedPause(pausable::EnforcedPause), + ExpectedPause(pausable::ExpectedPause), +} + +impl From for Error { + fn from(value: erc1155::Error) -> Self { + match value { + erc1155::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc1155::Error::InvalidSender(e) => Error::InvalidSender(e), + erc1155::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc1155::Error::InvalidReceiverWithReason(e) => { + Error::InvalidReceiverWithReason(e) + } + erc1155::Error::MissingApprovalForAll(e) => { + Error::MissingApprovalForAll(e) + } + erc1155::Error::InvalidApprover(e) => Error::InvalidApprover(e), + erc1155::Error::InvalidOperator(e) => Error::InvalidOperator(e), + erc1155::Error::InvalidArrayLength(e) => { + Error::InvalidArrayLength(e) + } + } + } +} + +impl From for Error { + fn from(value: pausable::Error) -> Self { + match value { + pausable::Error::EnforcedPause(e) => Error::EnforcedPause(e), + pausable::Error::ExpectedPause(e) => Error::ExpectedPause(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc1155Example { + erc1155: Erc1155, + pausable: Pausable, +} + +#[public] +#[implements(IErc1155, IPausable)] +impl Erc1155Example { + fn mint( + &mut self, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155._mint(to, token_id, amount, &data)?; + Ok(()) + } + + fn mint_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155._mint_batch(to, token_ids, amounts, &data)?; + Ok(()) + } +} + +#[public] +impl IPausable for Erc1155Example { + fn paused(&self) -> bool { + self.pausable.paused() + } +} + +#[public] +impl IErc1155 for Erc1155Example { + type Error = Error; + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.safe_transfer_from(from, to, id, value, data)?; + Ok(()) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data)?; + Ok(()) + } + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + Ok(self.erc1155.balance_of_batch(accounts, ids)?) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(self.erc1155.set_approval_for_all(operator, approved)?) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } +} + +#[public] +impl IErc165 for Erc1155Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc1155.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc1155-supply.mdx b/docs/content/contracts-stylus/0.2.0/erc1155-supply.mdx new file mode 100644 index 00000000..34acadcb --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc1155-supply.mdx @@ -0,0 +1,130 @@ +--- +title: ERC-1155 Supply +--- + +The OpenZeppelin [ERC-1155](/contracts-stylus/0.2.0/erc1155) Supply extension that adds tracking of total supply per token id. +Useful for scenarios where Fungible and Non-fungible tokens have to be clearly identified. + +## Usage + +In order to make an [ERC-1155](/contracts-stylus/0.2.0/erc1155) token with [Supply](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc1155/extensions/supply/index.html) flavour, +you need to reexport all the supply-related functions. +Make sure to apply the `#[selector(name = "totalSupply")]` attribute to the `total_supply_all` function! +You need to create the specified contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc1155::{ + self, + extensions::{Erc1155Supply, IErc1155Supply}, + IErc1155, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155Example { + erc1155_supply: Erc1155Supply, +} + +#[public] +#[implements(IErc1155, IErc1155Supply, IErc165)] +impl Erc1155Example { + // Add token minting feature. + fn mint( + &mut self, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155_supply._mint(to, id, value, &data) + } + + fn mint_batch( + &mut self, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155_supply._mint_batch(to, ids, values, &data) + } +} + +#[public] +impl IErc1155Supply for Erc1155Example { + fn total_supply(&self, id: U256) -> U256 { + self.erc1155_supply.total_supply(id) + } + + #[selector(name = "totalSupply")] + fn total_supply_all(&self) -> U256 { + self.erc1155_supply.total_supply_all() + } + + fn exists(&self, id: U256) -> bool { + self.erc1155_supply.exists(id) + } +} + +#[public] +impl IErc1155 for Erc1155Example { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155_supply.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155_supply.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155_supply.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155_supply.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155_supply.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155_supply + .safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc165 for Erc1155Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc1155_supply.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc1155-uri-storage.mdx b/docs/content/contracts-stylus/0.2.0/erc1155-uri-storage.mdx new file mode 100644 index 00000000..11aa7bd6 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc1155-uri-storage.mdx @@ -0,0 +1,121 @@ +--- +title: ERC-1155 URI Storage +--- + +The OpenZeppelin [ERC-1155](/contracts-stylus/0.2.0/erc1155) URI Storage extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which can point to metadata about the token, such as images, descriptions, and other attributes. +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-1155](/contracts-stylus/0.2.0/erc1155) token with [URI Storage](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc1155/extensions/uri_storage/index.html) flavour, +your token should also use [`ERC-1155 Metadata URI`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc1155/extensions/metadata_uri/index.html) extension to provide additional URI metadata for each token. +You need to create a specified contract as follows: + +```rust +use openzeppelin_stylus::{ + token::{ + erc1155, + erc1155::{ + extensions::{ + Erc1155MetadataUri, Erc1155UriStorage, IErc1155MetadataUri, + }, + Erc1155, IErc1155, + }, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155MetadataUriExample { + erc1155: Erc1155, + metadata_uri: Erc1155MetadataUri, + uri_storage: Erc1155UriStorage, +} + +#[public] +#[implements(IErc1155, IErc1155MetadataUri, IErc165)] +impl Erc1155MetadataUriExample { + #[constructor] + fn constructor(&mut self, uri: String) { + self.metadata_uri.constructor(uri); + } + + #[selector(name = "setTokenURI")] + fn set_token_uri(&mut self, token_id: U256, token_uri: String) { + self.uri_storage.set_token_uri(token_id, token_uri, &self.metadata_uri) + } + + #[selector(name = "setBaseURI")] + fn set_base_uri(&mut self, base_uri: String) { + self.uri_storage.set_base_uri(base_uri) + } +} + +#[public] +impl IErc1155 for Erc1155MetadataUriExample { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc1155MetadataUri for Erc1155MetadataUriExample { + fn uri(&self, token_id: U256) -> String { + self.uri_storage.uri(token_id, &self.metadata_uri) + } +} + +#[public] +impl IErc165 for Erc1155MetadataUriExample { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc1155.supports_interface(interface_id) + || self.metadata_uri.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc1155.mdx b/docs/content/contracts-stylus/0.2.0/erc1155.mdx new file mode 100644 index 00000000..873b78bb --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc1155.mdx @@ -0,0 +1,15 @@ +--- +title: ERC-1155 +--- + +ERC1155 is a novel token standard that aims to take the best from previous standards to create a [**fungibility-agnostic**](/contracts-stylus/0.2.0/tokens#different-kinds-of-tokens) and **gas-efficient** [token contract](/contracts-stylus/0.2.0/tokens#but-first-coffee-a-primer-on-token-contracts). + +## Extensions + +Additionally, there are multiple custom extensions, including: + +* [ERC-1155 Burnable](/contracts-stylus/0.2.0/erc1155-burnable): Optional Burnable extension of the ERC-1155 standard. +* [ERC-1155 Metadata URI](/contracts-stylus/0.2.0/erc1155-metadata-uri): Optional extension that adds a token metadata URI. +* [ERC-1155 Pausable](/contracts-stylus/0.2.0/erc1155-pausable): A primitive to pause contract operation like token transfers, minting and burning. +* [ERC-1155 URI Storage](/contracts-stylus/0.2.0/erc1155-uri-storage): A more flexible but more expensive way of storing URI metadata. +* [ERC-1155 Supply](/contracts-stylus/0.2.0/erc1155-supply): Extension of the ERC-1155 standard that adds tracking of total supply per token id. diff --git a/docs/content/contracts-stylus/0.2.0/erc20-burnable.mdx b/docs/content/contracts-stylus/0.2.0/erc20-burnable.mdx new file mode 100644 index 00000000..ad7b565a --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-burnable.mdx @@ -0,0 +1,88 @@ +--- +title: ERC-20 Burnable +--- + +Extension of [ERC-20](/contracts-stylus/0.2.0/erc20) that allows token holders to destroy both their own tokens and those that they have an allowance for, in a way that can be recognized off-chain (via event analysis). + +## Usage + +In order to make [`ERC-20 Burnable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/burnable/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::token::erc20::{ + self, extensions::IErc20Burnable, Erc20, IErc20, +}; + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, +} + +#[public] +#[implements(IErc20, IErc20Burnable)] +impl Erc20Example {} + + +#[public] +impl IErc20 for Erc20Example { + type Error = erc20::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer(to, value) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + self.erc20.approve(spender, value) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer_from(from, to, value) + } +} + + +#[public] +impl IErc20Burnable for Erc20Example { + type Error = erc20::Error; + + fn burn(&mut self, value: U256) -> Result<(), erc20::Error> { + // ... + self.erc20.burn(value) + } + + fn burn_from( + &mut self, + account: Address, + value: U256, + ) -> Result<(), erc20::Error> { + // ... + self.erc20.burn_from(account, value) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20-capped.mdx b/docs/content/contracts-stylus/0.2.0/erc20-capped.mdx new file mode 100644 index 00000000..e03e38eb --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-capped.mdx @@ -0,0 +1,152 @@ +--- +title: ERC-20 Capped +--- + +Extension of [ERC-20](/contracts-stylus/0.2.0/erc20) that adds a cap to the supply of tokens. + +## Usage + +In order to make [`ERC-20 Capped`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/capped/index.html) methods supervising the supply of tokens, you need to add them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::token::erc20::{ + self, + extensions::{capped, Capped, ICapped}, + Erc20, IErc20, +}; + +#[derive(SolidityError, Debug)] +enum Error { + ExceededCap(capped::ERC20ExceededCap), + InvalidCap(capped::ERC20InvalidCap), + InsufficientBalance(erc20::ERC20InsufficientBalance), + InvalidSender(erc20::ERC20InvalidSender), + InvalidReceiver(erc20::ERC20InvalidReceiver), + InsufficientAllowance(erc20::ERC20InsufficientAllowance), + InvalidSpender(erc20::ERC20InvalidSpender), + InvalidApprover(erc20::ERC20InvalidApprover), +} + +impl From for Error { + fn from(value: capped::Error) -> Self { + match value { + capped::Error::ExceededCap(e) => Error::ExceededCap(e), + capped::Error::InvalidCap(e) => Error::InvalidCap(e), + } + } +} + +impl From for Error { + fn from(value: erc20::Error) -> Self { + match value { + erc20::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc20::Error::InvalidSender(e) => Error::InvalidSender(e), + erc20::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc20::Error::InsufficientAllowance(e) => { + Error::InsufficientAllowance(e) + } + erc20::Error::InvalidSpender(e) => Error::InvalidSpender(e), + erc20::Error::InvalidApprover(e) => Error::InvalidApprover(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, + capped: Capped, +} + +#[public] +#[implements(IErc20, ICapped)] +impl Erc20Example { + #[constructor] + fn constructor(&mut self, cap: U256) -> Result<(), Error> { + Ok(self.capped.constructor(cap)?) + } + + // Add token minting feature. + // + // Make sure to handle `Capped` properly. You should not call + // [`Erc20::_update`] to mint tokens -- it will break the `Capped` + // mechanism. + fn mint( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Error> { + let max_supply = self.capped.cap(); + + // Overflow check required. + let supply = self + .erc20 + .total_supply() + .checked_add(value) + .expect("new supply should not exceed `U256::MAX`"); + + if supply > max_supply { + return Err(capped::Error::ExceededCap( + capped::ERC20ExceededCap { + increased_supply: supply, + cap: max_supply, + }, + ))?; + } + + self.erc20._mint(account, value)?; + Ok(()) + } +} + +#[public] +impl ICapped for Erc20Example { + fn cap(&self) -> U256 { + self.capped.cap() + } +} + +#[public] +impl IErc20 for Erc20Example { + type Error = Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20-flash-mint.mdx b/docs/content/contracts-stylus/0.2.0/erc20-flash-mint.mdx new file mode 100644 index 00000000..aa39684e --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-flash-mint.mdx @@ -0,0 +1,102 @@ +--- +title: ERC-20 Flash Mint +--- + +Extension of [ERC-20](/contracts-stylus/0.2.0/erc20) that provides flash loan support at the token level. + +## Usage + +In order to make [`ERC-20 Flash Mint`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/flash_mint/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::token::erc20::{ + extensions::{flash_mint, Erc20FlashMint, IErc3156FlashLender}, + Erc20, IErc20, +}; + +#[entrypoint] +#[storage] +struct Erc20FlashMintExample { + erc20: Erc20, + flash_mint: Erc20FlashMint, +} + +#[public] +#[implements(IErc20, IErc3156FlashLender)] +impl Erc20FlashMintExample {} + +#[public] +impl IErc3156FlashLender for Erc20FlashMintExample { + type Error = flash_mint::Error; + + fn max_flash_loan(&self, token: Address) -> U256 { + self.flash_mint.max_flash_loan(token, &self.erc20) + } + + fn flash_fee( + &self, + token: Address, + value: U256, + ) -> Result { + self.flash_mint.flash_fee(token, value) + } + + fn flash_loan( + &mut self, + receiver: Address, + token: Address, + value: U256, + data: Bytes, + ) -> Result { + self.flash_mint.flash_loan( + receiver, + token, + value, + &data, + &mut self.erc20, + ) + } +} + +#[public] +impl IErc20 for Erc20FlashMintExample { + type Error = flash_mint::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20-metadata.mdx b/docs/content/contracts-stylus/0.2.0/erc20-metadata.mdx new file mode 100644 index 00000000..2b682fd1 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-metadata.mdx @@ -0,0 +1,102 @@ +--- +title: ERC-20 Metadata +--- + +Extension of [ERC-20](/contracts-stylus/0.2.0/erc20) that adds the optional metadata functions from the ERC20 standard. + +## Usage + +In order to make [`ERC-20 Metadata`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/metadata/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + self, + extensions::{Erc20Metadata, IErc20Metadata}, + Erc20, IErc20, + }, +}; + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, + metadata: Erc20Metadata, +} + +#[public] +#[implements(IErc20, IErc20Metadata, IErc165)] +impl Erc20Example { + #[constructor] + fn constructor(&mut self, name: String, symbol: String) { + self.metadata.constructor(name, symbol); + } + + // ... +} + +#[public] +impl IErc20 for Erc20Example { + type Error = erc20::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer(to, value) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + self.erc20.approve(spender, value) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer_from(from, to, value) + } +} + +#[public] +impl IErc20Metadata for Erc20Example { + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + fn decimals(&self) -> U8 { + self.metadata.decimals() + } +} + +#[public] +impl IErc165 for Erc20Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc20.supports_interface(interface_id) + || self.metadata.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20-pausable.mdx b/docs/content/contracts-stylus/0.2.0/erc20-pausable.mdx new file mode 100644 index 00000000..4616e462 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-pausable.mdx @@ -0,0 +1,130 @@ +--- +title: ERC-20 Pausable +--- + +ERC20 token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug. + +## Usage + +In order to make your ERC20 token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/pausable/index.html) contract and apply its mechanisms to ERC20 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{self, Erc20, IErc20}, + utils::{pausable, IPausable, Pausable}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + InsufficientBalance(erc20::ERC20InsufficientBalance), + InvalidSender(erc20::ERC20InvalidSender), + InvalidReceiver(erc20::ERC20InvalidReceiver), + InsufficientAllowance(erc20::ERC20InsufficientAllowance), + InvalidSpender(erc20::ERC20InvalidSpender), + InvalidApprover(erc20::ERC20InvalidApprover), + EnforcedPause(pausable::EnforcedPause), + ExpectedPause(pausable::ExpectedPause), +} + +impl From for Error { + fn from(value: erc20::Error) -> Self { + match value { + erc20::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc20::Error::InvalidSender(e) => Error::InvalidSender(e), + erc20::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc20::Error::InsufficientAllowance(e) => { + Error::InsufficientAllowance(e) + } + erc20::Error::InvalidSpender(e) => Error::InvalidSpender(e), + erc20::Error::InvalidApprover(e) => Error::InvalidApprover(e), + } + } +} + +impl From for Error { + fn from(value: pausable::Error) -> Self { + match value { + pausable::Error::EnforcedPause(e) => Error::EnforcedPause(e), + pausable::Error::ExpectedPause(e) => Error::ExpectedPause(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, + pausable: Pausable, +} + +#[public] +#[implements(IErc20, IPausable)] +impl Erc20Example { + fn mint(&mut self, account: Address, value: U256) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20._mint(account, value)?; + Ok(()) + } +} + +#[public] +impl IErc20 for Erc20Example { + type Error = Error; + + fn transfer(&mut self, to: Address, value: U256) -> Result { + // ... + self.pausable.when_not_paused()?; + // ... + let result = self.erc20.transfer(to, value)?; + // ... + Ok(result) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + // ... + self.pausable.when_not_paused()?; + // ... + let result = self.erc20.transfer_from(from, to, value)?; + // ... + Ok(result) + } + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } +} + +#[public] +impl IPausable for Erc20Example { + fn paused(&self) -> bool { + self.pausable.paused() + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20-permit.mdx b/docs/content/contracts-stylus/0.2.0/erc20-permit.mdx new file mode 100644 index 00000000..54441b83 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-permit.mdx @@ -0,0 +1,133 @@ +--- +title: ERC-20 Permit +--- + +Adds the permit method, which can be used to change an account’s ERC20 allowance (see [`IErc20::allowance`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.allowance)) by presenting a message signed by the account. By not relying on [`IErc20::approve`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.approve), the token holder account doesn’t need to send a transaction, and thus is not required to hold Ether at all. + +## Usage + +In order to have [`ERC-20 Permit`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/permit/index.html) token, you need to use only this contract without [ERC-20](/contracts-stylus/0.2.0/erc20) as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::{permit, Erc20Permit, IErc20Permit}, + Erc20, IErc20, + }, + utils::{ + cryptography::eip712::IEip712, + nonces::{INonces, Nonces}, + }, +}; + +#[entrypoint] +#[storage] +struct Erc20PermitExample { + erc20: Erc20, + nonces: Nonces, + erc20_permit: Erc20Permit, +} + +#[storage] +struct Eip712; + +impl IEip712 for Eip712 { + const NAME: &'static str = "ERC-20 Permit Example"; + const VERSION: &'static str = "1"; +} + +#[public] +#[implements(IErc20, INonces, IErc20Permit)] +impl Erc20PermitExample { + // Add token minting feature. + fn mint( + &mut self, + account: Address, + value: U256, + ) -> Result<(), permit::Error> { + Ok(self.erc20._mint(account, value)?) + } +} + +#[public] +impl INonces for Erc20PermitExample { + fn nonces(&self, owner: Address) -> U256 { + self.nonces.nonces(owner) + } +} + +#[public] +impl IErc20Permit for Erc20PermitExample { + type Error = permit::Error; + + #[selector(name = "DOMAIN_SEPARATOR")] + fn domain_separator(&self) -> B256 { + self.erc20_permit.domain_separator() + } + + fn permit( + &mut self, + owner: Address, + spender: Address, + value: U256, + deadline: U256, + v: u8, + r: B256, + s: B256, + ) -> Result<(), Self::Error> { + self.erc20_permit.permit( + owner, + spender, + value, + deadline, + v, + r, + s, + &mut self.erc20, + &mut self.nonces, + ) + } +} + +#[public] +impl IErc20 for Erc20PermitExample { + type Error = permit::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20-wrapper.mdx b/docs/content/contracts-stylus/0.2.0/erc20-wrapper.mdx new file mode 100644 index 00000000..ac2f350b --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20-wrapper.mdx @@ -0,0 +1,113 @@ +--- +title: ERC-20 Wrapper +--- + +Extension of the ERC-20 token contract to support token wrapping. + +Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". +This is useful in conjunction with other modules. + +## Usage + +In order to make [`ERC20Wrapper`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/wrapper/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::token::erc20::{ + extensions::{wrapper, Erc20Wrapper, IErc20Wrapper}, + Erc20, IErc20, +}; +use stylus_sdk::{ + alloy_primitives::{Address, U256, U8}, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct Erc20WrapperExample { + erc20: Erc20, + erc20_wrapper: Erc20Wrapper, +} + +#[public] +#[implements(IErc20, IErc20Wrapper)] +impl Erc20WrapperExample { + #[constructor] + fn constructor( + &mut self, + underlying_token: Address, + ) -> Result<(), wrapper::Error> { + self.erc20_wrapper.constructor(underlying_token)? + } +} + +#[public] +impl IErc20Wrapper for Erc20WrapperExample { + type Error = wrapper::Error; + + fn underlying(&self) -> Address { + self.erc20_wrapper.underlying() + } + + fn decimals(&self) -> U8 { + self.erc20_wrapper.decimals() + } + + fn deposit_for( + &mut self, + account: Address, + value: U256, + ) -> Result { + self.erc20_wrapper.deposit_for(account, value, &mut self.erc20) + } + + fn withdraw_to( + &mut self, + account: Address, + value: U256, + ) -> Result { + self.erc20_wrapper.withdraw_to(account, value, &mut self.erc20) + } +} + +#[public] +impl IErc20 for Erc20WrapperExample { + type Error = wrapper::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc20.mdx b/docs/content/contracts-stylus/0.2.0/erc20.mdx new file mode 100644 index 00000000..8d8597e8 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc20.mdx @@ -0,0 +1,159 @@ +--- +title: ERC-20 +--- + +An ERC-20 token contract keeps track of [_fungible_ tokens](/contracts-stylus/0.2.0/tokens#different-kinds-of-tokens): any token is exactly equal to any other token; no token has a special right 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 provide many ERC20-related contracts for Arbitrum Stylus. +On the [`API reference`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/struct.Erc20.html) you’ll find detailed information on their properties and usage. + +## 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. + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + self, + extensions::{Erc20Metadata, IErc20Metadata}, + Erc20, IErc20, + }, +}; + +#[entrypoint] +#[storage] +struct GLDToken { + erc20: Erc20, + metadata: Erc20Metadata, +} + +#[public] +#[implements(IErc20, IErc20Metadata, IErc165)] +impl GLDToken { + #[constructor] + fn constructor(&mut self, name: String, symbol: String) { + self.metadata.constructor(name, symbol); + } + + // ... +} + +#[public] +impl IErc20 for GLDToken { + type Error = erc20::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer(to, value) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + self.erc20.approve(spender, value) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer_from(from, to, value) + } +} + +#[public] +impl IErc20Metadata for GLDToken { + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + fn decimals(&self) -> U8 { + self.metadata.decimals() + } +} + +#[public] +impl IErc165 for GLDToken { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc20.supports_interface(interface_id) + || self.metadata.supports_interface(interface_id) + } +} +``` + +Our contracts are often used via stylus-sdk [inheritance](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#inheritance-inherit-and-borrow), and here we’re reusing [`ERC20`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/struct.Erc20.html) for both the basic standard implementation and with optional extensions. + +## 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, ERC20 provides a [`decimals`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/metadata/trait.IErc20Metadata.html#tymethod.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 an exceptional reason not to. +When minting tokens or transferring them around, you will be actually sending the number `GLD * (10 ** decimals)`. + + +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. For example, to use `16` decimals, you would do: + +```rust +fn decimals(&self) -> U8 { + U8::from(16) +} +``` + +So if you want to send `5` tokens using a token contract with `18` decimals, the method to call will actually be: + +```rust +token.transfer(recipient, 5 * uint!(10_U256).pow(uint!(18_U256))); +``` + +## Extensions +Additionally, there are multiple custom extensions, including: + +* [ERC-20 Burnable](/contracts-stylus/0.2.0/erc20-burnable): destruction of own tokens. +* [ERC-20 Capped](/contracts-stylus/0.2.0/erc20-capped): enforcement of a cap to the total supply when minting tokens. +* [ERC-20 Metadata](/contracts-stylus/0.2.0/erc20-metadata): the extended ERC20 interface including the name, symbol, and decimals functions. +* [ERC-20 Pausable](/contracts-stylus/0.2.0/erc20-pausable): ability to pause token transfers. +* [ERC-20 Permit](/contracts-stylus/0.2.0/erc20-permit): gasless approval of tokens (standardized as [`EIP-2612`](https://eips.ethereum.org/EIPS/eip-2612)). +* [ERC-4626](/contracts-stylus/0.2.0/erc4626): tokenized vault that manages shares (represented as ERC-20) that are backed by assets (another ERC-20). +* [ERC-20 Flash-Mint](/contracts-stylus/0.2.0/erc20-flash-mint): token level support for flash loans through the minting and burning of ephemeral tokens (standardized as [`EIP-3156`](https://eips.ethereum.org/EIPS/eip-3156)). +* [ERC-20 Wrapper](/contracts-stylus/0.2.0/erc20-wrapper): wrapper to create an ERC-20 backed by another ERC-20, with deposit and withdraw methods. diff --git a/docs/content/contracts-stylus/0.2.0/erc2981.mdx b/docs/content/contracts-stylus/0.2.0/erc2981.mdx new file mode 100644 index 00000000..3b968696 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc2981.mdx @@ -0,0 +1,19 @@ +--- +title: ERC-2981 +--- + +ERC-2981 offers a way to keep track of NFT royalties across different platforms. + +Royalty is the amount of money given to the owner of a particular asset (NFT in this case). Full definition: https://en.wikipedia.org/wiki/Royalty_payment [royalty]. + +This makes ERC-2981 useful for keeping track of royalty information across different platforms. It ensures that users don’t have to specify this information on every platform, and the information remains transparent on the blockchain. + +## Limitations and Considerations + +* ***Marketplace Enforcement***: Some NFT marketplaces may not enforce royalties. +* ***Manual Distribution***: On-chain royalty tracking does not mean automatic payouts. +* ***Gas Costs***: Extra storage and computations can slightly increase gas fees. + +## Additional Resources + +* ERC-2981 EIP: [EIP-2981 Specification](https://eips.ethereum.org/EIPS/eip-2981) diff --git a/docs/content/contracts-stylus/0.2.0/erc4626.mdx b/docs/content/contracts-stylus/0.2.0/erc4626.mdx new file mode 100644 index 00000000..082b8d3c --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc4626.mdx @@ -0,0 +1,203 @@ +--- +title: ERC-4626 +--- + +Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626). + +This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for underlying "assets" through standardized `deposit`, `mint`, `redeem` and `burn` workflows. This contract extends the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this contract and not the "assets" token which is an independent contract. + +## Security concern: Inflation attack +To read more about the security concerns associated with the ERC-4626, check the [Inflation attack](https://docs.openzeppelin.com/contracts/5.x/erc4626#inflation-attack) description. + +## Usage + +In order to make [`ERC-4626`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc20/extensions/erc4626/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::{ + erc4626, Erc20Metadata, Erc4626, IErc20Metadata, IErc4626, + }, + Erc20, IErc20, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc4626Example { + erc4626: Erc4626, + erc20: Erc20, + metadata: Erc20Metadata, +} + +#[public] +#[implements(IErc4626, IErc20, IErc20Metadata, IErc165)] +impl Erc4626Example { + #[constructor] + fn constructor( + &mut self, + asset: Address, + decimals_offset: U8, + name: String, + symbol: String, + ) { + self.erc4626.constructor(asset, decimals_offset); + self.metadata.constructor(name, symbol); + } +} + +#[public] +impl IErc4626 for Erc4626Example { + type Error = erc4626::Error; + + fn asset(&self) -> Address { + self.erc4626.asset() + } + + fn total_assets(&self) -> Result { + self.erc4626.total_assets() + } + + fn convert_to_shares(&self, assets: U256) -> Result { + self.erc4626.convert_to_shares(assets, &self.erc20) + } + + fn convert_to_assets(&self, shares: U256) -> Result { + self.erc4626.convert_to_assets(shares, &self.erc20) + } + + fn max_deposit(&self, receiver: Address) -> U256 { + self.erc4626.max_deposit(receiver) + } + + fn preview_deposit(&self, assets: U256) -> Result { + self.erc4626.preview_deposit(assets, &self.erc20) + } + + fn deposit( + &mut self, + assets: U256, + receiver: Address, + ) -> Result { + self.erc4626.deposit(assets, receiver, &mut self.erc20) + } + + fn max_mint(&self, receiver: Address) -> U256 { + self.erc4626.max_mint(receiver) + } + + fn preview_mint(&self, shares: U256) -> Result { + self.erc4626.preview_mint(shares, &self.erc20) + } + + fn mint( + &mut self, + shares: U256, + receiver: Address, + ) -> Result { + self.erc4626.mint(shares, receiver, &mut self.erc20) + } + + fn max_withdraw(&self, owner: Address) -> Result { + self.erc4626.max_withdraw(owner, &self.erc20) + } + + fn preview_withdraw(&self, assets: U256) -> Result { + self.erc4626.preview_withdraw(assets, &self.erc20) + } + + fn withdraw( + &mut self, + assets: U256, + receiver: Address, + owner: Address, + ) -> Result { + self.erc4626.withdraw(assets, receiver, owner, &mut self.erc20) + } + + fn max_redeem(&self, owner: Address) -> U256 { + self.erc4626.max_redeem(owner, &self.erc20) + } + + fn preview_redeem(&self, shares: U256) -> Result { + self.erc4626.preview_redeem(shares, &self.erc20) + } + + fn redeem( + &mut self, + shares: U256, + receiver: Address, + owner: Address, + ) -> Result { + self.erc4626.redeem(shares, receiver, owner, &mut self.erc20) + } +} + +#[public] +impl IErc20 for Erc4626Example { + type Error = erc4626::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} + +#[public] +impl IErc20Metadata for Erc4626Example { + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + fn decimals(&self) -> U8 { + self.erc4626.decimals() + } +} + +#[public] +impl IErc165 for Erc4626Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + ::interface_id() == interface_id + || self.erc20.supports_interface(interface_id) + || self.metadata.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-burnable.mdx b/docs/content/contracts-stylus/0.2.0/erc721-burnable.mdx new file mode 100644 index 00000000..dd3ca2fc --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-burnable.mdx @@ -0,0 +1,113 @@ +--- +title: ERC-721 Burnable +--- + +[ERC-721](/contracts-stylus/0.2.0/erc721) Token that can be burned (destroyed). + +## Usage + +In order to make [`ERC-721 Burnable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/burnable/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{self, extensions::IErc721Burnable, Erc721, IErc721}, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, +} + +#[public] +#[implements(IErc721, IErc721Burnable, IErc165)] +impl Erc721Example { + fn burn(&mut self, token_id: U256) -> Result<(), erc721::Error> { + // ... + self.erc721.burn(token_id) + } +} + +#[public] +impl IErc721 for Erc721Example { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Burnable for Erc721Example { + type Error = erc721::Error; + + fn burn(&mut self, token_id: U256) -> Result<(), erc721::Error> { + // ... + self.erc721.burn(token_id) + } +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-consecutive.mdx b/docs/content/contracts-stylus/0.2.0/erc721-consecutive.mdx new file mode 100644 index 00000000..f33eef81 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-consecutive.mdx @@ -0,0 +1,117 @@ +--- +title: ERC-721 Consecutive +--- + +Consecutive extension for [ERC-721](/contracts-stylus/0.2.0/erc721) is useful for efficiently minting multiple tokens in a single transaction. This can significantly reduce gas costs and improve performance when creating a large number of tokens at once. + +## Usage + +In order to make [`ERC-721 Consecutive`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/consecutive/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + extensions::{consecutive, Erc721Consecutive}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721ConsecutiveExample { + erc721_consecutive: Erc721Consecutive, +} + +#[public] +#[inherit(IErc721, IErc165)] +impl Erc721ConsecutiveExample { + #[constructor] + fn constructor( + &mut self, + receivers: Vec
, + amounts: Vec, + first_consecutive_id: U96, + max_batch_size: U96, + ) -> Result<(), consecutive::Error> { + self.erc721_consecutive.first_consecutive_id.set(first_consecutive_id); + self.erc721_consecutive.max_batch_size.set(max_batch_size); + for (&receiver, &amount) in receivers.iter().zip(amounts.iter()) { + self.erc721_consecutive._mint_consecutive(receiver, amount)?; + } + Ok(()) + } +} + +#[public] +impl IErc721 for Erc721ConsecutiveExample { + type Error = consecutive::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc165 for Erc721ConsecutiveExample { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-enumerable.mdx b/docs/content/contracts-stylus/0.2.0/erc721-enumerable.mdx new file mode 100644 index 00000000..e314b76a --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-enumerable.mdx @@ -0,0 +1,264 @@ +--- +title: ERC-721 Enumerable +--- + +The OpenZeppelin [ERC-721](/contracts-stylus/0.2.0/erc721) Enumerable extension is used to provide additional functionality to the standard ERC-721 token. Specifically, it allows for enumeration of all the token IDs in the contract as well as all the token IDs owned by each account. This is useful for applications that need to list or iterate over tokens, such as marketplaces or wallets. + +## Usage + +In order to make an [ERC-721](/contracts-stylus/0.2.0/erc721) token with [Enumerable](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/enumerable/index.html) flavour, +you need to create a specified contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{enumerable, Erc721Enumerable, IErc721Burnable}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[derive(SolidityError, Debug)] +enum Error { + OutOfBoundsIndex(enumerable::ERC721OutOfBoundsIndex), + EnumerableForbiddenBatchMint( + enumerable::ERC721EnumerableForbiddenBatchMint, + ), + InvalidOwner(erc721::ERC721InvalidOwner), + NonexistentToken(erc721::ERC721NonexistentToken), + IncorrectOwner(erc721::ERC721IncorrectOwner), + InvalidSender(erc721::ERC721InvalidSender), + InvalidReceiver(erc721::ERC721InvalidReceiver), + InvalidReceiverWithReason(erc721::InvalidReceiverWithReason), + InsufficientApproval(erc721::ERC721InsufficientApproval), + InvalidApprover(erc721::ERC721InvalidApprover), + InvalidOperator(erc721::ERC721InvalidOperator), +} + +impl From for Error { + fn from(value: enumerable::Error) -> Self { + match value { + enumerable::Error::OutOfBoundsIndex(e) => { + Error::OutOfBoundsIndex(e) + } + enumerable::Error::EnumerableForbiddenBatchMint(e) => { + Error::EnumerableForbiddenBatchMint(e) + } + } + } +} + +impl From for Error { + fn from(value: erc721::Error) -> Self { + match value { + erc721::Error::InvalidOwner(e) => Error::InvalidOwner(e), + erc721::Error::NonexistentToken(e) => Error::NonexistentToken(e), + erc721::Error::IncorrectOwner(e) => Error::IncorrectOwner(e), + erc721::Error::InvalidSender(e) => Error::InvalidSender(e), + erc721::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc721::Error::InvalidReceiverWithReason(e) => { + Error::InvalidReceiverWithReason(e) + } + erc721::Error::InsufficientApproval(e) => { + Error::InsufficientApproval(e) + } + erc721::Error::InvalidApprover(e) => Error::InvalidApprover(e), + erc721::Error::InvalidOperator(e) => Error::InvalidOperator(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, + enumerable: Erc721Enumerable, +} + +#[public] +#[implements(IErc721, IErc721Enumerable, IErc165)] +impl Erc721Example { + fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { + self.erc721._mint(to, token_id)?; + + // Update the extension's state. + self.enumerable._add_token_to_all_tokens_enumeration(token_id); + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } +} + +#[public] +impl IErc721 for Erc721Example { + type Error = Error; + + fn balance_of(&self, owner: Address) -> Result { + Ok(self.erc721.balance_of(owner)?) + } + + fn owner_of(&self, token_id: U256) -> Result { + Ok(self.erc721.owner_of(token_id)?) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.safe_transfer_from(from, to, token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + #[selector(name = "safeTransferFrom")] + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.safe_transfer_from_with_data(from, to, token_id, data)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.transfer_from(from, to, token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + Ok(self.erc721.approve(to, token_id)?) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(self.erc721.set_approval_for_all(to, approved)?) + } + + fn get_approved(&self, token_id: U256) -> Result { + Ok(self.erc721.get_approved(token_id)?) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Burnable for Erc721Example { + type Error = Error; + + fn burn(&mut self, token_id: U256) -> Result<(), Self::Error> { + // Retrieve the owner. + let owner = self.erc721.owner_of(token_id)?; + + self.erc721.burn(token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + owner, + token_id, + &self.erc721, + )?; + self.enumerable._remove_token_from_all_tokens_enumeration(token_id); + + Ok(()) + } +} + +#[public] +impl IErc721Enumerable for Erc721Example { + type Error = Error; + + fn total_supply(&self) -> U256 { + self.enumerable.total_supply() + } + + fn token_by_index(&self, index: U256) -> Result { + Ok(self.enumerable.token_by_index(index)?) + } + + fn token_of_owner_by_index( + &self, + owner: Address, + index: U256, + ) -> Result { + Ok(self.enumerable.token_of_owner_by_index(owner, index)?) + } +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + || self.enumerable.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-metadata.mdx b/docs/content/contracts-stylus/0.2.0/erc721-metadata.mdx new file mode 100644 index 00000000..7a6f468e --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-metadata.mdx @@ -0,0 +1,136 @@ +--- +title: ERC-721 Metadata +--- + +Extension of [ERC-721](/contracts-stylus/0.2.0/erc721) that adds the optional metadata functions from the ERC721 standard. + +## Usage + +In order to make [`ERC-721 Metadata`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{Erc721Metadata, IErc721Metadata }, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721MetadataExample { + erc721: Erc721, + metadata: Erc721Metadata, +} + +#[public] +#[implements(IErc721, IErc721Metadata, IErc165)] +impl Erc721MetadataExample { + #[constructor] + fn constructor(&mut self, name: String, symbol: String, base_uri: String) { + self.metadata.constructor(name, symbol); + self.metadata.base_uri.set_str(base_uri); + } + + fn mint( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), erc721::Error> { + self.erc721._mint(to, token_id) + } +} + +#[public] +impl IErc721 for Erc721MetadataExample { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Metadata for Erc721MetadataExample { + type Error = erc721::Error; + + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + #[selector(name = "tokenURI")] + fn token_uri(&self, token_id: U256) -> Result { + self.metadata.token_uri(token_id, &self.erc721) + } +} + +#[public] +impl IErc165 for Erc721MetadataExample { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + || ::interface_id() == interface_id + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-pausable.mdx b/docs/content/contracts-stylus/0.2.0/erc721-pausable.mdx new file mode 100644 index 00000000..df2b1341 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-pausable.mdx @@ -0,0 +1,173 @@ +--- +title: ERC-721 Pausable +--- + +ERC721 token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers, e.g. caused by a bug. + +## Usage + +In order to make your ERC721 token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/pausable/index.html) contract and apply its mechanisms to ERC721 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{self, Erc721, IErc721}, + utils::{introspection::erc165::IErc165, pausable, IPausable, Pausable}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + InvalidOwner(erc721::ERC721InvalidOwner), + NonexistentToken(erc721::ERC721NonexistentToken), + IncorrectOwner(erc721::ERC721IncorrectOwner), + InvalidSender(erc721::ERC721InvalidSender), + InvalidReceiver(erc721::ERC721InvalidReceiver), + InvalidReceiverWithReason(erc721::InvalidReceiverWithReason), + InsufficientApproval(erc721::ERC721InsufficientApproval), + InvalidApprover(erc721::ERC721InvalidApprover), + InvalidOperator(erc721::ERC721InvalidOperator), + EnforcedPause(pausable::EnforcedPause), + ExpectedPause(pausable::ExpectedPause), +} + +impl From for Error { + fn from(value: erc721::Error) -> Self { + match value { + erc721::Error::InvalidOwner(e) => Error::InvalidOwner(e), + erc721::Error::NonexistentToken(e) => Error::NonexistentToken(e), + erc721::Error::IncorrectOwner(e) => Error::IncorrectOwner(e), + erc721::Error::InvalidSender(e) => Error::InvalidSender(e), + erc721::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc721::Error::InvalidReceiverWithReason(e) => { + Error::InvalidReceiverWithReason(e) + } + erc721::Error::InsufficientApproval(e) => { + Error::InsufficientApproval(e) + } + erc721::Error::InvalidApprover(e) => Error::InvalidApprover(e), + erc721::Error::InvalidOperator(e) => Error::InvalidOperator(e), + } + } +} + +impl From for Error { + fn from(value: pausable::Error) -> Self { + match value { + pausable::Error::EnforcedPause(e) => Error::EnforcedPause(e), + pausable::Error::ExpectedPause(e) => Error::ExpectedPause(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, + pausable: Pausable, +} + +#[public] +#[implements(IErc721, IPausable, IErc165)] +impl Erc721Example { + fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721._mint(to, token_id)?; + Ok(()) + } +} + +#[public] +impl IErc721 for Erc721Example { + type Error = Error; + + fn balance_of(&self, owner: Address) -> Result { + Ok(self.erc721.balance_of(owner)?) + } + + fn owner_of(&self, token_id: U256) -> Result { + Ok(self.erc721.owner_of(token_id)?) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.safe_transfer_from(from, to, token_id)?; + Ok(()) + } + + #[selector(name = "safeTransferFrom")] + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.safe_transfer_from_with_data(from, to, token_id, data)?; + Ok(()) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.transfer_from(from, to, token_id)?; + Ok(()) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + Ok(self.erc721.approve(to, token_id)?) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(self.erc721.set_approval_for_all(to, approved)?) + } + + fn get_approved(&self, token_id: U256) -> Result { + Ok(self.erc721.get_approved(token_id)?) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IPausable for Erc721Example { + fn paused(&self) -> bool { + self.pausable.paused() + } +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-uri-storage.mdx b/docs/content/contracts-stylus/0.2.0/erc721-uri-storage.mdx new file mode 100644 index 00000000..476168dc --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-uri-storage.mdx @@ -0,0 +1,154 @@ +--- +title: ERC-721 Uri Storage +--- + +The OpenZeppelin [ERC-721](/contracts-stylus/0.2.0/erc721) URI Storage extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which can point to metadata about the token, such as images, descriptions, and other attributes. +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-721](/contracts-stylus/0.2.0/erc721) token with [URI Storage](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/uri_storage/index.html) flavour, +your token should also use [`ERC-721 Metadata`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html) extension to provide additional metadata for each token. +You need to create a specified contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{ + Erc721Metadata, Erc721UriStorage, IErc721Metadata, + IErc721UriStorage, + }, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721MetadataExample { + erc721: Erc721, + metadata: Erc721Metadata, + uri_storage: Erc721UriStorage, +} + +#[public] +#[implements(IErc721, IErc721Metadata, IErc165)] +impl Erc721MetadataExample { + #[constructor] + fn constructor(&mut self, name: String, symbol: String, base_uri: String) { + self.metadata.constructor(name, symbol); + self.metadata.base_uri.set_str(base_uri); + } + + fn mint( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), erc721::Error> { + self.erc721._mint(to, token_id) + } + + #[selector(name = "setTokenURI")] + fn set_token_uri(&mut self, token_id: U256, token_uri: String) { + self.uri_storage._set_token_uri(token_id, token_uri) + } +} + +#[public] +impl IErc721 for Erc721MetadataExample { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Metadata for Erc721MetadataExample { + type Error = erc721::Error; + + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + #[selector(name = "tokenURI")] + fn token_uri(&self, token_id: U256) -> Result { + self.uri_storage.token_uri(token_id, &self.erc721, &self.metadata) + } +} + +// We implement this trait only to respect Rust's trait requirements. +// There's nothing to expose, as all the necessary functions were exposed by +// implementing `IErc721Metadata`. +impl IErc721UriStorage for Erc721MetadataExample {} + +#[public] +impl IErc165 for Erc721MetadataExample { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + || ::interface_id() == interface_id + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721-wrapper.mdx b/docs/content/contracts-stylus/0.2.0/erc721-wrapper.mdx new file mode 100644 index 00000000..fdf6e611 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721-wrapper.mdx @@ -0,0 +1,152 @@ +--- +title: ERC-721 Wrapper +--- + +Extension of the ERC-721 token contract to support token wrapping. + +Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". +This is useful in conjunction with other modules. + +## Usage + +In order to make [`ERC721Wrapper`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/wrapper/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{wrapper, Erc721Wrapper, IErc721Wrapper}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721WrapperExample { + erc721: Erc721, + erc721_wrapper: Erc721Wrapper, +} + +#[public] +#[implements(IErc721, IErc721Wrapper, IErc165)] +impl Erc721WrapperExample { + #[constructor] + fn constructor(&mut self, underlying_token: Address) { + self.erc721_wrapper.constructor(underlying_token); + } +} + +#[public] +impl IErc721 for Erc721WrapperExample { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Wrapper for Erc721WrapperExample { + type Error = wrapper::Error; + + fn underlying(&self) -> Address { + self.erc721_wrapper.underlying() + } + + fn deposit_for( + &mut self, + account: Address, + token_ids: Vec, + ) -> Result { + self.erc721_wrapper.deposit_for(account, token_ids, &mut self.erc721) + } + + fn withdraw_to( + &mut self, + account: Address, + token_ids: Vec, + ) -> Result { + self.erc721_wrapper.withdraw_to(account, token_ids, &mut self.erc721) + } + + fn on_erc721_received( + &mut self, + operator: Address, + from: Address, + token_id: U256, + data: Bytes, + ) -> Result, Self::Error> { + self.erc721_wrapper.on_erc721_received( + operator, + from, + token_id, + &data, + &mut self.erc721, + ) + } +} + +#[public] +impl IErc165 for Erc721WrapperExample { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/0.2.0/erc721.mdx b/docs/content/contracts-stylus/0.2.0/erc721.mdx new file mode 100644 index 00000000..e611e884 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/erc721.mdx @@ -0,0 +1,175 @@ +--- +title: ERC-721 +--- + +We’ve discussed how you can make a _fungible_ token using [ERC-20](/contracts-stylus/0.2.0/erc20), 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 [_non-fungible_ tokens](/contracts-stylus/0.2.0/tokens#different-kinds-of-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 [`API reference`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/struct.Erc721.html) 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 any account can call `awardItem` to mint items. +To restrict what accounts can be minted per item. +We can use an [Access Control](/contracts-stylus/0.2.0/access-control) extension. + +Here’s what a contract for tokenized items might look like: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{Erc721Metadata, IErc721Metadata}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct GameItem { + erc721: Erc721, + metadata: Erc721Metadata, + next_token_id: StorageU256, +} + +#[public] +#[implements(IErc721, IErc721Metadata, IErc165)] +impl GameItem { + #[constructor] + fn constructor(&mut self, name: String, symbol: String, base_uri: String) { + self.metadata.constructor(name, symbol); + self.metadata.base_uri.set_str(base_uri); + } + + fn award_item(&mut self, player: Address) -> Result { + let token_id = self.next_token_id.get() + uint!(1_U256); + self.next_token_id.set(token_id); + + self.erc721._mint(player, token_id)?; + + Ok(token_id) + } +} + +#[public] +impl IErc721 for GameItem { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Metadata for GameItem { + type Error = erc721::Error; + + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + #[selector(name = "tokenURI")] + fn token_uri(&self, token_id: U256) -> Result { + self.metadata.token_uri(token_id, &self.erc721) + } +} + +#[public] +impl IErc165 for GameItem { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + || ::interface_id() == interface_id + } +} +``` + +The [`Erc721Metadata`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/token/erc721/extensions/metadata/struct.Erc721Metadata.html) contract is an extension contract of ERC-721. +It extends the contract itself with the name, symbol and base uri for the token. + +Also note that, unlike ERC-20, ERC-721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +For more information about erc721 schema, check out the [ERC-721 specification](https://eips.ethereum.org/EIPS/eip-721). + + +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! + + +## Extensions + +Additionally, there are multiple custom extensions, including: + +* [ERC-721 Burnable](/contracts-stylus/0.2.0/erc721-burnable): A way for token holders to burn their own tokens. +* [ERC-721 Consecutive](/contracts-stylus/0.2.0/erc721-consecutive): An implementation of [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) for minting batches of tokens during construction, in accordance with ERC721. +* [ERC-721 Enumerable](/contracts-stylus/0.2.0/erc721-enumerable): Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* [ERC-721 Metadata](/contracts-stylus/0.2.0/erc721-metadata): Optional extension that adds name, symbol, and token URI, almost always included. +* [ERC-721 Pausable](/contracts-stylus/0.2.0/erc721-pausable): A primitive to pause contract operation. +* [ERC-721 Uri Storage](/contracts-stylus/0.2.0/erc721-uri-storage): A more flexible but more expensive way of storing metadata. +* [ERC-721 Wrapper](/contracts-stylus/0.2.0/erc721-wrapper): Wrapper to create an ERC-721 backed by another ERC-721, with deposit and withdraw methods. diff --git a/docs/content/contracts-stylus/0.2.0/finance.mdx b/docs/content/contracts-stylus/0.2.0/finance.mdx new file mode 100644 index 00000000..7a3378dd --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/finance.mdx @@ -0,0 +1,11 @@ +--- +title: Finance +--- + +Contracts that include primitives for financial systems. + +## VestingWallet +* [VestingWallet](/contracts-stylus/0.2.0/vesting-wallet) handles the vesting of Ether and ERC-20 + tokens for a given beneficiary. Custody of multiple tokens can be given to this + contract, which will release the token to the beneficiary following a given, + customizable, vesting schedule. diff --git a/docs/content/contracts-stylus/0.2.0/index.mdx b/docs/content/contracts-stylus/0.2.0/index.mdx new file mode 100644 index 00000000..a3064d4e --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/index.mdx @@ -0,0 +1,9 @@ +--- +title: Contracts for Stylus +--- + +**A library for secure smart contract development** written in Rust for [Stylus](https://docs.arbitrum.io/stylus/gentle-introduction). + + +You can track our roadmap in our [Github Project](https://github.com/orgs/OpenZeppelin/projects/35). + diff --git a/docs/content/contracts-stylus/0.2.0/tokens.mdx b/docs/content/contracts-stylus/0.2.0/tokens.mdx new file mode 100644 index 00000000..f5d91760 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/tokens.mdx @@ -0,0 +1,31 @@ +--- +title: 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 + +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 + +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 [fungibility](https://en.wikipedia.org/wiki/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, ERC-721 or ERC-1155 token standards, and that’s why you’re here. Head to our specialized guides to learn more about these: + +* [ERC-20](/contracts-stylus/0.2.0/erc20): the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. +* [ERC-721](/contracts-stylus/0.2.0/erc721): the de-facto solution for non-fungible tokens, often used for collectibles and games. +* [ERC-1155](/contracts-stylus/0.2.0/erc1155): 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. diff --git a/docs/content/contracts-stylus/0.2.0/utilities.mdx b/docs/content/contracts-stylus/0.2.0/utilities.mdx new file mode 100644 index 00000000..fe88067c --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/utilities.mdx @@ -0,0 +1,49 @@ +--- +title: Utilities +--- + +The OpenZeppelin Stylus Contracts provides a ton of useful utilities that you can use in your project. +For a complete list, check out the [API Reference](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/index.html). +Here are some of the more popular ones. + +## Introspection + +It’s frequently helpful to know whether a contract supports an interface you’d like to use. +[`ERC-165`](https://eips.ethereum.org/EIPS/eip-165) is a standard that helps do runtime interface detection. +Contracts for Stylus provides helpers for implementing ERC-165 in your contracts: + +* [`IERC165`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html) — this is the ERC-165 trait that defines [`supportsInterface`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface). In order to implement ERC-165 interface detection, you should manually expose [`supportsInterface`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface) function in your contract. + +```rust +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, +} + +#[public] +#[implements(IErc721, IErc165)] +impl Erc721Example { + // ... +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: FixedBytes<4>) -> bool { + self.erc721.supports_interface(interface_id) + } +} + +#[public] +impl IErc721 for Erc721Example { + // ... +} +``` + +## Structures + +Some use cases require more powerful data structures than arrays and mappings offered natively in alloy and the Stylus sdk. +Contracts for Stylus provides these libraries for enhanced data structure management: + +* [`BitMaps`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/structs/bitmap/index.html): Store packed booleans in storage. +* [`Checkpoints`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/utils/structs/checkpoints/index.html): Checkpoint values with built-in lookups. diff --git a/docs/content/contracts-stylus/0.2.0/vesting-wallet.mdx b/docs/content/contracts-stylus/0.2.0/vesting-wallet.mdx new file mode 100644 index 00000000..489928b3 --- /dev/null +++ b/docs/content/contracts-stylus/0.2.0/vesting-wallet.mdx @@ -0,0 +1,170 @@ +--- +title: VestingWallet +--- + +A vesting wallet is an ownable contract that can receive native currency and +ERC-20 tokens, and release these assets to the wallet owner, also referred to as +"beneficiary", according to a vesting schedule. + +Any assets transferred to this contract will follow the vesting schedule as if +they were locked from the beginning. Consequently, if the vesting has already +started, any amount of tokens sent to this contract will (at least partly) be +immediately releasable. + +By setting the duration to 0, one can configure this contract to behave like +an asset timelock that hold tokens for a beneficiary until a specified time. + + + + +Since the wallet is [Ownable](/contracts-stylus/0.2.0/access-control#ownership-and-ownable), +and ownership can be transferred, it is possible to sell unvested tokens. +Preventing this in a smart contract is difficult, considering that: 1) a +beneficiary address could be a counterfactually deployed contract, 2) +there is likely to be a migration path for EOAs to become contracts in the near +future. + + + + + + +When using this contract with any token whose balance is adjusted automatically +(i.e. a rebase token), make sure to account the supply/balance adjustment in the +vesting schedule to ensure the vested amount is as intended. + + + + + + +Chains with support for native ERC20s may allow the vesting wallet to withdraw +the underlying asset as both an ERC20 and as native currency. For example, if +chain C supports token A and the wallet gets deposited 100 A, then at 50% of +the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as +native currency (totaling 75 A). Consider disabling one of the withdrawal methods. + + + +## Usage + +In order to make [`VestingWallet`](https://docs.rs/openzeppelin-stylus/0.2.0/openzeppelin_stylus/finance/vesting_wallet/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::finance::vesting_wallet::{ + self, IVestingWallet, VestingWallet, +}; + +#[entrypoint] +#[storage] +struct VestingWalletExample { + vesting_wallet: VestingWallet, +} + +#[public] +#[implements(IVestingWallet)] +impl VestingWalletExample { + #[constructor] + fn constructor( + &mut self, + beneficiary: Address, + start_timestamp: U64, + duration_seconds: U64, + ) -> Result<(), vesting_wallet::Error> { + self.vesting_wallet.constructor( + beneficiary, + start_timestamp, + duration_seconds, + ) + } + + #[receive] + fn receive(&mut self) -> Result<(), Vec> { + self.vesting_wallet.receive() + } +} + +#[public] +impl IVestingWallet for VestingWalletExample { + type Error = vesting_wallet::Error; + + fn owner(&self) -> Address { + self.vesting_wallet.owner() + } + + fn transfer_ownership( + &mut self, + new_owner: Address, + ) -> Result<(), Self::Error> { + self.vesting_wallet.transfer_ownership(new_owner) + } + + fn renounce_ownership( + &mut self, + ) -> Result<(), Self::Error> { + self.vesting_wallet.renounce_ownership() + } + + fn start(&self) -> U256 { + self.vesting_wallet.start() + } + + fn duration(&self) -> U256 { + self.vesting_wallet.duration() + } + + fn end(&self) -> U256 { + self.vesting_wallet.end() + } + + #[selector(name = "released")] + fn released_eth(&self) -> U256 { + self.vesting_wallet.released_eth() + } + + #[selector(name = "released")] + fn released_erc20(&self, token: Address) -> U256 { + self.vesting_wallet.released_erc20(token) + } + + #[selector(name = "releasable")] + fn releasable_eth(&self) -> U256 { + self.vesting_wallet.releasable_eth() + } + + #[selector(name = "releasable")] + fn releasable_erc20( + &mut self, + token: Address, + ) -> Result { + self.vesting_wallet.releasable_erc20(token) + } + + #[selector(name = "release")] + fn release_eth(&mut self) -> Result<(), Self::Error> { + self.vesting_wallet.release_eth() + } + + #[selector(name = "release")] + fn release_erc20( + &mut self, + token: Address, + ) -> Result<(), Self::Error> { + self.vesting_wallet.release_erc20(token) + } + + #[selector(name = "vestedAmount")] + fn vested_amount_eth(&self, timestamp: u64) -> U256 { + self.vesting_wallet.vested_amount_eth(timestamp) + } + + #[selector(name = "vestedAmount")] + fn vested_amount_erc20( + &mut self, + token: Address, + timestamp: u64, + ) -> Result { + self.vesting_wallet.vested_amount_erc20(token, timestamp) + } +} +``` diff --git a/docs/content/contracts-stylus/access-control.mdx b/docs/content/contracts-stylus/access-control.mdx new file mode 100644 index 00000000..f2d9adca --- /dev/null +++ b/docs/content/contracts-stylus/access-control.mdx @@ -0,0 +1,331 @@ +--- +title: 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 [steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7). + +## 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 for Stylus provides [`Ownable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/access/ownable/struct.Ownable.html) for implementing ownership in your contracts. + +```rust +use openzeppelin_stylus::access::ownable::{self, Ownable}; + +#[derive(SolidityError, Debug)] +enum Error { + UnauthorizedAccount(ownable::OwnableUnauthorizedAccount), + InvalidOwner(ownable::OwnableInvalidOwner), + // other custom errors... +} + +impl From for Error { + fn from(value: ownable::Error) -> Self { + match value { + ownable::Error::UnauthorizedAccount(e) => { + Error::UnauthorizedAccount(e) + } + ownable::Error::InvalidOwner(e) => Error::InvalidOwner(e), + } + } +} + +#[entrypoint] +#[storage] +struct OwnableExample { + ownable: Ownable, +} + +#[public] +#[implements(IOwnable)] +impl MyContract { + fn normal_thing(&self) { + // anyone can call this normal_thing() + } + + fn special_thing(&mut self) -> Result<(), Error> { + self.ownable.only_owner()?; + + // only the owner can call special_thing()! + + Ok(()) + } +} + +#[public] +impl IOwnable for MyContract { + type Error = Error; + + fn owner(&self) -> Address { + self.ownable.owner() + } + + fn transfer_ownership( + &mut self, + new_owner: Address, + ) -> Result<(), Self::Error> { + Ok(self.ownable.transfer_ownership(new_owner)?) + } + + fn renounce_ownership(&mut self) -> Result<(), Self::Error> { + Ok(self.ownable.renounce_ownership()?) + } +} +``` + +At deployment, the [`owner`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.owner) of an `Ownable` contract is set to the provided `initial_owner` parameter. + +Ownable also lets you: + +* [`transfer_ownership`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.transfer_ownership) from the owner account to a new one, and +* [`renounce_ownership`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/access/ownable/struct.Ownable.html#method.renounce_ownership) for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `only_owner` will no longer be callable! + + +Note that **a contract can also be the owner of another one**! This opens the door to using, for example, a [Gnosis Safe](https://gnosis-safe.io), an [Aragon DAO](https://aragon.org), 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 [MakerDAO](https://makerdao.com), use systems similar to this one. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. [_Role-Based Access Control (RBAC)_](https://en.wikipedia.org/wiki/Role-based_access_control) 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 `only_owner`. This check can be enforced through the `only_role` 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 `AccessControl` + +OpenZeppelin Contracts provides [`AccessControl`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/access/control/struct.AccessControl.html) 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 [ERC-20 token](/contracts-stylus/erc20) to define a 'minter' role, which allows accounts that have it create new tokens. Note that the example is unassuming of the way you construct your contract. + +```rust +use openzeppelin_stylus::{ + access::control::{self, AccessControl, IAccessControl}, + token::erc20::{self, Erc20, IErc20}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + UnauthorizedAccount(control::AccessControlUnauthorizedAccount), + BadConfirmation(control::AccessControlBadConfirmation), + InsufficientBalance(erc20::ERC20InsufficientBalance), + InvalidSender(erc20::ERC20InvalidSender), + InvalidReceiver(erc20::ERC20InvalidReceiver), + InsufficientAllowance(erc20::ERC20InsufficientAllowance), + InvalidSpender(erc20::ERC20InvalidSpender), + InvalidApprover(erc20::ERC20InvalidApprover), +} + +impl From for Error { + fn from(value: control::Error) -> Self { + match value { + control::Error::UnauthorizedAccount(e) => { + Error::UnauthorizedAccount(e) + } + control::Error::BadConfirmation(e) => Error::BadConfirmation(e), + } + } +} + +impl From for Error { + fn from(value: erc20::Error) -> Self { + match value { + erc20::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc20::Error::InvalidSender(e) => Error::InvalidSender(e), + erc20::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc20::Error::InsufficientAllowance(e) => { + Error::InsufficientAllowance(e) + } + erc20::Error::InvalidSpender(e) => Error::InvalidSpender(e), + erc20::Error::InvalidApprover(e) => Error::InvalidApprover(e), + } + } +} + +#[entrypoint] +#[storage] +struct Example { + erc20: Erc20, + access: AccessControl, +} + +const MINTER_ROLE: [u8; 32] = + keccak_const::Keccak256::new().update(b"MINTER_ROLE").finalize(); + +#[public] +#[implements(IErc20, IAccessControl)] +impl Example { + fn mint(&mut self, to: Address, amount: U256) -> Result<(), Error> { + self.access.only_role(MINTER_ROLE.into())?; + self.erc20._mint(to, amount)?; + Ok(()) + } +} + +#[public] +impl IErc20 for Example { + type Error = Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} + +#[public] +impl IAccessControl for Example { + type Error = Error; + + fn has_role(&self, role: B256, account: Address) -> bool { + self.access.has_role(role, account) + } + + fn only_role(&self, role: B256) -> Result<(), Self::Error> { + Ok(self.access.only_role(role)?) + } + + fn get_role_admin(&self, role: B256) -> B256 { + self.access.get_role_admin(role) + } + + fn grant_role( + &mut self, + role: B256, + account: Address, + ) -> Result<(), Self::Error> { + Ok(self.access.grant_role(role, account)?) + } + + fn revoke_role( + &mut self, + role: B256, + account: Address, + ) -> Result<(), Self::Error> { + Ok(self.access.revoke_role(role, account)?) + } + + fn renounce_role( + &mut self, + role: B256, + confirmation: Address, + ) -> Result<(), Self::Error> { + Ok(self.access.renounce_role(role, confirmation)?) + } +} +``` + + +Make sure you fully understand how [`AccessControl`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/access/control/struct.AccessControl.html) 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 `only_role` modifier: + +```rust +use openzeppelin_stylus::{ + access::control::{self, AccessControl, IAccessControl}, + token::erc20::{self, Erc20, IErc20}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + AccessControl(control::Error), + Erc20(erc20::Error), +} + +#[entrypoint] +#[storage] +struct Example { + erc20: Erc20, + access: AccessControl, +} + +const MINTER_ROLE: [u8; 32] = + keccak_const::Keccak256::new().update(b"MINTER_ROLE").finalize(); + +const BURNER_ROLE: [u8; 32] = + keccak_const::Keccak256::new().update(b"BURNER_ROLE").finalize(); + +#[public] +#[implements(IErc20, IAccessControl)] +impl Example { + fn mint(&mut self, to: Address, amount: U256) -> Result<(), Error> { + self.access.only_role(MINTER_ROLE.into())?; + self.erc20._mint(to, amount)?; + Ok(()) + } + + fn burn(&mut self, from: Address, amount: U256) -> Result<(), Error> { + self.access.only_role(BURNER_ROLE.into())?; + self.erc20._burn(from, amount)?; + Ok(()) + } +} + +#[public] +impl IErc20 for Example { + // ... +} + +#[public] +impl IAccessControl for Example { + // ... +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 Roles + +The ERC-20 token example above uses `_grant_role`, 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 `has_role` 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 `grant_role` and `revoke_role` 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 `_set_role_admin` is used to select a new admin role. + +Note that, by default, no accounts are granted the 'minter' or 'burner' roles. We assume you use a constructor to set the default admin role as the role of the deployer, or have a different mechanism where you make sure that you are able to grant 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 `grant_role` to give minting or burning permission, and `revoke_role` 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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. diff --git a/docs/content/contracts-stylus/beacon-proxy.mdx b/docs/content/contracts-stylus/beacon-proxy.mdx new file mode 100644 index 00000000..4b092824 --- /dev/null +++ b/docs/content/contracts-stylus/beacon-proxy.mdx @@ -0,0 +1,332 @@ +--- +title: Beacon Proxy +--- + +Beacon Proxy is an advanced proxy pattern that allows multiple proxy contracts to share a single beacon contract that determines their implementation. This pattern is particularly useful for scenarios where you want to upgrade multiple proxy contracts simultaneously by updating a single beacon. + +The OpenZeppelin Stylus Contracts provides a complete implementation of the Beacon Proxy pattern, including the `BeaconProxy` contract and `UpgradeableBeacon` contract. + +## Understanding Beacon Proxy + +The Beacon Proxy pattern consists of three main components: + +1. ***Beacon Contract***: A contract that stores the current implementation address. +2. ***Beacon Proxy***: Multiple proxy contracts that delegate to the beacon for their implementation. +3. ***Implementation Contract***: The actual logic contract that gets executed. + +### How It Works + +1. Multiple `BeaconProxy` contracts are deployed, each pointing to the same `UpgradeableBeacon`. +2. The `UpgradeableBeacon` stores the current implementation address. +3. When a call is made to any `BeaconProxy`, it queries the beacon for the current implementation. +4. The proxy then delegates the call to that implementation. +5. To upgrade all proxies, you only need to update the beacon’s implementation. + +### Benefits + +* ***Mass Upgrades***: Upgrade multiple proxies with a single transaction. +* ***Gas Efficiency***: Shared beacon reduces storage costs. +* ***Consistency***: All proxies always use the same implementation. +* ***Centralized Control***: Single point of control for upgrades. +* ***Trust Minimization***: Proxies can verify the beacon’s implementation. + +## Basic Beacon Proxy Implementation + +Here’s how to implement a basic beacon proxy: + +```rust +use openzeppelin_stylus::proxy::{ + beacon::{proxy::BeaconProxy, IBeacon}, + erc1967, + IProxy, +}; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::Address, + prelude::*, + ArbResult, +}; + +#[entrypoint] +#[storage] +struct MyBeaconProxy { + beacon_proxy: BeaconProxy, +} + +#[public] +impl MyBeaconProxy { + #[constructor] + fn constructor( + &mut self, + beacon: Address, + data: Bytes, + ) -> Result<(), erc1967::utils::Error> { + self.beacon_proxy.constructor(beacon, &data) + } + + /// Get the beacon address + fn get_beacon(&self) -> Address { + self.beacon_proxy.get_beacon() + } + + /// Get the current implementation address from the beacon + fn implementation(&self) -> Result> { + self.beacon_proxy.implementation() + } + + /// Fallback function that delegates all calls to the implementation + #[fallback] + fn fallback(&mut self, calldata: &[u8]) -> ArbResult { + unsafe { self.do_fallback(calldata) } + } +} + +unsafe impl IProxy for MyBeaconProxy { + fn implementation(&self) -> Result> { + self.beacon_proxy.implementation() + } +} +``` + +## Upgradeable Beacon Implementation + +The `UpgradeableBeacon` contract manages the implementation address and provides upgrade functionality: + +```rust +use openzeppelin_stylus::{ + access::ownable::{IOwnable, Ownable}, + proxy::beacon::{IBeacon, IUpgradeableBeacon, UpgradeableBeacon}, +}; +use stylus_sdk::{ + alloy_primitives::Address, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct MyUpgradeableBeacon { + beacon: UpgradeableBeacon, +} + +#[public] +impl MyUpgradeableBeacon { + #[constructor] + fn constructor( + &mut self, + implementation: Address, + initial_owner: Address, + ) -> Result<(), beacon::Error> { + self.beacon.constructor(implementation, initial_owner) + } + + /// Upgrade to a new implementation (only owner) + fn upgrade_to( + &mut self, + new_implementation: Address, + ) -> Result<(), beacon::Error> { + self.beacon.upgrade_to(new_implementation) + } +} + +#[public] +impl IBeacon for MyUpgradeableBeacon { + fn implementation(&self) -> Result> { + self.beacon.implementation() + } +} + +#[public] +impl IOwnable for MyUpgradeableBeacon { + fn owner(&self) -> Address { + self.beacon.owner() + } + + fn transfer_ownership(&mut self, new_owner: Address) -> Result<(), Vec> { + self.beacon.transfer_ownership(new_owner) + } + + fn renounce_ownership(&mut self) -> Result<(), Vec> { + self.beacon.renounce_ownership() + } +} + +#[public] +impl IUpgradeableBeacon for MyUpgradeableBeacon { + fn upgrade_to(&mut self, new_implementation: Address) -> Result<(), Vec> { + Ok(self.beacon.upgrade_to(new_implementation)?) + } +} +``` + +## Custom Beacon Implementation + +You can also implement your own beacon contract by implementing the `IBeacon` trait: + +```rust +use openzeppelin_stylus::proxy::beacon::IBeacon; +use stylus_sdk::{ + alloy_primitives::Address, + prelude::*, + storage::StorageAddress, +}; + +#[entrypoint] +#[storage] +struct MyCustomBeacon { + implementation: StorageAddress, + admin: StorageAddress, +} + +#[public] +impl MyCustomBeacon { + #[constructor] + fn constructor(&mut self, implementation: Address, admin: Address) { + self.implementation.set(implementation); + self.admin.set(admin); + } + + /// Upgrade implementation (only admin) + fn upgrade_implementation(&mut self, new_implementation: Address) -> Result<(), Vec> { + if self.admin.get() != msg::sender() { + return Err("Only admin can upgrade".abi_encode()); + } + + if !new_implementation.has_code() { + return Err("Invalid implementation".abi_encode()); + } + + self.implementation.set(new_implementation); + Ok(()) + } +} + +#[public] +impl IBeacon for MyCustomBeacon { + fn implementation(&self) -> Result> { + Ok(self.implementation.get()) + } +} +``` + +## Constructor Data + +Like ERC-1967 proxies, beacon proxies support initialization data: + +```rust +impl MyBeaconProxy { + #[constructor] + fn constructor( + &mut self, + beacon: Address, + data: Bytes, + ) -> Result<(), erc1967::utils::Error> { + // If data is provided, it will be passed to the implementation + // returned by the beacon during construction via delegatecall + self.beacon_proxy.constructor(beacon, &data) + } +} +``` + +The `data` parameter can be used to: + +* ***Initialize storage***: Pass encoded function calls to set up initial state. +* ***Mint initial tokens***: Call mint functions on token contracts. +* ***Set up permissions***: Configure initial access control settings. +* ***Empty data***: Pass empty bytes if no initialization is needed. + +### Example: Initializing with Data + +```rust +use alloy_sol_macro::sol; +use alloy_sol_types::SolCall; + +sol! { + interface IERC20 { + function mint(address to, uint256 amount) external; + } +} + +// In your deployment script or test +let beacon = deploy_beacon(); +let implementation = deploy_implementation(); +let initial_owner = alice; +let initial_supply = U256::from(1000000); + +// Encode the mint call +let mint_data = IERC20::mintCall { + to: initial_owner, + amount: initial_supply, +}.abi_encode(); + +// Deploy beacon proxy with initialization data +let proxy = MyBeaconProxy::deploy( + beacon.address(), + mint_data.into(), +).expect("Failed to deploy beacon proxy"); +``` + +## Storage Layout Safety + +Beacon proxies use ERC-1967 storage slots for safety: + +### Benefits + +* ***No Storage Collisions***: Implementation storage cannot conflict with proxy storage. +* ***Predictable Layout***: Storage slots are standardized and well-documented. +* ***Upgrade Safety***: New implementations can safely use any storage layout. +* ***Gas Efficiency***: No need for complex storage gap patterns. + +### Implementation Storage + +Your implementation contract can use any storage layout without worrying about conflicts: + +```rust +#[entrypoint] +#[storage] +struct MyToken { + // These fields are safe to use - they won't conflict with beacon proxy storage + balances: StorageMapping, + allowances: StorageMapping<(Address, Address), U256>, + total_supply: StorageU256, + name: StorageString, + symbol: StorageString, + decimals: StorageU8, + // ... any other storage fields +} +``` + +## Best Practices + +1. ***Trust the beacon***: Ensure you control or trust the beacon contract, as it determines all proxy implementations. +2. ***Use proper access control***: Implement admin controls for beacon upgrade functions. +3. ***Test mass upgrades***: Ensure all proxies work correctly after beacon upgrades. +4. ***Monitor beacon events***: Track beacon upgrades for transparency. +5. ***Handle initialization data carefully***: Only send value when providing initialization data. +6. ***Document beacon ownership***: Clearly document who controls the beacon. +7. ***Use standardized slots***: Don’t override the ERC-1967 storage slots in your implementation. +8. ***Consider beacon immutability***: Beacon proxies cannot change their beacon address after deployment. + +## Common Pitfalls + +* ***Untrusted beacon***: Using a beacon you don’t control can lead to malicious upgrades. +* ***Beacon immutability***: Beacon proxies cannot change their beacon address after deployment. +* ***Missing access control***: Protect beacon upgrade functions with proper access control. +* ***Storage layout changes***: Be careful when changing storage layout in new implementations. +* ***Incorrect initialization data***: Ensure initialization data is properly encoded. +* ***Sending value without data***: Beacon proxies prevent sending value without initialization data. + +## Use Cases + +Beacon proxies are particularly useful for: + +* ***Token Contracts***: Multiple token instances sharing the same implementation. +* ***NFT Collections***: Multiple NFT contracts with identical logic. +* ***DeFi Protocols***: Multiple vault or pool contracts. +* ***DAO Governance***: Multiple governance contracts. +* ***Cross-chain Bridges***: Multiple bridge contracts on different chains. + +## Related Patterns + +* [Basic proxy](/contracts-stylus/proxy): Basic proxy pattern using `delegate_call` for upgradeable contracts. +* [Beacon Proxy](/contracts-stylus/beacon-proxy): Multiple proxies pointing to a single beacon contract for mass upgrades of the implementation contract address. +* [UUPS Proxy](/contracts-stylus/uups-proxy): The Universal Upgradeable Proxy Standard (UUPS) that is a minimal and gas-efficient pattern for upgradeable contracts. diff --git a/docs/content/contracts-stylus/changelog.mdx b/docs/content/contracts-stylus/changelog.mdx new file mode 100644 index 00000000..e0350986 --- /dev/null +++ b/docs/content/contracts-stylus/changelog.mdx @@ -0,0 +1,126 @@ +--- +title: Changelog +--- + + +# [v0.3.0](https://github.com/OpenZeppelin/rust-contracts-stylus/releases/tag/v0.3.0) - 2025-09-10 + +### Added + +- **UUPS Proxy**: `UUPSUpgradeable` contract and `IErc1822Proxiable` trait for user-controlled upgradeable proxies. +- **Beacon Proxy**: `BeaconProxy` contract and `IBeacon` interface, supporting the beacon proxy pattern for upgradeable contracts. +- **Upgradeable Beacon**: `UpgradeableBeacon` contract, allowing upgradeable beacon-based proxies with owner-controlled implementation upgrades. +- **Enumerable Sets**: Generic `EnumerableSet` type with implementations for `Address`, `B256`, `U8`, `U16`, `U32`, `U64`, `U128`, `U256`. +- **Token Receivers**: `IErc1155Receiver` and `IErc721Receiver` traits with corresponding `Erc1155Holder` and `Erc721Holder` contracts. +- **Access Control Extensions**: `AccessControlEnumerable` extension that supports role member enumeration. +- **Enhanced SafeERC20**: Additional methods including `try_safe_transfer`, `try_safe_transfer_from`, and relaxed call variants. +- **Cryptography**: EDDSA (Ed25519) signature scheme, Twisted-Edwards Curves, and enhanced elliptic curve configurations (secp256k1, Baby Jubjub, Bandersnatch, Curve25519, Jubjub). +- **Precompiles**: Enhanced `Precompiles` trait with `p256_verify` wrapper function for ergonomic precompile invocation. +- **Type Conversions**: Bidirectional conversions between `ruint::Uint` and crypto library `Uint` types, plus conversions between `Uint` and primitive integer types. + +### Changed + +- **Type Aliases**: Standardized `FixedBytes<4>` to `B32`, `FixedBytes<32>` to `B256`, and `StorageFixedBytes<32>` to `StorageB256`. +- **API Simplifications**: Simplified Pedersen hash API to accept any type implementing `Into`. +- **Interface Compliance**: Removed redundant interface ID checks in `Erc1155Supply`. + +### Changed (Breaking) + +- **Interface Naming**: Renamed Solidity interfaces for consistency (`IERC721Receiver` → `IErc721ReceiverInterface`, `IERC1155Receiver` → `IErc1155ReceiverInterface`). +- **Trait Bounds**: Added `IErc721Receiver` trait bound to `IErc721Wrapper` trait. +- **Error Handling**: Replaced associated error types with raw byte output (`Vec`) in receiver traits for ABI compliance. +- **Deref Removal**: Removed `Deref` implementations for extension contracts to improve API clarity. +- **API Simplifications**: Prefix `ct_` removed for constant functions at `openzeppelin-crypto`. + +**Full Changelog**: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.2.0...v0.3.0. + +[Changes][v0.3.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/rust-contracts-stylus/releases/tag/v0.2.0) - 2025-06-20 + +### Added + +- **ERC-1155 token** (`Erc1155`, `Burnable`, `MetadataUri`, `Supply`, `UriStorage`). +- **ERC-4626** “Tokenized Vault Standard” and **ERC-20 Flash Mint** extension. +- **ERC-2981** on-chain royalties. +- **ERC-20 Utils**: `SafeErc20`. +- **Finance**: `VestingWallet`. +- **Ownership**: `Ownable2Step`. +- **Token wrappers**: `Erc20Wrapper`, `Erc721Wrapper`. +- **Cryptography**: Poseidon2 hash, Pedersen hash (Starknet params), Short-Weierstrass Curves. +- **Math & utils**: optimised `Uint<_>` big-integer ops (`mul_div`, shift-left/right, checked/unchecked add/sub). +- **Constructors** now supported across all contracts. +- All events derive `Debug`; contracts implement `MethodError` and `IErc165`. + +### Changed + +- Keccak constants pre-computed at compile-time. +- Optimisations across contracts and crypto libraries. + +### Changed (Breaking) + +- **Stylus SDK** ↑ to **v0.9.0** (`cargo-stylus` ↑ to v0.6.0) +- Contracts refactored to new inheritance model. +- Interface IDs now returned by `fn interface_id()`; `IErc165::supports_interface` takes `&self`; `Erc165` struct removed. +- Public state fields made private. +- Feature **`std` removed** from libraries. + +### Fixed + +- Correct ERC-165 IDs for `IErc721Metadata`, `IErc721Enumerable`, etc. +- `#[interface_id]` macro now propagates super-traits correctly. +- Edge-cases in Merkle proofs, big-int overflows and re-entrancy bugs resolved. + +**Full Changelog**: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.1.2...v0.2.0. + +[Changes][v0.2.0] + + + +# [v0.1.2](https://github.com/OpenZeppelin/rust-contracts-stylus/releases/tag/v0.1.2) - 2025-04-12 + +## Summary + +### Breaking Change +- build: in v0.1.1 bump sdk to 0.6.1. [#628](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/628) + +**Full Changelog**: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.1.1...v0.1.2 + +[Changes][v0.1.2] + + + +# [v0.1.1](https://github.com/OpenZeppelin/rust-contracts-stylus/releases/tag/v0.1.1) - 2024-10-28 + +## Summary + +### Changed +- Mini alloc is now used by default via the stylus-sdk. This avoids conflicts with duplicate `#[global_allocator]` definitions. [#373](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/373) +- Removed the panic handler from the library, making it easier for `std` and `no_std` projects to use the library. [#373](https://github.com/OpenZeppelin/rust-contracts-stylus/issues/373) + +## Full Changes List +* fix: reference-types error on nightly version by [@qalisander](https://github.com/qalisander) in [#375](https://github.com/OpenZeppelin/rust-contracts-stylus/pull/375) +* fix: use mini-alloc by default and remove panic handler by [@qalisander](https://github.com/qalisander) and [@ggonzalez94](https://github.com/ggonzalez94) in [#373](https://github.com/OpenZeppelin/rust-contracts-stylus/pull/373) +* build: bump v0.1.1 by [@bidzyyys](https://github.com/bidzyyys) in [#382](https://github.com/OpenZeppelin/rust-contracts-stylus/pull/382) + + +**Full Changelog**: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.1.0...v0.1.1 + +[Changes][v0.1.1] + + + +# [v0.1.0](https://github.com/OpenZeppelin/rust-contracts-stylus/releases/tag/v0.1.0) - 2024-10-17 + +First release 🎉 + +[Changes][v0.1.0] + + +[v0.3.0]: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.2.0...v0.3.0 +[v0.2.0]: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.1.2...v0.2.0 +[v0.1.2]: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.1.1...v0.1.2 +[v0.1.1]: https://github.com/OpenZeppelin/rust-contracts-stylus/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/OpenZeppelin/rust-contracts-stylus/tree/v0.1.0 diff --git a/docs/content/contracts-stylus/common.mdx b/docs/content/contracts-stylus/common.mdx new file mode 100644 index 00000000..395233ba --- /dev/null +++ b/docs/content/contracts-stylus/common.mdx @@ -0,0 +1,8 @@ +--- +title: Common (Tokens) +--- + +Contracts that are common to multiple token standards. + +## ERC-2981 +* **[ERC-2981](https://eips.ethereum.org/EIPS/eip-2981)**: standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal support for royalty payments across all NFT marketplaces and ecosystem participants. diff --git a/docs/content/contracts-stylus/crypto.mdx b/docs/content/contracts-stylus/crypto.mdx new file mode 100644 index 00000000..e6df8e7a --- /dev/null +++ b/docs/content/contracts-stylus/crypto.mdx @@ -0,0 +1,29 @@ +--- +title: Crypto +--- + +The OpenZeppelin Rust Contracts provide a crate for common cryptographic procedures in a blockchain environment. The following documents the available functionality. + +## 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. + + +OpenZeppelin Contracts provides a [JavaScript library](https://github.com/OpenZeppelin/merkle-tree) for building trees off-chain and generating proofs. + + +[`MerkleProof`](https://docs.rs/openzeppelin-crypto/0.3.0/openzeppelin_crypto/merkle/struct.Verifier.html) provides: + +* [`verify`](https://docs.rs/openzeppelin-crypto/0.3.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify) - can prove that some value is part of a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). +* [`verify_multi_proof`](https://docs.rs/openzeppelin-crypto/0.3.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof) - can prove multiple values are part of a Merkle tree. + +```rust +fn verify(&self, proof: Vec, root: B256, leaf: B256) -> bool { + let proof: Vec<[u8; 32]> = proof.into_iter().map(|m| *m).collect(); + Verifier::::verify(&proof, *root, *leaf) +} +``` + +Note that these functions use `keccak256` as the hashing algorithm, but our library also provides generic counterparts: [`verify_with_builder`](https://docs.rs/openzeppelin-crypto/0.3.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_with_builder) and [`verify_multi_proof_with_builder`](https://docs.rs/openzeppelin-crypto/0.3.0/openzeppelin_crypto/merkle/struct.Verifier.html#method.verify_multi_proof_with_builder). + +We also provide an adapter [`hash`](https://docs.rs/openzeppelin-crypto/0.3.0/openzeppelin_crypto/hash/index.html) module to use your own hashers in conjunction with them that resembles Rust’s standard library’s API. diff --git a/docs/content/contracts-stylus/erc1155-burnable.mdx b/docs/content/contracts-stylus/erc1155-burnable.mdx new file mode 100644 index 00000000..6b114461 --- /dev/null +++ b/docs/content/contracts-stylus/erc1155-burnable.mdx @@ -0,0 +1,134 @@ +--- +title: ERC-1155 Burnable +--- + +Extension of [ERC-1155](/contracts-stylus/erc1155) that allows token holders to destroy both their +own tokens and those that they have been approved to use. + +## Usage + +In order to make [`ERC-1155 Burnable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc1155/extensions/burnable/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc1155::{self, extensions::IErc1155Burnable, Erc1155, IErc1155}, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155Example { + erc1155: Erc1155, +} + +#[public] +#[implements(IErc1155, IErc1155Burnable, IErc165)] +impl Erc1155Example { + fn mint( + &mut self, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155._mint(to, token_id, amount, &data) + } + + fn mint_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155._mint_batch(to, token_ids, amounts, &data) + } +} + +#[public] +impl IErc1155Burnable for Erc1155Example { + type Error = erc1155::Error; + + fn burn( + &mut self, + account: Address, + token_id: U256, + value: U256, + ) -> Result<(), erc1155::Error> { + // ... + self.erc1155.burn(account, token_id, value)?; + // ... + Ok(()) + } + + fn burn_batch( + &mut self, + account: Address, + token_ids: Vec, + values: Vec, + ) -> Result<(), erc1155::Error> { + // ... + self.erc1155.burn_batch(account, token_ids, values)?; + // ... + Ok(()) + } +} + +#[public] +impl IErc1155 for Erc1155Example { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc165 for Erc1155Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc1155.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc1155-metadata-uri.mdx b/docs/content/contracts-stylus/erc1155-metadata-uri.mdx new file mode 100644 index 00000000..bfc286c9 --- /dev/null +++ b/docs/content/contracts-stylus/erc1155-metadata-uri.mdx @@ -0,0 +1,107 @@ +--- +title: ERC-1155 Metadata URI +--- + +The OpenZeppelin [ERC-1155](/contracts-stylus/erc1155) Metadata URI extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which points to a JSON file that conforms to the [ERC-1155 Metadata URI JSON Schema](https://eips.ethereum.org/EIPS/eip-1155#erc-1155-metadata-uri-json-schema). +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-1155](/contracts-stylus/erc1155) token with [Metadata URI](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc1155/extensions/metadata_uri/index.html) flavour, +you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::{ + erc1155, + erc1155::{ + extensions::{Erc1155MetadataUri, IErc1155MetadataUri}, + Erc1155, IErc1155, + }, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155MetadataUriExample { + erc1155: Erc1155, + metadata_uri: Erc1155MetadataUri, +} + +#[public] +#[implements(IErc1155, IErc1155MetadataUri, IErc165)] +impl Erc1155MetadataUriExample { + #[constructor] + fn constructor(&mut self, uri: String) { + self.metadata_uri.constructor(uri); + } +} + +#[public] +impl IErc1155 for Erc1155MetadataUriExample { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc1155MetadataUri for Erc1155MetadataUriExample { + fn uri(&self, token_id: U256) -> String { + self.metadata_uri.uri(token_id) + } +} + +#[public] +impl IErc165 for Erc1155MetadataUriExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc1155.supports_interface(interface_id) + || self.metadata_uri.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc1155-pausable.mdx b/docs/content/contracts-stylus/erc1155-pausable.mdx new file mode 100644 index 00000000..c975b987 --- /dev/null +++ b/docs/content/contracts-stylus/erc1155-pausable.mdx @@ -0,0 +1,176 @@ +--- +title: ERC-1155 Pausable +--- + +[ERC-1155](/contracts-stylus/erc1155) token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug. + +## Usage + +In order to make your [ERC-1155](/contracts-stylus/erc1155) token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/pausable/index.html) contract and apply its mechanisms to ERC1155 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc1155::{self, Erc1155, IErc1155}, + utils::{introspection::erc165::IErc165, pausable, IPausable, Pausable}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + InsufficientBalance(erc1155::ERC1155InsufficientBalance), + InvalidSender(erc1155::ERC1155InvalidSender), + InvalidReceiver(erc1155::ERC1155InvalidReceiver), + InvalidReceiverWithReason(erc1155::InvalidReceiverWithReason), + MissingApprovalForAll(erc1155::ERC1155MissingApprovalForAll), + InvalidApprover(erc1155::ERC1155InvalidApprover), + InvalidOperator(erc1155::ERC1155InvalidOperator), + InvalidArrayLength(erc1155::ERC1155InvalidArrayLength), + EnforcedPause(pausable::EnforcedPause), + ExpectedPause(pausable::ExpectedPause), +} + +impl From for Error { + fn from(value: erc1155::Error) -> Self { + match value { + erc1155::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc1155::Error::InvalidSender(e) => Error::InvalidSender(e), + erc1155::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc1155::Error::InvalidReceiverWithReason(e) => { + Error::InvalidReceiverWithReason(e) + } + erc1155::Error::MissingApprovalForAll(e) => { + Error::MissingApprovalForAll(e) + } + erc1155::Error::InvalidApprover(e) => Error::InvalidApprover(e), + erc1155::Error::InvalidOperator(e) => Error::InvalidOperator(e), + erc1155::Error::InvalidArrayLength(e) => { + Error::InvalidArrayLength(e) + } + } + } +} + +impl From for Error { + fn from(value: pausable::Error) -> Self { + match value { + pausable::Error::EnforcedPause(e) => Error::EnforcedPause(e), + pausable::Error::ExpectedPause(e) => Error::ExpectedPause(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc1155Example { + erc1155: Erc1155, + pausable: Pausable, +} + +#[public] +#[implements(IErc1155, IPausable)] +impl Erc1155Example { + fn mint( + &mut self, + to: Address, + token_id: U256, + amount: U256, + data: Bytes, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155._mint(to, token_id, amount, &data)?; + Ok(()) + } + + fn mint_batch( + &mut self, + to: Address, + token_ids: Vec, + amounts: Vec, + data: Bytes, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155._mint_batch(to, token_ids, amounts, &data)?; + Ok(()) + } +} + +#[public] +impl IPausable for Erc1155Example { + fn paused(&self) -> bool { + self.pausable.paused() + } +} + +#[public] +impl IErc1155 for Erc1155Example { + type Error = Error; + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.safe_transfer_from(from, to, id, value, data)?; + Ok(()) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data)?; + Ok(()) + } + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + Ok(self.erc1155.balance_of_batch(accounts, ids)?) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(self.erc1155.set_approval_for_all(operator, approved)?) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } +} + +#[public] +impl IErc165 for Erc1155Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc1155.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc1155-supply.mdx b/docs/content/contracts-stylus/erc1155-supply.mdx new file mode 100644 index 00000000..73686200 --- /dev/null +++ b/docs/content/contracts-stylus/erc1155-supply.mdx @@ -0,0 +1,130 @@ +--- +title: ERC-1155 Supply +--- + +The OpenZeppelin [ERC-1155](/contracts-stylus/erc1155) Supply extension that adds tracking of total supply per token id. +Useful for scenarios where Fungible and Non-fungible tokens have to be clearly identified. + +## Usage + +In order to make an [ERC-1155](/contracts-stylus/erc1155) token with [Supply](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc1155/extensions/supply/index.html) flavour, +you need to reexport all the supply-related functions. +Make sure to apply the `#[selector(name = "totalSupply")]` attribute to the `total_supply_all` function! +You need to create the specified contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc1155::{ + self, + extensions::{Erc1155Supply, IErc1155Supply}, + IErc1155, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155Example { + erc1155_supply: Erc1155Supply, +} + +#[public] +#[implements(IErc1155, IErc1155Supply, IErc165)] +impl Erc1155Example { + // Add token minting feature. + fn mint( + &mut self, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155_supply._mint(to, id, value, &data) + } + + fn mint_batch( + &mut self, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), erc1155::Error> { + self.erc1155_supply._mint_batch(to, ids, values, &data) + } +} + +#[public] +impl IErc1155Supply for Erc1155Example { + fn total_supply(&self, id: U256) -> U256 { + self.erc1155_supply.total_supply(id) + } + + #[selector(name = "totalSupply")] + fn total_supply_all(&self) -> U256 { + self.erc1155_supply.total_supply_all() + } + + fn exists(&self, id: U256) -> bool { + self.erc1155_supply.exists(id) + } +} + +#[public] +impl IErc1155 for Erc1155Example { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155_supply.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155_supply.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155_supply.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155_supply.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155_supply.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155_supply + .safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc165 for Erc1155Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc1155_supply.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc1155-uri-storage.mdx b/docs/content/contracts-stylus/erc1155-uri-storage.mdx new file mode 100644 index 00000000..b66a4b86 --- /dev/null +++ b/docs/content/contracts-stylus/erc1155-uri-storage.mdx @@ -0,0 +1,121 @@ +--- +title: ERC-1155 URI Storage +--- + +The OpenZeppelin [ERC-1155](/contracts-stylus/erc1155) URI Storage extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which can point to metadata about the token, such as images, descriptions, and other attributes. +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-1155](/contracts-stylus/erc1155) token with [URI Storage](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc1155/extensions/uri_storage/index.html) flavour, +your token should also use [`ERC-1155 Metadata URI`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc1155/extensions/metadata_uri/index.html) extension to provide additional URI metadata for each token. +You need to create a specified contract as follows: + +```rust +use openzeppelin_stylus::{ + token::{ + erc1155, + erc1155::{ + extensions::{ + Erc1155MetadataUri, Erc1155UriStorage, IErc1155MetadataUri, + }, + Erc1155, IErc1155, + }, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc1155MetadataUriExample { + erc1155: Erc1155, + metadata_uri: Erc1155MetadataUri, + uri_storage: Erc1155UriStorage, +} + +#[public] +#[implements(IErc1155, IErc1155MetadataUri, IErc165)] +impl Erc1155MetadataUriExample { + #[constructor] + fn constructor(&mut self, uri: String) { + self.metadata_uri.constructor(uri); + } + + #[selector(name = "setTokenURI")] + fn set_token_uri(&mut self, token_id: U256, token_uri: String) { + self.uri_storage.set_token_uri(token_id, token_uri, &self.metadata_uri) + } + + #[selector(name = "setBaseURI")] + fn set_base_uri(&mut self, base_uri: String) { + self.uri_storage.set_base_uri(base_uri) + } +} + +#[public] +impl IErc1155 for Erc1155MetadataUriExample { + type Error = erc1155::Error; + + fn balance_of(&self, account: Address, id: U256) -> U256 { + self.erc1155.balance_of(account, id) + } + + fn balance_of_batch( + &self, + accounts: Vec
, + ids: Vec, + ) -> Result, Self::Error> { + self.erc1155.balance_of_batch(accounts, ids) + } + + fn set_approval_for_all( + &mut self, + operator: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc1155.set_approval_for_all(operator, approved) + } + + fn is_approved_for_all(&self, account: Address, operator: Address) -> bool { + self.erc1155.is_approved_for_all(account, operator) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + id: U256, + value: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_transfer_from(from, to, id, value, data) + } + + fn safe_batch_transfer_from( + &mut self, + from: Address, + to: Address, + ids: Vec, + values: Vec, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc1155.safe_batch_transfer_from(from, to, ids, values, data) + } +} + +#[public] +impl IErc1155MetadataUri for Erc1155MetadataUriExample { + fn uri(&self, token_id: U256) -> String { + self.uri_storage.uri(token_id, &self.metadata_uri) + } +} + +#[public] +impl IErc165 for Erc1155MetadataUriExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc1155.supports_interface(interface_id) + || self.metadata_uri.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc1155.mdx b/docs/content/contracts-stylus/erc1155.mdx new file mode 100644 index 00000000..ac91ab96 --- /dev/null +++ b/docs/content/contracts-stylus/erc1155.mdx @@ -0,0 +1,15 @@ +--- +title: ERC-1155 +--- + +ERC1155 is a novel token standard that aims to take the best from previous standards to create a [**fungibility-agnostic**](./tokens#different-kinds-of-tokens) and **gas-efficient** [token contract](./tokens#but-first-coffee-a-primer-on-token-contracts). + +## Extensions + +Additionally, there are multiple custom extensions, including: + +* [ERC-1155 Burnable](/contracts-stylus/erc1155-burnable): Optional Burnable extension of the ERC-1155 standard. +* [ERC-1155 Metadata URI](/contracts-stylus/erc1155-metadata-uri): Optional extension that adds a token metadata URI. +* [ERC-1155 Pausable](/contracts-stylus/erc1155-pausable): A primitive to pause contract operation like token transfers, minting and burning. +* [ERC-1155 URI Storage](/contracts-stylus/erc1155-uri-storage): A more flexible but more expensive way of storing URI metadata. +* [ERC-1155 Supply](/contracts-stylus/erc1155-supply): Extension of the ERC-1155 standard that adds tracking of total supply per token id. diff --git a/docs/content/contracts-stylus/erc1967.mdx b/docs/content/contracts-stylus/erc1967.mdx new file mode 100644 index 00000000..5e300a2d --- /dev/null +++ b/docs/content/contracts-stylus/erc1967.mdx @@ -0,0 +1,276 @@ +--- +title: ERC-1967 Proxy +--- + +ERC-1967 is a standardized proxy pattern that defines specific storage slots for proxy contracts to prevent storage collisions between the proxy and implementation contracts. This standard ensures that proxy contracts can be safely upgraded without conflicts. + +The OpenZeppelin Stylus Contracts provides a complete implementation of the ERC-1967 standard, including the `Erc1967Proxy` contract and `Erc1967Utils` library for managing the standardized storage slots. + +## Understanding ERC-1967 + +ERC-1967 defines specific storage slots for proxy contracts: + +* ***Implementation Slot***: Stores the address of the current implementation contract. +* ***Admin Slot***: Stores the address of the admin account that can upgrade the proxy. +* ***Beacon Slot***: Stores the address of the beacon contract (for beacon proxies). + +These slots are calculated using specific keccak-256 hashes to ensure they don’t conflict with typical storage layouts. + +### Storage Slot Calculations + +The storage slots are calculated as follows: + +```rust +// 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc_U256 +pub const IMPLEMENTATION_SLOT: B256 = { + const HASH: [u8; 32] = keccak_const::Keccak256::new() + .update(b"eip1967.proxy.implementation") + .finalize(); + B256::new( + U256::from_be_bytes(HASH).wrapping_sub(uint!(1_U256)).to_be_bytes(), + ) +}; + +// 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103_U256 +pub const ADMIN_SLOT: B256 = { + const HASH: [u8; 32] = keccak_const::Keccak256::new() + .update(b"eip1967.proxy.admin") + .finalize(); + B256::new( + U256::from_be_bytes(HASH).wrapping_sub(uint!(1_U256)).to_be_bytes(), + ) +}; + +// 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50_U256 +pub const BEACON_SLOT: B256 = { + const HASH: [u8; 32] = keccak_const::Keccak256::new() + .update(b"eip1967.proxy.beacon") + .finalize(); + B256::new( + U256::from_be_bytes(HASH).wrapping_sub(uint!(1_U256)).to_be_bytes(), + ) +}; +``` + +## Basic ERC-1967 Proxy Implementation + +Here’s how to implement a basic ERC-1967 proxy: + +```rust +use openzeppelin_stylus::proxy::{ + erc1967::{self, Erc1967Proxy}, + IProxy, +}; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::Address, + prelude::*, + ArbResult, +}; + +#[entrypoint] +#[storage] +struct MyErc1967Proxy { + erc1967: Erc1967Proxy, +} + +#[public] +impl MyErc1967Proxy { + #[constructor] + pub fn constructor( + &mut self, + implementation: Address, + data: Bytes, + ) -> Result<(), erc1967::utils::Error> { + self.erc1967.constructor(implementation, &data) + } + + fn implementation(&self) -> Result> { + self.erc1967.implementation() + } + + #[payable] + #[fallback] + fn fallback(&mut self, calldata: &[u8]) -> ArbResult { + unsafe { self.erc1967.do_fallback(calldata) } + } +} +``` + +## ERC-1967 Utils Library + +The `Erc1967Utils` library provides helper functions for managing ERC-1967 storage slots: + +### Getting Values + +```rust +use openzeppelin_stylus::proxy::erc1967::utils::Erc1967Utils; + +// Get the current implementation address +let implementation = Erc1967Utils::get_implementation(); + +// Get the current admin address +let admin = Erc1967Utils::get_admin(); + +// Get the current beacon address +let beacon = Erc1967Utils::get_beacon(); +``` + +### Upgrading Implementation + +```rust +use openzeppelin_stylus::proxy::erc1967::utils::Erc1967Utils; +use stylus_sdk::{abi::Bytes, prelude::*}; + +impl MyErc1967Proxy { + /// Upgrade to a new implementation + fn upgrade_implementation( + &mut self, + new_implementation: Address, + data: Bytes, + ) -> Result<(), erc1967::utils::Error> { + Erc1967Utils::upgrade_to_and_call(self, new_implementation, &data) + } +} +``` + +### Changing Admin + +```rust +use openzeppelin_stylus::proxy::erc1967::utils::Erc1967Utils; + +impl MyErc1967Proxy { + /// Change the admin address + fn change_admin(&mut self, new_admin: Address) -> Result<(), erc1967::utils::Error> { + Erc1967Utils::change_admin(new_admin) + } +} +``` + +### Upgrading Beacon + +```rust +use openzeppelin_stylus::proxy::erc1967::utils::Erc1967Utils; +use stylus_sdk::{abi::Bytes, prelude::*}; + +impl MyErc1967Proxy { + /// Upgrade to a new beacon + fn upgrade_beacon( + &mut self, + new_beacon: Address, + data: Bytes, + ) -> Result<(), erc1967::utils::Error> { + Erc1967Utils::upgrade_beacon_to_and_call(self, new_beacon, &data) + } +} +``` + +## Constructor Data + +The ERC-1967 proxy constructor accepts optional initialization data: + +```rust +impl MyErc1967Proxy { + #[constructor] + fn constructor( + &mut self, + implementation: Address, + data: Bytes, + ) -> Result<(), erc1967::utils::Error> { + // If data is provided, it will be passed to the implementation + // during construction via delegatecall + self.erc1967.constructor(implementation, &data) + } +} +``` + +The `data` parameter can be used to: + +* ***Initialize storage***: Pass encoded function calls to set up initial state. +* ***Mint initial tokens***: Call mint functions on token contracts. +* ***Set up permissions***: Configure initial access control settings. +* ***Empty data***: Pass empty bytes if no initialization is needed. + +### Example: Initializing with Data + +```rust +use alloy_sol_macro::sol; +use alloy_sol_types::SolCall; + +sol! { + interface IERC20 { + function mint(address to, uint256 amount) external; + } +} + +// In your deployment script or test +let implementation = deploy_implementation(); +let initial_owner = alice; +let initial_supply = U256::from(1000000); + +// Encode the mint call +let mint_data = IERC20::mintCall { + to: initial_owner, + amount: initial_supply, +}.abi_encode(); + +// Deploy proxy with initialization data +let proxy = MyErc1967Proxy::deploy( + implementation, + mint_data.into(), +).expect("Failed to deploy proxy"); +``` + +## Storage Layout Safety + +ERC-1967 provides storage layout safety through standardized slots: + +### Benefits + +* ***No Storage Collisions***: Implementation storage cannot conflict with proxy storage. +* ***Predictable Layout***: Storage slots are standardized and well-documented. +* ***Upgrade Safety***: New implementations can safely use any storage layout. +* ***Gas Efficiency***: No need for complex storage gap patterns. + +### Implementation Storage + +Your implementation contract can use any storage layout without worrying about conflicts: + +```rust +#[entrypoint] +#[storage] +struct MyToken { + // These fields are safe to use - they won't conflict with ERC-1967 slots + balances: StorageMapping, + allowances: StorageMapping<(Address, Address), U256>, + total_supply: StorageU256, + name: StorageString, + symbol: StorageString, + decimals: StorageU8, + // ... any other storage fields +} +``` + +## Best Practices + +1. ***Always validate addresses***: ERC-1967 automatically validates that implementation and beacon addresses have code. +2. ***Use proper access control***: Implement admin controls for upgrade functions. +3. ***Test upgrades thoroughly***: Ensure new implementations are compatible with existing storage. +4. ***Emit events***: ERC-1967 events are automatically emitted, providing transparency. +5. ***Handle initialization data carefully***: Only send value when providing initialization data. +6. ***Document storage layout***: Even though ERC-1967 prevents conflicts, document your implementation’s storage. +7. ***Use standardized slots***: Don’t override the ERC-1967 storage slots in your implementation. + +## Common Pitfalls + +* ***Sending value without data***: ERC-1967 prevents sending value without initialization data to avoid stuck funds. +* ***Invalid implementation addresses***: Always ensure implementation contracts are deployed before upgrading. +* ***Missing access control***: Protect upgrade functions with proper access control. +* ***Storage layout changes***: Be careful when changing storage layout in new implementations. +* ***Incorrect initialization data***: Ensure initialization data is properly encoded. + +## Related Patterns + +* [Basic Proxy](/contracts-stylus/proxy): Basic proxy pattern using `delegate_call` for upgradeable contracts. +* [Beacon Proxy](/contracts-stylus/beacon-proxy): Multiple proxies pointing to a single beacon contract for mass upgrades of the implementation contract address. +* [UUPS Proxy](/contracts-stylus/uups-proxy): The Universal Upgradeable Proxy Standard (UUPS) that is a minimal and gas-efficient pattern for upgradeable contracts. diff --git a/docs/content/contracts-stylus/erc20-burnable.mdx b/docs/content/contracts-stylus/erc20-burnable.mdx new file mode 100644 index 00000000..f2ae283d --- /dev/null +++ b/docs/content/contracts-stylus/erc20-burnable.mdx @@ -0,0 +1,88 @@ +--- +title: ERC-20 Burnable +--- + +Extension of [ERC-20](/contracts-stylus/erc20) that allows token holders to destroy both their own tokens and those that they have an allowance for, in a way that can be recognized off-chain (via event analysis). + +## Usage + +In order to make [`ERC-20 Burnable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/burnable/index.html) methods "external" so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::token::erc20::{ + self, extensions::IErc20Burnable, Erc20, IErc20, +}; + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, +} + +#[public] +#[implements(IErc20, IErc20Burnable)] +impl Erc20Example {} + + +#[public] +impl IErc20 for Erc20Example { + type Error = erc20::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer(to, value) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + self.erc20.approve(spender, value) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer_from(from, to, value) + } +} + + +#[public] +impl IErc20Burnable for Erc20Example { + type Error = erc20::Error; + + fn burn(&mut self, value: U256) -> Result<(), erc20::Error> { + // ... + self.erc20.burn(value) + } + + fn burn_from( + &mut self, + account: Address, + value: U256, + ) -> Result<(), erc20::Error> { + // ... + self.erc20.burn_from(account, value) + } +} +``` diff --git a/docs/content/contracts-stylus/erc20-capped.mdx b/docs/content/contracts-stylus/erc20-capped.mdx new file mode 100644 index 00000000..0a406f3d --- /dev/null +++ b/docs/content/contracts-stylus/erc20-capped.mdx @@ -0,0 +1,152 @@ +--- +title: ERC-20 Capped +--- + +Extension of [ERC-20](/contracts-stylus/erc20) that adds a cap to the supply of tokens. + +## Usage + +In order to make [`ERC-20 Capped`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/capped/index.html) methods supervising the supply of tokens, you need to add them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::token::erc20::{ + self, + extensions::{capped, Capped, ICapped}, + Erc20, IErc20, +}; + +#[derive(SolidityError, Debug)] +enum Error { + ExceededCap(capped::ERC20ExceededCap), + InvalidCap(capped::ERC20InvalidCap), + InsufficientBalance(erc20::ERC20InsufficientBalance), + InvalidSender(erc20::ERC20InvalidSender), + InvalidReceiver(erc20::ERC20InvalidReceiver), + InsufficientAllowance(erc20::ERC20InsufficientAllowance), + InvalidSpender(erc20::ERC20InvalidSpender), + InvalidApprover(erc20::ERC20InvalidApprover), +} + +impl From for Error { + fn from(value: capped::Error) -> Self { + match value { + capped::Error::ExceededCap(e) => Error::ExceededCap(e), + capped::Error::InvalidCap(e) => Error::InvalidCap(e), + } + } +} + +impl From for Error { + fn from(value: erc20::Error) -> Self { + match value { + erc20::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc20::Error::InvalidSender(e) => Error::InvalidSender(e), + erc20::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc20::Error::InsufficientAllowance(e) => { + Error::InsufficientAllowance(e) + } + erc20::Error::InvalidSpender(e) => Error::InvalidSpender(e), + erc20::Error::InvalidApprover(e) => Error::InvalidApprover(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, + capped: Capped, +} + +#[public] +#[implements(IErc20, ICapped)] +impl Erc20Example { + #[constructor] + fn constructor(&mut self, cap: U256) -> Result<(), Error> { + Ok(self.capped.constructor(cap)?) + } + + // Add token minting feature. + // + // Make sure to handle `Capped` properly. You should not call + // [`Erc20::_update`] to mint tokens -- it will break the `Capped` + // mechanism. + fn mint( + &mut self, + account: Address, + value: U256, + ) -> Result<(), Error> { + let max_supply = self.capped.cap(); + + // Overflow check required. + let supply = self + .erc20 + .total_supply() + .checked_add(value) + .expect("new supply should not exceed `U256::MAX`"); + + if supply > max_supply { + return Err(capped::Error::ExceededCap( + capped::ERC20ExceededCap { + increased_supply: supply, + cap: max_supply, + }, + ))?; + } + + self.erc20._mint(account, value)?; + Ok(()) + } +} + +#[public] +impl ICapped for Erc20Example { + fn cap(&self) -> U256 { + self.capped.cap() + } +} + +#[public] +impl IErc20 for Erc20Example { + type Error = Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/erc20-flash-mint.mdx b/docs/content/contracts-stylus/erc20-flash-mint.mdx new file mode 100644 index 00000000..65101a8d --- /dev/null +++ b/docs/content/contracts-stylus/erc20-flash-mint.mdx @@ -0,0 +1,102 @@ +--- +title: ERC-20 Flash Mint +--- + +Extension of [ERC-20](/contracts-stylus/erc20) that provides flash loan support at the token level. + +## Usage + +In order to make [`ERC-20 Flash Mint`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/flash_mint/index.html) methods "external" so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::token::erc20::{ + extensions::{flash_mint, Erc20FlashMint, IErc3156FlashLender}, + Erc20, IErc20, +}; + +#[entrypoint] +#[storage] +struct Erc20FlashMintExample { + erc20: Erc20, + flash_mint: Erc20FlashMint, +} + +#[public] +#[implements(IErc20, IErc3156FlashLender)] +impl Erc20FlashMintExample {} + +#[public] +impl IErc3156FlashLender for Erc20FlashMintExample { + type Error = flash_mint::Error; + + fn max_flash_loan(&self, token: Address) -> U256 { + self.flash_mint.max_flash_loan(token, &self.erc20) + } + + fn flash_fee( + &self, + token: Address, + value: U256, + ) -> Result { + self.flash_mint.flash_fee(token, value) + } + + fn flash_loan( + &mut self, + receiver: Address, + token: Address, + value: U256, + data: Bytes, + ) -> Result { + self.flash_mint.flash_loan( + receiver, + token, + value, + &data, + &mut self.erc20, + ) + } +} + +#[public] +impl IErc20 for Erc20FlashMintExample { + type Error = flash_mint::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/erc20-metadata.mdx b/docs/content/contracts-stylus/erc20-metadata.mdx new file mode 100644 index 00000000..19c19fcb --- /dev/null +++ b/docs/content/contracts-stylus/erc20-metadata.mdx @@ -0,0 +1,102 @@ +--- +title: ERC-20 Metadata +--- + +Extension of [ERC-20](/contracts-stylus/erc20) that adds the optional metadata functions from the ERC20 standard. + +## Usage + +In order to make [`ERC-20 Metadata`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/metadata/index.html) methods "external" so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + self, + extensions::{Erc20Metadata, IErc20Metadata}, + Erc20, IErc20, + }, +}; + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, + metadata: Erc20Metadata, +} + +#[public] +#[implements(IErc20, IErc20Metadata, IErc165)] +impl Erc20Example { + #[constructor] + fn constructor(&mut self, name: String, symbol: String) { + self.metadata.constructor(name, symbol); + } + + // ... +} + +#[public] +impl IErc20 for Erc20Example { + type Error = erc20::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer(to, value) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + self.erc20.approve(spender, value) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer_from(from, to, value) + } +} + +#[public] +impl IErc20Metadata for Erc20Example { + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + fn decimals(&self) -> U8 { + self.metadata.decimals() + } +} + +#[public] +impl IErc165 for Erc20Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc20.supports_interface(interface_id) + || self.metadata.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc20-pausable.mdx b/docs/content/contracts-stylus/erc20-pausable.mdx new file mode 100644 index 00000000..39abd5c8 --- /dev/null +++ b/docs/content/contracts-stylus/erc20-pausable.mdx @@ -0,0 +1,130 @@ +--- +title: ERC-20 Pausable +--- + +ERC20 token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers in the event of a large bug. + +## Usage + +In order to make your ERC20 token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/pausable) contract and apply its mechanisms to ERC20 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{self, Erc20, IErc20}, + utils::{pausable, IPausable, Pausable}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + InsufficientBalance(erc20::ERC20InsufficientBalance), + InvalidSender(erc20::ERC20InvalidSender), + InvalidReceiver(erc20::ERC20InvalidReceiver), + InsufficientAllowance(erc20::ERC20InsufficientAllowance), + InvalidSpender(erc20::ERC20InvalidSpender), + InvalidApprover(erc20::ERC20InvalidApprover), + EnforcedPause(pausable::EnforcedPause), + ExpectedPause(pausable::ExpectedPause), +} + +impl From for Error { + fn from(value: erc20::Error) -> Self { + match value { + erc20::Error::InsufficientBalance(e) => { + Error::InsufficientBalance(e) + } + erc20::Error::InvalidSender(e) => Error::InvalidSender(e), + erc20::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc20::Error::InsufficientAllowance(e) => { + Error::InsufficientAllowance(e) + } + erc20::Error::InvalidSpender(e) => Error::InvalidSpender(e), + erc20::Error::InvalidApprover(e) => Error::InvalidApprover(e), + } + } +} + +impl From for Error { + fn from(value: pausable::Error) -> Self { + match value { + pausable::Error::EnforcedPause(e) => Error::EnforcedPause(e), + pausable::Error::ExpectedPause(e) => Error::ExpectedPause(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc20Example { + erc20: Erc20, + pausable: Pausable, +} + +#[public] +#[implements(IErc20, IPausable)] +impl Erc20Example { + fn mint(&mut self, account: Address, value: U256) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc20._mint(account, value)?; + Ok(()) + } +} + +#[public] +impl IErc20 for Erc20Example { + type Error = Error; + + fn transfer(&mut self, to: Address, value: U256) -> Result { + // ... + self.pausable.when_not_paused()?; + // ... + let result = self.erc20.transfer(to, value)?; + // ... + Ok(result) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + // ... + self.pausable.when_not_paused()?; + // ... + let result = self.erc20.transfer_from(from, to, value)?; + // ... + Ok(result) + } + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } +} + +#[public] +impl IPausable for Erc20Example { + fn paused(&self) -> bool { + self.pausable.paused() + } +} +``` diff --git a/docs/content/contracts-stylus/erc20-permit.mdx b/docs/content/contracts-stylus/erc20-permit.mdx new file mode 100644 index 00000000..1a11a6ee --- /dev/null +++ b/docs/content/contracts-stylus/erc20-permit.mdx @@ -0,0 +1,133 @@ +--- +title: ERC-20 Permit +--- + +Adds the permit method, which can be used to change an account's ERC20 allowance (see [`IErc20::allowance`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.allowance)) by presenting a message signed by the account. By not relying on [`IErc20::approve`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/trait.IErc20.html#tymethod.approve), the token holder account doesn't need to send a transaction, and thus is not required to hold Ether at all. + +## Usage + +In order to have [`ERC-20 Permit`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/permit/index.html) token, you need to use only this contract without [ERC-20](/contracts-stylus/erc20) as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::{permit, Erc20Permit, IErc20Permit}, + Erc20, IErc20, + }, + utils::{ + cryptography::eip712::IEip712, + nonces::{INonces, Nonces}, + }, +}; + +#[entrypoint] +#[storage] +struct Erc20PermitExample { + erc20: Erc20, + nonces: Nonces, + erc20_permit: Erc20Permit, +} + +#[storage] +struct Eip712; + +impl IEip712 for Eip712 { + const NAME: &'static str = "ERC-20 Permit Example"; + const VERSION: &'static str = "1"; +} + +#[public] +#[implements(IErc20, INonces, IErc20Permit)] +impl Erc20PermitExample { + // Add token minting feature. + fn mint( + &mut self, + account: Address, + value: U256, + ) -> Result<(), permit::Error> { + Ok(self.erc20._mint(account, value)?) + } +} + +#[public] +impl INonces for Erc20PermitExample { + fn nonces(&self, owner: Address) -> U256 { + self.nonces.nonces(owner) + } +} + +#[public] +impl IErc20Permit for Erc20PermitExample { + type Error = permit::Error; + + #[selector(name = "DOMAIN_SEPARATOR")] + fn domain_separator(&self) -> B256 { + self.erc20_permit.domain_separator() + } + + fn permit( + &mut self, + owner: Address, + spender: Address, + value: U256, + deadline: U256, + v: u8, + r: B256, + s: B256, + ) -> Result<(), Self::Error> { + self.erc20_permit.permit( + owner, + spender, + value, + deadline, + v, + r, + s, + &mut self.erc20, + &mut self.nonces, + ) + } +} + +#[public] +impl IErc20 for Erc20PermitExample { + type Error = permit::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/erc20-wrapper.mdx b/docs/content/contracts-stylus/erc20-wrapper.mdx new file mode 100644 index 00000000..7cd87471 --- /dev/null +++ b/docs/content/contracts-stylus/erc20-wrapper.mdx @@ -0,0 +1,113 @@ +--- +title: ERC-20 Wrapper +--- + +Extension of the ERC-20 token contract to support token wrapping. + +Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". +This is useful in conjunction with other modules. + +## Usage + +In order to make [`ERC20Wrapper`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/wrapper/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::token::erc20::{ + extensions::{wrapper, Erc20Wrapper, IErc20Wrapper}, + Erc20, IErc20, +}; +use stylus_sdk::{ + alloy_primitives::{Address, U256, U8}, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct Erc20WrapperExample { + erc20: Erc20, + erc20_wrapper: Erc20Wrapper, +} + +#[public] +#[implements(IErc20, IErc20Wrapper)] +impl Erc20WrapperExample { + #[constructor] + fn constructor( + &mut self, + underlying_token: Address, + ) -> Result<(), wrapper::Error> { + self.erc20_wrapper.constructor(underlying_token)? + } +} + +#[public] +impl IErc20Wrapper for Erc20WrapperExample { + type Error = wrapper::Error; + + fn underlying(&self) -> Address { + self.erc20_wrapper.underlying() + } + + fn decimals(&self) -> U8 { + self.erc20_wrapper.decimals() + } + + fn deposit_for( + &mut self, + account: Address, + value: U256, + ) -> Result { + self.erc20_wrapper.deposit_for(account, value, &mut self.erc20) + } + + fn withdraw_to( + &mut self, + account: Address, + value: U256, + ) -> Result { + self.erc20_wrapper.withdraw_to(account, value, &mut self.erc20) + } +} + +#[public] +impl IErc20 for Erc20WrapperExample { + type Error = wrapper::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} +``` diff --git a/docs/content/contracts-stylus/erc20.mdx b/docs/content/contracts-stylus/erc20.mdx new file mode 100644 index 00000000..ff252411 --- /dev/null +++ b/docs/content/contracts-stylus/erc20.mdx @@ -0,0 +1,158 @@ +--- +title: ERC-20 +--- + +An ERC-20 token contract keeps track of [_fungible_ tokens](/contracts-stylus/tokens#different-kinds-of-tokens): any token is exactly equal to any other token; no token has a special right 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 provide many ERC20-related contracts for Arbitrum Stylus. +On the [`API reference`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/struct.Erc20.html) you'll find detailed information on their properties and usage. + +## 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. + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + self, + extensions::{Erc20Metadata, IErc20Metadata}, + Erc20, IErc20, + }, +}; + +#[entrypoint] +#[storage] +struct GLDToken { + erc20: Erc20, + metadata: Erc20Metadata, +} + +#[public] +#[implements(IErc20, IErc20Metadata, IErc165)] +impl GLDToken { + #[constructor] + fn constructor(&mut self, name: String, symbol: String) { + self.metadata.constructor(name, symbol); + } + + // ... +} + +#[public] +impl IErc20 for GLDToken { + type Error = erc20::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer(to, value) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + self.erc20.approve(spender, value) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + self.erc20.transfer_from(from, to, value) + } +} + +#[public] +impl IErc20Metadata for GLDToken { + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + fn decimals(&self) -> U8 { + self.metadata.decimals() + } +} + +#[public] +impl IErc165 for GLDToken { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc20.supports_interface(interface_id) + || self.metadata.supports_interface(interface_id) + } +} +``` + +Our contracts are often used via stylus-sdk [inheritance](https://docs.arbitrum.io/stylus/reference/rust-sdk-guide#inheritance-inherit-and-borrow), and here we're reusing [`ERC20`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/struct.Erc20.html) for both the basic standard implementation and with optional extensions. + +## 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, ERC20 provides a [`decimals`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/metadata/trait.IErc20Metadata.html#tymethod.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 an exceptional reason not to. +When minting tokens or transferring them around, you will be actually sending the number `GLD * (10 ** decimals)`. + + +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. For example, to use `16` decimals, you would do: + +```rust +fn decimals(&self) -> U8 { + U8::from(16) +} +``` + +So if you want to send `5` tokens using a token contract with `18` decimals, the method to call will actually be: + +```rust +token.transfer(recipient, 5 * uint!(10_U256).pow(uint!(18_U256))); +``` + +## Extensions +Additionally, there are multiple custom extensions, including: + +* [ERC-20 Burnable](/contracts-stylus/erc20-burnable): destruction of own tokens. +* [ERC-20 Capped](/contracts-stylus/erc20-capped): enforcement of a cap to the total supply when minting tokens. +* [ERC-20 Metadata](/contracts-stylus/erc20-metadata): the extended ERC20 interface including the name, symbol, and decimals functions. +* [ERC-20 Pausable](/contracts-stylus/erc20-pausable): ability to pause token transfers. +* [ERC-20 Permit](/contracts-stylus/erc20-permit): gasless approval of tokens (standardized as [`EIP-2612`](https://eips.ethereum.org/EIPS/eip-2612)). +* [ERC-4626](/contracts-stylus/erc4626): tokenized vault that manages shares (represented as ERC-20) that are backed by assets (another ERC-20). +* [ERC-20 Flash-Mint](/contracts-stylus/erc20-flash-mint): token level support for flash loans through the minting and burning of ephemeral tokens (standardized as [`EIP-3156`](https://eips.ethereum.org/EIPS/eip-3156)). +* [ERC-20 Wrapper](/contracts-stylus/erc20-wrapper): wrapper to create an ERC-20 backed by another ERC-20, with deposit and withdraw methods. diff --git a/docs/content/contracts-stylus/erc2981.mdx b/docs/content/contracts-stylus/erc2981.mdx new file mode 100644 index 00000000..3b968696 --- /dev/null +++ b/docs/content/contracts-stylus/erc2981.mdx @@ -0,0 +1,19 @@ +--- +title: ERC-2981 +--- + +ERC-2981 offers a way to keep track of NFT royalties across different platforms. + +Royalty is the amount of money given to the owner of a particular asset (NFT in this case). Full definition: https://en.wikipedia.org/wiki/Royalty_payment [royalty]. + +This makes ERC-2981 useful for keeping track of royalty information across different platforms. It ensures that users don’t have to specify this information on every platform, and the information remains transparent on the blockchain. + +## Limitations and Considerations + +* ***Marketplace Enforcement***: Some NFT marketplaces may not enforce royalties. +* ***Manual Distribution***: On-chain royalty tracking does not mean automatic payouts. +* ***Gas Costs***: Extra storage and computations can slightly increase gas fees. + +## Additional Resources + +* ERC-2981 EIP: [EIP-2981 Specification](https://eips.ethereum.org/EIPS/eip-2981) diff --git a/docs/content/contracts-stylus/erc4626.mdx b/docs/content/contracts-stylus/erc4626.mdx new file mode 100644 index 00000000..9f88ba88 --- /dev/null +++ b/docs/content/contracts-stylus/erc4626.mdx @@ -0,0 +1,203 @@ +--- +title: ERC-4626 +--- + +Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in [ERC-4626](https://eips.ethereum.org/EIPS/eip-4626). + +This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for underlying "assets" through standardized `deposit`, `mint`, `redeem` and `burn` workflows. This contract extends the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this contract and not the "assets" token which is an independent contract. + +## Security concern: Inflation attack +To read more about the security concerns associated with the ERC-4626, check the [Inflation attack](/contracts/5.x/erc4626#security-concern-inflation-attack) description. + +## Usage + +In order to make [`ERC-4626`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc20/extensions/erc4626/index.html) methods "external" so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc20::{ + extensions::{ + erc4626, Erc20Metadata, Erc4626, IErc20Metadata, IErc4626, + }, + Erc20, IErc20, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc4626Example { + erc4626: Erc4626, + erc20: Erc20, + metadata: Erc20Metadata, +} + +#[public] +#[implements(IErc4626, IErc20, IErc20Metadata, IErc165)] +impl Erc4626Example { + #[constructor] + fn constructor( + &mut self, + asset: Address, + decimals_offset: U8, + name: String, + symbol: String, + ) { + self.erc4626.constructor(asset, decimals_offset); + self.metadata.constructor(name, symbol); + } +} + +#[public] +impl IErc4626 for Erc4626Example { + type Error = erc4626::Error; + + fn asset(&self) -> Address { + self.erc4626.asset() + } + + fn total_assets(&self) -> Result { + self.erc4626.total_assets() + } + + fn convert_to_shares(&self, assets: U256) -> Result { + self.erc4626.convert_to_shares(assets, &self.erc20) + } + + fn convert_to_assets(&self, shares: U256) -> Result { + self.erc4626.convert_to_assets(shares, &self.erc20) + } + + fn max_deposit(&self, receiver: Address) -> U256 { + self.erc4626.max_deposit(receiver) + } + + fn preview_deposit(&self, assets: U256) -> Result { + self.erc4626.preview_deposit(assets, &self.erc20) + } + + fn deposit( + &mut self, + assets: U256, + receiver: Address, + ) -> Result { + self.erc4626.deposit(assets, receiver, &mut self.erc20) + } + + fn max_mint(&self, receiver: Address) -> U256 { + self.erc4626.max_mint(receiver) + } + + fn preview_mint(&self, shares: U256) -> Result { + self.erc4626.preview_mint(shares, &self.erc20) + } + + fn mint( + &mut self, + shares: U256, + receiver: Address, + ) -> Result { + self.erc4626.mint(shares, receiver, &mut self.erc20) + } + + fn max_withdraw(&self, owner: Address) -> Result { + self.erc4626.max_withdraw(owner, &self.erc20) + } + + fn preview_withdraw(&self, assets: U256) -> Result { + self.erc4626.preview_withdraw(assets, &self.erc20) + } + + fn withdraw( + &mut self, + assets: U256, + receiver: Address, + owner: Address, + ) -> Result { + self.erc4626.withdraw(assets, receiver, owner, &mut self.erc20) + } + + fn max_redeem(&self, owner: Address) -> U256 { + self.erc4626.max_redeem(owner, &self.erc20) + } + + fn preview_redeem(&self, shares: U256) -> Result { + self.erc4626.preview_redeem(shares, &self.erc20) + } + + fn redeem( + &mut self, + shares: U256, + receiver: Address, + owner: Address, + ) -> Result { + self.erc4626.redeem(shares, receiver, owner, &mut self.erc20) + } +} + +#[public] +impl IErc20 for Erc4626Example { + type Error = erc4626::Error; + + fn total_supply(&self) -> U256 { + self.erc20.total_supply() + } + + fn balance_of(&self, account: Address) -> U256 { + self.erc20.balance_of(account) + } + + fn transfer( + &mut self, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer(to, value)?) + } + + fn allowance(&self, owner: Address, spender: Address) -> U256 { + self.erc20.allowance(owner, spender) + } + + fn approve( + &mut self, + spender: Address, + value: U256, + ) -> Result { + Ok(self.erc20.approve(spender, value)?) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + value: U256, + ) -> Result { + Ok(self.erc20.transfer_from(from, to, value)?) + } +} + +#[public] +impl IErc20Metadata for Erc4626Example { + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + fn decimals(&self) -> U8 { + self.erc4626.decimals() + } +} + +#[public] +impl IErc165 for Erc4626Example { + fn supports_interface(&self, interface_id: B32) -> bool { + ::interface_id() == interface_id + || self.erc20.supports_interface(interface_id) + || self.metadata.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-burnable.mdx b/docs/content/contracts-stylus/erc721-burnable.mdx new file mode 100644 index 00000000..4ee77d7a --- /dev/null +++ b/docs/content/contracts-stylus/erc721-burnable.mdx @@ -0,0 +1,113 @@ +--- +title: ERC-721 Burnable +--- + +[ERC-721](/contracts-stylus/erc721) Token that can be burned (destroyed). + +## Usage + +In order to make [`ERC-721 Burnable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/burnable/index.html) methods "external" so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{self, extensions::IErc721Burnable, Erc721, IErc721}, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, +} + +#[public] +#[implements(IErc721, IErc721Burnable, IErc165)] +impl Erc721Example { + fn burn(&mut self, token_id: U256) -> Result<(), erc721::Error> { + // ... + self.erc721.burn(token_id) + } +} + +#[public] +impl IErc721 for Erc721Example { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Burnable for Erc721Example { + type Error = erc721::Error; + + fn burn(&mut self, token_id: U256) -> Result<(), erc721::Error> { + // ... + self.erc721.burn(token_id) + } +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-consecutive.mdx b/docs/content/contracts-stylus/erc721-consecutive.mdx new file mode 100644 index 00000000..75910aef --- /dev/null +++ b/docs/content/contracts-stylus/erc721-consecutive.mdx @@ -0,0 +1,117 @@ +--- +title: ERC-721 Consecutive +--- + +Consecutive extension for [ERC-721](/contracts-stylus/erc721) is useful for efficiently minting multiple tokens in a single transaction. This can significantly reduce gas costs and improve performance when creating a large number of tokens at once. + +## Usage + +In order to make [`ERC-721 Consecutive`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/consecutive/index.html) methods "external" so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + extensions::{consecutive, Erc721Consecutive}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721ConsecutiveExample { + erc721_consecutive: Erc721Consecutive, +} + +#[public] +#[inherit(IErc721, IErc165)] +impl Erc721ConsecutiveExample { + #[constructor] + fn constructor( + &mut self, + receivers: Vec
, + amounts: Vec, + first_consecutive_id: U96, + max_batch_size: U96, + ) -> Result<(), consecutive::Error> { + self.erc721_consecutive.first_consecutive_id.set(first_consecutive_id); + self.erc721_consecutive.max_batch_size.set(max_batch_size); + for (&receiver, &amount) in receivers.iter().zip(amounts.iter()) { + self.erc721_consecutive._mint_consecutive(receiver, amount)?; + } + Ok(()) + } +} + +#[public] +impl IErc721 for Erc721ConsecutiveExample { + type Error = consecutive::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc165 for Erc721ConsecutiveExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-enumerable.mdx b/docs/content/contracts-stylus/erc721-enumerable.mdx new file mode 100644 index 00000000..766b1ed0 --- /dev/null +++ b/docs/content/contracts-stylus/erc721-enumerable.mdx @@ -0,0 +1,263 @@ +--- +title: ERC-721 Enumerable +--- + +The OpenZeppelin [ERC-721](/contracts-stylus/erc721) Enumerable extension is used to provide additional functionality to the standard ERC-721 token. Specifically, it allows for enumeration of all the token IDs in the contract as well as all the token IDs owned by each account. This is useful for applications that need to list or iterate over tokens, such as marketplaces or wallets. + +## Usage + +In order to make an [ERC-721](/contracts-stylus/erc721) token with [Enumerable](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/enumerable/index.html) flavour, +you need to create a specified contract as follows: +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{enumerable, Erc721Enumerable, IErc721Burnable}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[derive(SolidityError, Debug)] +enum Error { + OutOfBoundsIndex(enumerable::ERC721OutOfBoundsIndex), + EnumerableForbiddenBatchMint( + enumerable::ERC721EnumerableForbiddenBatchMint, + ), + InvalidOwner(erc721::ERC721InvalidOwner), + NonexistentToken(erc721::ERC721NonexistentToken), + IncorrectOwner(erc721::ERC721IncorrectOwner), + InvalidSender(erc721::ERC721InvalidSender), + InvalidReceiver(erc721::ERC721InvalidReceiver), + InvalidReceiverWithReason(erc721::InvalidReceiverWithReason), + InsufficientApproval(erc721::ERC721InsufficientApproval), + InvalidApprover(erc721::ERC721InvalidApprover), + InvalidOperator(erc721::ERC721InvalidOperator), +} + +impl From for Error { + fn from(value: enumerable::Error) -> Self { + match value { + enumerable::Error::OutOfBoundsIndex(e) => { + Error::OutOfBoundsIndex(e) + } + enumerable::Error::EnumerableForbiddenBatchMint(e) => { + Error::EnumerableForbiddenBatchMint(e) + } + } + } +} + +impl From for Error { + fn from(value: erc721::Error) -> Self { + match value { + erc721::Error::InvalidOwner(e) => Error::InvalidOwner(e), + erc721::Error::NonexistentToken(e) => Error::NonexistentToken(e), + erc721::Error::IncorrectOwner(e) => Error::IncorrectOwner(e), + erc721::Error::InvalidSender(e) => Error::InvalidSender(e), + erc721::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc721::Error::InvalidReceiverWithReason(e) => { + Error::InvalidReceiverWithReason(e) + } + erc721::Error::InsufficientApproval(e) => { + Error::InsufficientApproval(e) + } + erc721::Error::InvalidApprover(e) => Error::InvalidApprover(e), + erc721::Error::InvalidOperator(e) => Error::InvalidOperator(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, + enumerable: Erc721Enumerable, +} + +#[public] +#[implements(IErc721, IErc721Enumerable, IErc165)] +impl Erc721Example { + fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { + self.erc721._mint(to, token_id)?; + + // Update the extension's state. + self.enumerable._add_token_to_all_tokens_enumeration(token_id); + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } +} + +#[public] +impl IErc721 for Erc721Example { + type Error = Error; + + fn balance_of(&self, owner: Address) -> Result { + Ok(self.erc721.balance_of(owner)?) + } + + fn owner_of(&self, token_id: U256) -> Result { + Ok(self.erc721.owner_of(token_id)?) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.safe_transfer_from(from, to, token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + #[selector(name = "safeTransferFrom")] + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.safe_transfer_from_with_data(from, to, token_id, data)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + // Retrieve the previous owner. + let previous_owner = self.erc721.owner_of(token_id)?; + + self.erc721.transfer_from(from, to, token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + previous_owner, + token_id, + &self.erc721, + )?; + self.enumerable._add_token_to_owner_enumeration( + to, + token_id, + &self.erc721, + )?; + + Ok(()) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + Ok(self.erc721.approve(to, token_id)?) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(self.erc721.set_approval_for_all(to, approved)?) + } + + fn get_approved(&self, token_id: U256) -> Result { + Ok(self.erc721.get_approved(token_id)?) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Burnable for Erc721Example { + type Error = Error; + + fn burn(&mut self, token_id: U256) -> Result<(), Self::Error> { + // Retrieve the owner. + let owner = self.erc721.owner_of(token_id)?; + + self.erc721.burn(token_id)?; + + // Update the extension's state. + self.enumerable._remove_token_from_owner_enumeration( + owner, + token_id, + &self.erc721, + )?; + self.enumerable._remove_token_from_all_tokens_enumeration(token_id); + + Ok(()) + } +} + +#[public] +impl IErc721Enumerable for Erc721Example { + type Error = Error; + + fn total_supply(&self) -> U256 { + self.enumerable.total_supply() + } + + fn token_by_index(&self, index: U256) -> Result { + Ok(self.enumerable.token_by_index(index)?) + } + + fn token_of_owner_by_index( + &self, + owner: Address, + index: U256, + ) -> Result { + Ok(self.enumerable.token_of_owner_by_index(owner, index)?) + } +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + || self.enumerable.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-metadata.mdx b/docs/content/contracts-stylus/erc721-metadata.mdx new file mode 100644 index 00000000..9f368013 --- /dev/null +++ b/docs/content/contracts-stylus/erc721-metadata.mdx @@ -0,0 +1,136 @@ +--- +title: ERC-721 Metadata +--- + +Extension of [ERC-721](/contracts-stylus/erc721) that adds the optional metadata functions from the ERC721 standard. + +## Usage + +In order to make [`ERC-721 Metadata`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html) methods “external” so that other contracts can call them, you need to add the following code to your contract: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{Erc721Metadata, IErc721Metadata }, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721MetadataExample { + erc721: Erc721, + metadata: Erc721Metadata, +} + +#[public] +#[implements(IErc721, IErc721Metadata, IErc165)] +impl Erc721MetadataExample { + #[constructor] + fn constructor(&mut self, name: String, symbol: String, base_uri: String) { + self.metadata.constructor(name, symbol); + self.metadata.base_uri.set_str(base_uri); + } + + fn mint( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), erc721::Error> { + self.erc721._mint(to, token_id) + } +} + +#[public] +impl IErc721 for Erc721MetadataExample { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Metadata for Erc721MetadataExample { + type Error = erc721::Error; + + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + #[selector(name = "tokenURI")] + fn token_uri(&self, token_id: U256) -> Result { + self.metadata.token_uri(token_id, &self.erc721) + } +} + +#[public] +impl IErc165 for Erc721MetadataExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + || ::interface_id() == interface_id + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-pausable.mdx b/docs/content/contracts-stylus/erc721-pausable.mdx new file mode 100644 index 00000000..2251f2a5 --- /dev/null +++ b/docs/content/contracts-stylus/erc721-pausable.mdx @@ -0,0 +1,173 @@ +--- +title: ERC-721 Pausable +--- + +ERC721 token with pausable token transfers, minting, and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation period, or having an emergency switch for freezing all token transfers, e.g. caused by a bug. + +## Usage + +In order to make your ERC721 token `pausable`, you need to use the [`Pausable`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/pausable) contract and apply its mechanisms to ERC721 token functions as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{self, Erc721, IErc721}, + utils::{introspection::erc165::IErc165, pausable, IPausable, Pausable}, +}; + +#[derive(SolidityError, Debug)] +enum Error { + InvalidOwner(erc721::ERC721InvalidOwner), + NonexistentToken(erc721::ERC721NonexistentToken), + IncorrectOwner(erc721::ERC721IncorrectOwner), + InvalidSender(erc721::ERC721InvalidSender), + InvalidReceiver(erc721::ERC721InvalidReceiver), + InvalidReceiverWithReason(erc721::InvalidReceiverWithReason), + InsufficientApproval(erc721::ERC721InsufficientApproval), + InvalidApprover(erc721::ERC721InvalidApprover), + InvalidOperator(erc721::ERC721InvalidOperator), + EnforcedPause(pausable::EnforcedPause), + ExpectedPause(pausable::ExpectedPause), +} + +impl From for Error { + fn from(value: erc721::Error) -> Self { + match value { + erc721::Error::InvalidOwner(e) => Error::InvalidOwner(e), + erc721::Error::NonexistentToken(e) => Error::NonexistentToken(e), + erc721::Error::IncorrectOwner(e) => Error::IncorrectOwner(e), + erc721::Error::InvalidSender(e) => Error::InvalidSender(e), + erc721::Error::InvalidReceiver(e) => Error::InvalidReceiver(e), + erc721::Error::InvalidReceiverWithReason(e) => { + Error::InvalidReceiverWithReason(e) + } + erc721::Error::InsufficientApproval(e) => { + Error::InsufficientApproval(e) + } + erc721::Error::InvalidApprover(e) => Error::InvalidApprover(e), + erc721::Error::InvalidOperator(e) => Error::InvalidOperator(e), + } + } +} + +impl From for Error { + fn from(value: pausable::Error) -> Self { + match value { + pausable::Error::EnforcedPause(e) => Error::EnforcedPause(e), + pausable::Error::ExpectedPause(e) => Error::ExpectedPause(e), + } + } +} + +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, + pausable: Pausable, +} + +#[public] +#[implements(IErc721, IPausable, IErc165)] +impl Erc721Example { + fn mint(&mut self, to: Address, token_id: U256) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721._mint(to, token_id)?; + Ok(()) + } +} + +#[public] +impl IErc721 for Erc721Example { + type Error = Error; + + fn balance_of(&self, owner: Address) -> Result { + Ok(self.erc721.balance_of(owner)?) + } + + fn owner_of(&self, token_id: U256) -> Result { + Ok(self.erc721.owner_of(token_id)?) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.safe_transfer_from(from, to, token_id)?; + Ok(()) + } + + #[selector(name = "safeTransferFrom")] + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.safe_transfer_from_with_data(from, to, token_id, data)?; + Ok(()) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Error> { + // ... + self.pausable.when_not_paused()?; + // ... + self.erc721.transfer_from(from, to, token_id)?; + Ok(()) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + Ok(self.erc721.approve(to, token_id)?) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + Ok(self.erc721.set_approval_for_all(to, approved)?) + } + + fn get_approved(&self, token_id: U256) -> Result { + Ok(self.erc721.get_approved(token_id)?) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IPausable for Erc721Example { + fn paused(&self) -> bool { + self.pausable.paused() + } +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-uri-storage.mdx b/docs/content/contracts-stylus/erc721-uri-storage.mdx new file mode 100644 index 00000000..dfaad6c5 --- /dev/null +++ b/docs/content/contracts-stylus/erc721-uri-storage.mdx @@ -0,0 +1,153 @@ +--- +title: ERC-721 Uri Storage +--- + +The OpenZeppelin [ERC-721](/contracts-stylus/erc721) URI Storage extension is needed to manage and store URIs for individual tokens. This extension allows each token to have its own unique URI, +which can point to metadata about the token, such as images, descriptions, and other attributes. +This is particularly useful for non-fungible tokens (NFTs) where each token is unique and may have different metadata. + +## Usage + +In order to make an [ERC-721](/contracts-stylus/erc721) token with [URI Storage](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/uri_storage/index.html) flavour, +your token should also use [`ERC-721 Metadata`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/metadata/index.html) extension to provide additional metadata for each token. +You need to create a specified contract as follows: +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{ + Erc721Metadata, Erc721UriStorage, IErc721Metadata, + IErc721UriStorage, + }, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct Erc721MetadataExample { + erc721: Erc721, + metadata: Erc721Metadata, + uri_storage: Erc721UriStorage, +} + +#[public] +#[implements(IErc721, IErc721Metadata, IErc165)] +impl Erc721MetadataExample { + #[constructor] + fn constructor(&mut self, name: String, symbol: String, base_uri: String) { + self.metadata.constructor(name, symbol); + self.metadata.base_uri.set_str(base_uri); + } + + fn mint( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), erc721::Error> { + self.erc721._mint(to, token_id) + } + + #[selector(name = "setTokenURI")] + fn set_token_uri(&mut self, token_id: U256, token_uri: String) { + self.uri_storage._set_token_uri(token_id, token_uri) + } +} + +#[public] +impl IErc721 for Erc721MetadataExample { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Metadata for Erc721MetadataExample { + type Error = erc721::Error; + + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + #[selector(name = "tokenURI")] + fn token_uri(&self, token_id: U256) -> Result { + self.uri_storage.token_uri(token_id, &self.erc721, &self.metadata) + } +} + +// We implement this trait only to respect Rust's trait requirements. +// There's nothing to expose, as all the necessary functions were exposed by +// implementing `IErc721Metadata`. +impl IErc721UriStorage for Erc721MetadataExample {} + +#[public] +impl IErc165 for Erc721MetadataExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + || ::interface_id() == interface_id + } +} +``` diff --git a/docs/content/contracts-stylus/erc721-wrapper.mdx b/docs/content/contracts-stylus/erc721-wrapper.mdx new file mode 100644 index 00000000..5817e177 --- /dev/null +++ b/docs/content/contracts-stylus/erc721-wrapper.mdx @@ -0,0 +1,163 @@ +--- +title: ERC-721 Wrapper +--- + +Extension of the ERC-721 token contract to support token wrapping. + +Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". +This is useful in conjunction with other modules. + +## Usage + +In order to make [`ERC721Wrapper`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/wrapper) methods "external" so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{wrapper, Erc721Wrapper, IErc721Wrapper}, + receiver::IErc721Receiver, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; +use stylus_sdk::{ + abi::Bytes, + alloy_primitives::{aliases::B32, Address, U256}, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct Erc721WrapperExample { + erc721: Erc721, + erc721_wrapper: Erc721Wrapper, +} + +#[public] +#[implements(IErc721, IErc721Wrapper, IErc165, IErc721Receiver)] +impl Erc721WrapperExample { + #[constructor] + fn constructor(&mut self, underlying_token: Address) { + self.erc721_wrapper.constructor(underlying_token); + } +} + +#[public] +impl IErc721 for Erc721WrapperExample { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Wrapper for Erc721WrapperExample { + type Error = wrapper::Error; + + fn underlying(&self) -> Address { + self.erc721_wrapper.underlying() + } + + fn deposit_for( + &mut self, + account: Address, + token_ids: Vec, + ) -> Result { + self.erc721_wrapper.deposit_for(account, token_ids, &mut self.erc721) + } + + fn withdraw_to( + &mut self, + account: Address, + token_ids: Vec, + ) -> Result { + self.erc721_wrapper.withdraw_to(account, token_ids, &mut self.erc721) + } +} + +#[public] +impl IErc721Receiver for Erc721WrapperExample { + fn on_erc721_received( + &mut self, + operator: Address, + from: Address, + token_id: U256, + data: Bytes, + ) -> Result> { + self.erc721_wrapper + .on_erc721_received( + operator, + from, + token_id, + &data, + &mut self.erc721, + ) + .map_err(|e| e.into()) + } +} + +#[public] +impl IErc165 for Erc721WrapperExample { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + } +} +``` diff --git a/docs/content/contracts-stylus/erc721.mdx b/docs/content/contracts-stylus/erc721.mdx new file mode 100644 index 00000000..8779880e --- /dev/null +++ b/docs/content/contracts-stylus/erc721.mdx @@ -0,0 +1,175 @@ +--- +title: ERC-721 +--- + +We've discussed how you can make a _fungible_ token using [ERC-20](/contracts-stylus/erc20), 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 [_non-fungible_ tokens](/contracts-stylus/tokens#different-kinds-of-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 [`API reference`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/struct.Erc721.html) 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 any account can call `awardItem` to mint items. +To restrict what accounts can be minted per item. +We can use an [Access Control](/contracts-stylus/access-control) extension. + +Here's what a contract for tokenized items might look like: + +```rust +use openzeppelin_stylus::{ + token::erc721::{ + self, + extensions::{Erc721Metadata, IErc721Metadata}, + Erc721, IErc721, + }, + utils::introspection::erc165::IErc165, +}; + +#[entrypoint] +#[storage] +struct GameItem { + erc721: Erc721, + metadata: Erc721Metadata, + next_token_id: StorageU256, +} + +#[public] +#[implements(IErc721, IErc721Metadata, IErc165)] +impl GameItem { + #[constructor] + fn constructor(&mut self, name: String, symbol: String, base_uri: String) { + self.metadata.constructor(name, symbol); + self.metadata.base_uri.set_str(base_uri); + } + + fn award_item(&mut self, player: Address) -> Result { + let token_id = self.next_token_id.get() + uint!(1_U256); + self.next_token_id.set(token_id); + + self.erc721._mint(player, token_id)?; + + Ok(token_id) + } +} + +#[public] +impl IErc721 for GameItem { + type Error = erc721::Error; + + fn balance_of(&self, owner: Address) -> Result { + self.erc721.balance_of(owner) + } + + fn owner_of(&self, token_id: U256) -> Result { + self.erc721.owner_of(token_id) + } + + fn safe_transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from(from, to, token_id) + } + + fn safe_transfer_from_with_data( + &mut self, + from: Address, + to: Address, + token_id: U256, + data: Bytes, + ) -> Result<(), Self::Error> { + self.erc721.safe_transfer_from_with_data(from, to, token_id, data) + } + + fn transfer_from( + &mut self, + from: Address, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.transfer_from(from, to, token_id) + } + + fn approve( + &mut self, + to: Address, + token_id: U256, + ) -> Result<(), Self::Error> { + self.erc721.approve(to, token_id) + } + + fn set_approval_for_all( + &mut self, + to: Address, + approved: bool, + ) -> Result<(), Self::Error> { + self.erc721.set_approval_for_all(to, approved) + } + + fn get_approved(&self, token_id: U256) -> Result { + self.erc721.get_approved(token_id) + } + + fn is_approved_for_all(&self, owner: Address, operator: Address) -> bool { + self.erc721.is_approved_for_all(owner, operator) + } +} + +#[public] +impl IErc721Metadata for GameItem { + type Error = erc721::Error; + + fn name(&self) -> String { + self.metadata.name() + } + + fn symbol(&self) -> String { + self.metadata.symbol() + } + + #[selector(name = "tokenURI")] + fn token_uri(&self, token_id: U256) -> Result { + self.metadata.token_uri(token_id, &self.erc721) + } +} + +#[public] +impl IErc165 for GameItem { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + || ::interface_id() == interface_id + } +} +``` + +The [`Erc721Metadata`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/token/erc721/extensions/metadata/struct.Erc721Metadata.html) contract is an extension contract of ERC-721. +It extends the contract itself with the name, symbol and base uri for the token. + +Also note that, unlike ERC-20, ERC-721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +For more information about erc721 schema, check out the [ERC-721 specification](https://eips.ethereum.org/EIPS/eip-721). + + +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! + +## Extensions + +Additionally, there are multiple custom extensions, including: + +* [ERC-721 Burnable](/contracts-stylus/erc721-burnable): A way for token holders to burn their own tokens. +* [ERC-721 Consecutive](/contracts-stylus/erc721-consecutive): An implementation of [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) for minting batches of tokens during construction, in accordance with ERC721. +* [ERC-721 Enumerable](/contracts-stylus/erc721-enumerable): Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* [ERC-721 Metadata](/contracts-stylus/erc721-metadata): Optional extension that adds name, symbol, and token URI, almost always included. +* [ERC-721 Pausable](/contracts-stylus/erc721-pausable): A primitive to pause contract operation. +* [ERC-721 Uri Storage](/contracts-stylus/erc721-uri-storage): A more flexible but more expensive way of storing metadata. +* [ERC-721 Wrapper](/contracts-stylus/erc721-wrapper): Wrapper to create an ERC-721 backed by another ERC-721, with deposit and withdraw methods. diff --git a/docs/content/contracts-stylus/finance.mdx b/docs/content/contracts-stylus/finance.mdx new file mode 100644 index 00000000..6a78127e --- /dev/null +++ b/docs/content/contracts-stylus/finance.mdx @@ -0,0 +1,11 @@ +--- +title: Finance +--- + +Contracts that include primitives for financial systems. + +## VestingWallet +* [VestingWallet](/contracts-stylus/vesting-wallet) handles the vesting of Ether and ERC-20 + tokens for a given beneficiary. Custody of multiple tokens can be given to this + contract, which will release the token to the beneficiary following a given, + customizable, vesting schedule. diff --git a/docs/content/contracts-stylus/index.mdx b/docs/content/contracts-stylus/index.mdx new file mode 100644 index 00000000..bb93498a --- /dev/null +++ b/docs/content/contracts-stylus/index.mdx @@ -0,0 +1,83 @@ +--- +title: OpenZeppelin Contracts for Stylus +--- + +**A secure, modular smart contract library for [Stylus](https://docs.arbitrum.io/stylus/gentle-introduction), written in Rust.** + +OpenZeppelin Contracts for Stylus brings time-tested smart contract patterns to Arbitrum's WASM-based execution environment. This library provides `no_std`-compatible modules for building secure, reusable contracts in [Stylus](https://docs.arbitrum.io/stylus/gentle-introduction). + +## Features + +* ✨ Security-first contracts ported from [`openzeppelin-contracts`](https://github.com/OpenZeppelin/openzeppelin-contracts). +* 📦 Written in Rust with full `no_std` support. +* 🧪 Tested with both unit and integration tests. +* 🚧 Actively developed. + +## Quick Start + +Add the dependency to your `Cargo.toml`: + +```toml +[dependencies] +openzeppelin-stylus = "=0.2.0" +``` + +Enable the ABI export feature: + +```toml +[features] +export-abi = ["openzeppelin-stylus/export-abi"] +``` + +## Usage Example + +A minimal ERC-20 implementation using the library: + +```rust +use openzeppelin_stylus::token::erc20::self, Erc20, IErc20; +use stylus_sdk:: + alloy_primitives::{Address, U256, + prelude::*, +}; + +#[entrypoint] +#[storage] +struct Erc20Example + erc20: Erc20, + + +#[public] +#[implements(IErc20)] +impl Erc20Example {} + +#[public] +impl IErc20 for Erc20Example + // ERC-20 logic implementation... + +``` + +Explore more examples in the [`examples` directory](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/examples). + +## Compatibility + +This library is designed to work with `no_std`. To keep your contracts compatible, disable default features for any dependencies that pull in the standard library: + +```toml +[dependencies] +alloy-primitives = version = "=0.8.20", default-features = false +stylus-sdk = "=0.9.0" +``` + +## Roadmap & Contributing + +See what’s planned or in development in our [roadmap](https://github.com/orgs/OpenZeppelin/projects/35). + +Interested in contributing? Read the [contribution guide](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/CONTRIBUTING.md). + +## Security + +While this library is under active development, security remains a top priority. For past audits and security reports, see the [`audits` directory](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/audits). + +## License + +Released under the [MIT License](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/LICENSE). diff --git a/docs/content/contracts-stylus/proxy.mdx b/docs/content/contracts-stylus/proxy.mdx new file mode 100644 index 00000000..5cb93079 --- /dev/null +++ b/docs/content/contracts-stylus/proxy.mdx @@ -0,0 +1,274 @@ +--- +title: Proxy Patterns +--- + +Proxy contracts are a fundamental pattern in smart contract development that allow you to separate the storage and logic of your contracts. This enables powerful features like upgradeability, gas optimization, and code reuse. + +The OpenZeppelin Stylus Contracts provides the `IProxy` trait, which implements a low-level proxy pattern using the Stylus [`delegate_call`](https://docs.rs/stylus-sdk/0.9.0/stylus_sdk/call/fn.delegate_call.html) function. This allows you to delegate all calls to another contract while maintaining the same storage context. + +## Understanding Proxy Patterns + +A proxy contract acts as a "wrapper" around an implementation contract. When users interact with the proxy: + +1. The proxy receives the call. +2. It delegates the call to the implementation contract using `delegate_call`. +3. The implementation executes the logic in the proxy’s storage context. +4. The result is returned to the user. + +This pattern provides several benefits: + +* ***Upgradeability***: You can change the implementation while keeping the same proxy address. +* ***Gas Efficiency***: Multiple proxies can share the same implementation code. +* ***Storage Separation***: Logic and storage are cleanly separated. + +## The IProxy Trait + +The `IProxy` trait provides the core functionality for implementing proxy patterns: + +```rust +use openzeppelin_stylus::proxy::IProxy; +use stylus_sdk::prelude::*; + +pub unsafe trait IProxy: TopLevelStorage + Sized { + /// Delegates the current call to a specific implementation + fn delegate( + &mut self, + implementation: Address, + calldata: &[u8], + ) -> Result, Error>; + + /// Returns the address of the implementation contract + fn implementation(&self) -> Result>; + + /// Fallback function that delegates calls to the implementation + fn do_fallback(&mut self, calldata: &[u8]) -> Result, Vec>; +} +``` + +## Basic Proxy Implementation + +Here’s a minimal example of how to implement a basic proxy contract: + +```rust +use openzeppelin_stylus::proxy::IProxy; +use stylus_sdk::{ + alloy_primitives::Address, + prelude::*, + storage::StorageAddress, + ArbResult, +}; + +#[entrypoint] +#[storage] +struct MyProxy { + implementation: StorageAddress, +} + +#[public] +impl MyProxy { + #[constructor] + fn constructor(&mut self, implementation: Address) { + self.implementation.set(implementation); + } + + /// Fallback function that delegates all calls to the implementation + #[fallback] + fn fallback(&mut self, calldata: &[u8]) -> ArbResult { + unsafe { self.do_fallback(calldata) } + } +} + +impl IProxy for MyProxy { + fn implementation(&self) -> Result> { + Ok(self.implementation.get()) + } +} +``` + +This is the minimal implementation required for a working proxy. The `IProxy` trait provides the `do_fallback` method that handles the delegation logic. + +### Enhanced Proxy with Admin Controls + +For production use, you’ll typically want to add admin controls for upgrading the implementation: + +```rust +use openzeppelin_stylus::proxy::IProxy; +use stylus_sdk::{ + alloy_primitives::Address, + prelude::*, + storage::{StorageAddress, StorageBool}, + ArbResult, +}; + +#[entrypoint] +#[storage] +struct MyUpgradeableProxy { + implementation: StorageAddress, + admin: StorageAddress, +} + +#[public] +impl MyUpgradeableProxy { + #[constructor] + fn constructor(&mut self, implementation: Address, admin: Address) { + self.implementation.set(implementation); + self.admin.set(admin); + } + + /// Admin function to update the implementation + fn upgrade_implementation(&mut self, new_implementation: Address) -> Result<(), Vec> { + // Only admin can upgrade + if self.admin.get() != msg::sender() { + return Err("Only admin can upgrade".abi_encode()); + } + + self.implementation.set(new_implementation); + Ok(()) + } + + /// Admin function to transfer admin rights + fn transfer_admin(&mut self, new_admin: Address) -> Result<(), Vec> { + if self.admin.get() != msg::sender() { + return Err("Only admin can transfer admin".abi_encode()); + } + + self.admin.set(new_admin); + Ok(()) + } + + /// Fallback function that delegates all calls to the implementation + #[fallback] + fn fallback(&mut self, calldata: &[u8]) -> ArbResult { + self.do_fallback(calldata) + } +} + +impl IProxy for MyUpgradeableProxy { + fn implementation(&self) -> Result> { + let impl_addr = self.implementation.get(); + if impl_addr == Address::ZERO { + return Err("Implementation not set".abi_encode()); + } + Ok(impl_addr) + } +} +``` + +## Implementation Contract + +The implementation contract contains the actual business logic. Here’s an example ERC-20 implementation: + +```rust +#[entrypoint] +#[storage] +struct MyToken { + // ⚠️ The storage layout here must match the proxy's storage layout exactly. + // For example, if the proxy defines implementation and admin addresses, + // the implementation must define them in the same order and type. + // This prevents storage collisions when using delegatecall. + implementation: StorageAddress, + admin: StorageAddress, + // Now you can set the actual implementation-specific state fields. + erc20: Erc20, +} + +#[public] +#[implements(IErc20)] +impl MyToken { + #[constructor] + fn constructor(&mut self, name: String, symbol: String) { + // Initialize the ERC-20 with metadata + self.erc20.constructor(name, symbol); + } + + /// Mint tokens to a specific address (only for demonstration) + fn mint(&mut self, to: Address, amount: U256) -> Result<(), erc20::Error> { + self.erc20._mint(to, amount) + } +} + +#[public] +impl IErc20 for MyToken { + // ... +} +``` + +## Advanced Proxy Features + +### Direct Delegation + +You can also delegate calls directly to different implementations using the `delegate` method from the `IProxy` trait: + +```rust +impl MyProxy { + /// Delegate to a specific implementation (useful for testing or special cases) + fn delegate_to_implementation( + &mut self, + target_implementation: Address, + calldata: &[u8], + ) -> Result, Vec> { + Ok(IProxy::delegate(self, target_implementation, calldata)?) + } +} +``` + +## Storage Layout Considerations + +When working with proxy patterns like the `MyUpgradeableProxy` example above, it’s essential to understand how storage is actually structured under the hood. Even though the implementation contract contains the business logic, all state is stored in the proxy contract itself. This means that the proxy’s storage layout must be carefully designed to match what the implementation expects. + +For instance, in the `MyUpgradeableProxy` example, the proxy struct contains fields like `implementation` and `admin` for proxy management, but it also needs to reserve space for all the state variables that the implementation contract will use (such as token balances, allowances, etc.). This ensures that when the implementation logic is executed via `delegate_call`, it interacts with the correct storage slots. + +Here’s what the storage struct for a proxy might look like under the hood in practice: + +```rust +#[storage] +struct MyUpgradeableProxy { + // Proxy-specific storage + implementation: StorageAddress, + admin: StorageAddress, + + // Implementation storage (shared with the implementation contract) + // These fields must exactly match the implementation contract's storage layout + // They are automatically initialized to default values (0, empty mappings, etc.) + balances: StorageMapping, + allowances: StorageMapping<(Address, Address), U256>, + total_supply: StorageU256, + // ... any additional state used by the implementation +} +``` + +### Important Notes About Storage Initialization + +The implementation storage fields in the proxy ***do not need to be explicitly set*** - they are automatically initialized to their default values when the proxy contract is deployed. + +By structuring your proxy’s storage this way, you ensure that both the proxy and the implementation contract are always in sync regarding where each piece of data is stored, preventing storage collisions and upgrade issues. + +## Best Practices + +1. ***Always validate implementation addresses***: Check that the implementation is not the zero address and is a valid contract. +2. ***Use proper access control***: Implement admin functions to control who can upgrade the implementation. +3. ***Test thoroughly***: Proxy patterns can be complex, so comprehensive testing is essential. +4. ***Consider upgrade safety***: Ensure that storage layouts are compatible between implementations. +5. ***Document storage layout***: Clearly document the storage layout to prevent future conflicts. +6. ***Use events***: Emit events when the implementation is upgraded for transparency. + +## Common Pitfalls + +* ***Storage collisions***: Ensure proxy and implementation storage don't conflict. +* ***Missing validation***: Always validate implementation addresses. +* ***Incorrect delegatecall usage***: The proxy must use [`delegate_call`](https://docs.rs/stylus-sdk/0.9.0/stylus_sdk/call/fn.delegate_call.html), not [`call`](https://docs.rs/stylus-sdk/0.9.0/stylus_sdk/call/fn.call.html). +* ***Forgetting to implement IProxy***: The trait must be implemented for the fallback to work. +## Working Example + +A complete working example of the basic proxy pattern can be found in the repository at `examples/proxy/`. This example demonstrates: + +* Minimal proxy implementation using `IProxy`. +* Integration with ERC-20 token contracts. +* Comprehensive test coverage. +* Proper error handling. + +## Related Patterns + +* [ERC-1967 Proxy](/contracts-stylus/erc1967): A standardized proxy pattern with specific storage slots. +* [Beacon Proxy](/contracts-stylus/beacon-proxy): Multiple proxies pointing to a single beacon contract for mass upgrades of the implementation contract address. +* [UUPS Proxy](/contracts-stylus/uups-proxy): The Universal Upgradeable Proxy Standard (UUPS) that is a minimal and gas-efficient pattern for upgradeable contracts. diff --git a/docs/content/contracts-stylus/tokens.mdx b/docs/content/contracts-stylus/tokens.mdx new file mode 100644 index 00000000..b7fa121f --- /dev/null +++ b/docs/content/contracts-stylus/tokens.mdx @@ -0,0 +1,31 @@ +--- +title: 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 + +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 + +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 [fungibility](https://en.wikipedia.org/wiki/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, ERC-721 or ERC-1155 token standards, and that's why you're here. Head to our specialized guides to learn more about these: + +* [ERC-20](/contracts-stylus/erc20): the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. +* [ERC-721](/contracts-stylus/erc721): the de-facto solution for non-fungible tokens, often used for collectibles and games. +* [ERC-1155](/contracts-stylus/erc1155): 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. diff --git a/docs/content/contracts-stylus/utilities.mdx b/docs/content/contracts-stylus/utilities.mdx new file mode 100644 index 00000000..4408895f --- /dev/null +++ b/docs/content/contracts-stylus/utilities.mdx @@ -0,0 +1,50 @@ +--- +title: Utilities +--- + +The OpenZeppelin Stylus Contracts provides a ton of useful utilities that you can use in your project. +For a complete list, check out the [API Reference](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/index.html). +Here are some of the more popular ones. + +## Introspection + +It's frequently helpful to know whether a contract supports an interface you'd like to use. +[`ERC-165`](https://eips.ethereum.org/EIPS/eip-165) is a standard that helps do runtime interface detection. +Contracts for Stylus provides helpers for implementing ERC-165 in your contracts: + +* [`IERC165`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html) — this is the ERC-165 trait that defines [`supportsInterface`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface). In order to implement ERC-165 interface detection, you should manually expose [`supportsInterface`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/introspection/erc165/trait.IErc165.html#tymethod.supports_interface) function in your contract. + +```rust +#[entrypoint] +#[storage] +struct Erc721Example { + erc721: Erc721, +} + +#[public] +#[implements(IErc721, IErc165)] +impl Erc721Example { + // ... +} + +#[public] +impl IErc165 for Erc721Example { + fn supports_interface(&self, interface_id: B32) -> bool { + self.erc721.supports_interface(interface_id) + } +} + +#[public] +impl IErc721 for Erc721Example { + // ... +} +``` + +## Structures + +Some use cases require more powerful data structures than arrays and mappings offered natively in `alloy` and the Stylus SDK. +Contracts for Stylus provides these libraries for enhanced data structure management: + +* [`BitMaps`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/structs/bitmap/index.html): Store packed booleans in storage. +* [`Checkpoints`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/structs/checkpoints/index.html): Checkpoint values with built-in lookups. +* [`EnumerableSets`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/utils/structs/enumerable_set/index.html): Contract for managing sets of many primitive types. diff --git a/docs/content/contracts-stylus/uups-proxy.mdx b/docs/content/contracts-stylus/uups-proxy.mdx new file mode 100644 index 00000000..ea675aa1 --- /dev/null +++ b/docs/content/contracts-stylus/uups-proxy.mdx @@ -0,0 +1,286 @@ +--- +title: UUPS Proxy +--- +The Universal Upgradeable Proxy Standard (UUPS) is a minimal and gas-efficient +pattern for upgradeable contracts. Defined in the [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822) +specification, UUPS delegates upgrade logic to the implementation contract +itself — reducing proxy complexity and deployment costs. + +The OpenZeppelin Stylus Contracts provide a full implementation of the UUPS pattern via `UUPSUpgradeable` and `Erc1967Proxy`. + +## Overview + +UUPS uses the ERC-1967 proxy architecture to separate upgrade logic from proxy behavior. Instead of maintaining upgradeability in the proxy, all upgrade control is implemented within the logic contract. + +Key components: + +* ***Proxy Contract (`Erc1967Proxy`)*** — delegates calls via `delegate_call`. +* ***Implementation Contract*** — contains application logic and upgrade control. +* ***Upgrade Functions*** — reside in the implementation, not the proxy. + +In Solidity, upgrade safety often relies on an `immutable` self-address and context checks. Stylus, however, uses a small adaptation that is covered below. + +## Context Detection in Stylus + +Stylus does not currently support the `immutable` keyword. Instead of storing `__self = address(this)`, the implementation uses a dedicated boolean flag in a unique storage slot to distinguish direct vs delegated (proxy) execution contexts. + +```rust +// boolean flag stored in a unique slot +logic_flag: bool; +``` + +The implementation’s constructor sets `logic_flag = true` in the implementation’s own storage. When code runs via a proxy (delegatecall), the proxy’s storage does not contain this flag, so reads as `false`. This enables `only_proxy()` to check for delegated execution without relying on an address sentinel. + +## Initialization + +Stylus requires explicit initialization for both the implementation and the proxy: + +* ***Constructor*** – called exactly once on deployment of the logic (implementation) contract. This sets the implementation-only `logic_flag` used for context checks. +* ***Version setup (`set_version`)*** – called via the proxy (delegatecall) to write the logic’s `VERSION_NUMBER` into the proxy’s storage. This aligns the proxy’s version with the logic and enables upgrade paths guarded by `only_proxy()`. + + + + +Call `set_version()` is triggered automatically via `upgrade_to_and_call()`. +Devs are only required to manually call `set_version()` when deploying the proxy. + + + +***Trade-offs:*** + +* ***Storage Cost:*** Requires one additional storage slot. +* ***Runtime Safety:*** Maintains the same guarantees as the Solidity version. +* ***Gas Impact:*** One-time cost during initialization; negligible runtime overhead. + +## Why UUPS? + +* ***Gas Efficient*** — Upgrades are handled within the logic contract. +* ***Secure*** — Authorization and validation are managed in one place. +* ***Standardized*** — Conforms to ERC-1822 and ERC-1967. +* ***Flexible*** — Upgrade logic can include custom access control and validation. +* ***Safe by Design*** — Uses dedicated ERC-1967 slots to prevent storage collisions. + +## How It Works + +1. Deploy `Erc1967Proxy` with an initial implementation and encoded `set_version` data. +2. Proxy delegates all calls to the implementation contract via `delegate_call`. +3. Implementation exposes `upgrade_to_and_call`, guarded by access control (e.g. `Ownable`). +4. Upgrades validate the new implementation using `proxiable_uuid()`. +5. Stylus uses a two-step pattern: `constructor` on logic deployment, then `set_version` during proxy setup. + +## Implementing a UUPS Contract + +Minimal example with `Ownable`, `UUPSUpgradeable`, and `Erc20` logic: + +```rust +#[entrypoint] +#[storage] +struct MyUUPSContract { + erc20: Erc20, + ownable: Ownable, + uups: UUPSUpgradeable, +} + +#[public] +#[implements(IErc20, IUUPSUpgradeable, IErc1822Proxiable, IOwnable)] +impl MyUUPSContract { + // Accepting owner here only to enable invoking functions directly on the + // UUPS + #[constructor] + fn constructor(&mut self, owner: Address) -> Result<(), Error> { + self.uups.constructor(); + self.ownable.constructor(owner)?; + Ok(()) + } + + fn mint(&mut self, to: Address, value: U256) -> Result<(), erc20::Error> { + self.erc20._mint(to, value) + } + + /// Initializes the contract. + fn initialize(&mut self, owner: Address) -> Result<(), Error> { + self.uups.set_version()?; + self.ownable.constructor(owner)?; + Ok(()) + } + + fn set_version(&mut self) -> Result<(), Error> { + Ok(self.uups.set_version()?) + } + + fn get_version(&self) -> U32 { + self.uups.get_version() + } +} + +#[public] +impl IUUPSUpgradeable for MyUUPSContract { + #[selector(name = "UPGRADE_INTERFACE_VERSION")] + fn upgrade_interface_version(&self) -> String { + self.uups.upgrade_interface_version() + } + + #[payable] + fn upgrade_to_and_call( + &mut self, + new_implementation: Address, + data: Bytes, + ) -> Result<(), Vec> { + // Make sure to provide upgrade authorization in your implementation + // contract. + self.ownable.only_owner()?; + self.uups.upgrade_to_and_call(new_implementation, data)?; + Ok(()) + } +} + +#[public] +impl IErc1822Proxiable for MyUUPSContract { + #[selector(name = "proxiableUUID")] + fn proxiable_uuid(&self) -> Result> { + self.uups.proxiable_uuid() + } +} +``` + +## Implementing the Proxy + +A simple UUPS-compatible proxy using ERC-1967: + +```rust +#[entrypoint] +#[storage] +struct MyUUPSProxy { + proxy: Erc1967Proxy, +} + +#[public] +impl MyUUPSProxy { + #[constructor] + fn constructor(&mut self, implementation: Address, data: Bytes) -> Result<(), erc1967::utils::Error> { + self.proxy.constructor(implementation, &data) + } + + fn implementation(&self) -> Result> { + self.proxy.implementation() + } + + #[fallback] + fn fallback(&mut self, calldata: &[u8]) -> ArbResult { + unsafe { self.proxy.do_fallback(calldata) } + } +} + +unsafe impl IProxy for MyUUPSProxy { + fn implementation(&self) -> Result> { + self.proxy.implementation() + } +} +``` + +## Upgrade Safety + +### 1. Access Control + +Upgrades must be restricted to trusted accounts, e.g. via `only_owner`: + +```rust +self.ownable.only_owner()?; +``` + +### 2. Proxy Context Enforcement + +Ensures upgrade calls come from a delegate call: + +```rust +self.uups.only_proxy()?; // Reverts if not called via proxy +``` + +***Explanation:*** +`only_proxy()` checks that execution is delegated (not direct), the caller is an ERC-1967 proxy (implementation slot is non-zero), and the proxy-stored version equals the logic’s `VERSION_NUMBER`. + +### 3. Proxiable UUID Validation + +Guarantees compatibility with UUPS: + +```rust +self.uups.proxiable_uuid()? == IMPLEMENTATION_SLOT; +``` + +## Initialization + +The UUPS proxy supports initialization data that is delegated to the implementation on deployment. This is typically used to invoke `set_version` first, and optionally invoke your own initialization routines (e.g., ownership or token supply setup) if needed. + +```rust +let data = IMyContract::setVersionCall {}.abi_encode(); +MyUUPSProxy::deploy(implementation_addr, data.into()); +``` + +### ⚠️ Initialization Must Be Explicit (Your Contract State) + +If your contract needs additional initialization beyond `set_version()` (e.g., ownership, token supply), expose a properly designed initialization function and protect it appropriately (e.g., single-use guard or access control). Failing to do so can lead to: +* Orphaned contracts with no owner. +* Uninitialized token supply or core state. +* Denial of future upgrades if your own guards are misused. + +```rust +/// Optional contract initialization (example). +fn init_contract_state(&mut self, owner: Address) -> Result<(), Vec> { + self.ownable.constructor(owner)?; + + /// other initialization logic. + + self.uups.set_version()?; + + Ok(()) +} +``` + + +If you expose additional initialization functions, ensure they are protected from re-execution after the proxy is live. + + +## Initializing the Proxy + +Initialization data is typically a call to the implementation’s `set_version` function: + +```rust +let data = IMyContract::setVersionCall {}.abi_encode(); +MyUUPSProxy::deploy(implementation_addr, data.into()); +``` + +This setup call is run via `delegate_call` during proxy deployment. + +## Security Best Practices + +* Restrict upgrade access (e.g. `only_owner`). +* Validate all upgrade targets. +* Test upgrades across versions. +* Monitor upgrade events (`Upgraded`). +* Use empty data unless initialization is needed. +* Ensure new implementations return the correct `proxiable UUID`. +* ***Enforce proxy context checks*** — `only_proxy()` ensures upgrades cannot be called directly on the implementation. + +## Common Pitfalls + +* Forgetting access control. +* Direct calls to upgrade logic (not via proxy). +* Missing `proxiable UUID` validation. +* Changing storage layout without planning. +* Sending ETH to constructor without data (will revert). +* The `VERSION_NUMBER` is not increased to the higher value. + +## Use Cases + +* Upgradeable tokens standards (e.g. ERC-20, ERC-721, ERC-1155). +* Modular DeFi protocols. +* DAO frameworks. +* NFT marketplaces. +* Access control registries. +* Cross-chain bridges. + +## Related + +* [ERC-1967 Proxy](/contracts-stylus/erc1967) +* [Beacon Proxy](/contracts-stylus/beacon-proxy) +* [Basic Proxy](/contracts-stylus/proxy) diff --git a/docs/content/contracts-stylus/vesting-wallet.mdx b/docs/content/contracts-stylus/vesting-wallet.mdx new file mode 100644 index 00000000..556c26c1 --- /dev/null +++ b/docs/content/contracts-stylus/vesting-wallet.mdx @@ -0,0 +1,170 @@ +--- +title: VestingWallet +--- + +A vesting wallet is an ownable contract that can receive native currency and +ERC-20 tokens, and release these assets to the wallet owner, also referred to as +"beneficiary", according to a vesting schedule. + +Any assets transferred to this contract will follow the vesting schedule as if +they were locked from the beginning. Consequently, if the vesting has already +started, any amount of tokens sent to this contract will (at least partly) be +immediately releasable. + +By setting the duration to 0, one can configure this contract to behave like +an asset timelock that hold tokens for a beneficiary until a specified time. + + + + +Since the wallet is [Ownable](/contracts-stylus/access-control#ownership-and-ownable), +and ownership can be transferred, it is possible to sell unvested tokens. +Preventing this in a smart contract is difficult, considering that: 1) a +beneficiary address could be a counterfactually deployed contract, 2) +there is likely to be a migration path for EOAs to become contracts in the near +future. + + + + + + +When using this contract with any token whose balance is adjusted automatically +(i.e. a rebase token), make sure to account the supply/balance adjustment in the +vesting schedule to ensure the vested amount is as intended. + + + + + + +Chains with support for native ERC20s may allow the vesting wallet to withdraw +the underlying asset as both an ERC20 and as native currency. For example, if +chain C supports token A and the wallet gets deposited 100 A, then at 50% of +the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as +native currency (totaling 75 A). Consider disabling one of the withdrawal methods. + + + +## Usage + +In order to make [`VestingWallet`](https://docs.rs/openzeppelin-stylus/0.3.0/openzeppelin_stylus/finance/vesting_wallet/index.html) methods “external” so that other contracts can call them, you need to implement them by yourself for your final contract as follows: + +```rust +use openzeppelin_stylus::finance::vesting_wallet::{ + self, IVestingWallet, VestingWallet, +}; + +#[entrypoint] +#[storage] +struct VestingWalletExample { + vesting_wallet: VestingWallet, +} + +#[public] +#[implements(IVestingWallet)] +impl VestingWalletExample { + #[constructor] + fn constructor( + &mut self, + beneficiary: Address, + start_timestamp: U64, + duration_seconds: U64, + ) -> Result<(), vesting_wallet::Error> { + self.vesting_wallet.constructor( + beneficiary, + start_timestamp, + duration_seconds, + ) + } + + #[receive] + fn receive(&mut self) -> Result<(), Vec> { + self.vesting_wallet.receive() + } +} + +#[public] +impl IVestingWallet for VestingWalletExample { + type Error = vesting_wallet::Error; + + fn owner(&self) -> Address { + self.vesting_wallet.owner() + } + + fn transfer_ownership( + &mut self, + new_owner: Address, + ) -> Result<(), Self::Error> { + self.vesting_wallet.transfer_ownership(new_owner) + } + + fn renounce_ownership( + &mut self, + ) -> Result<(), Self::Error> { + self.vesting_wallet.renounce_ownership() + } + + fn start(&self) -> U256 { + self.vesting_wallet.start() + } + + fn duration(&self) -> U256 { + self.vesting_wallet.duration() + } + + fn end(&self) -> U256 { + self.vesting_wallet.end() + } + + #[selector(name = "released")] + fn released_eth(&self) -> U256 { + self.vesting_wallet.released_eth() + } + + #[selector(name = "released")] + fn released_erc20(&self, token: Address) -> U256 { + self.vesting_wallet.released_erc20(token) + } + + #[selector(name = "releasable")] + fn releasable_eth(&self) -> U256 { + self.vesting_wallet.releasable_eth() + } + + #[selector(name = "releasable")] + fn releasable_erc20( + &mut self, + token: Address, + ) -> Result { + self.vesting_wallet.releasable_erc20(token) + } + + #[selector(name = "release")] + fn release_eth(&mut self) -> Result<(), Self::Error> { + self.vesting_wallet.release_eth() + } + + #[selector(name = "release")] + fn release_erc20( + &mut self, + token: Address, + ) -> Result<(), Self::Error> { + self.vesting_wallet.release_erc20(token) + } + + #[selector(name = "vestedAmount")] + fn vested_amount_eth(&self, timestamp: u64) -> U256 { + self.vesting_wallet.vested_amount_eth(timestamp) + } + + #[selector(name = "vestedAmount")] + fn vested_amount_erc20( + &mut self, + token: Address, + timestamp: u64, + ) -> Result { + self.vesting_wallet.vested_amount_erc20(token, timestamp) + } +} +``` diff --git a/docs/content/contracts/3.x/access-control.mdx b/docs/content/contracts/3.x/access-control.mdx new file mode 100644 index 00000000..58de3473 --- /dev/null +++ b/docs/content/contracts/3.x/access-control.mdx @@ -0,0 +1,222 @@ +--- +title: 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 [steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7). + +## 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 provides [`Ownable`](/contracts/3.x/api/access#Ownable) for implementing ownership in your contracts. + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyContract is Ownable + function normalThing() public { + // anyone can call this normalThing() + + + function specialThing() public onlyOwner + // only the owner can call specialThing()! + +} +``` + +By default, the [`owner`](/contracts/3.x/api/access#Ownable-owner--) of an `Ownable` contract is the account that deployed it, which is usually exactly what you want. + +Ownable also lets you: + +* [`transferOwnership`](/contracts/3.x/api/access#Ownable-transferOwnership-address-) from the owner account to a new one, and +* [`renounceOwnership`](/contracts/3.x/api/access#Ownable-renounceOwnership--) for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable! + + +Note that **a contract can also be the owner of another one**! This opens the door to using, for example, a [Gnosis Multisig](https://github.com/gnosis/MultiSigWallet) or [Gnosis Safe](https://safe.gnosis.io), an [Aragon DAO](https://aragon.org), an [ERC725/uPort](https://www.uport.me) identity contract, 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 [MakerDAO](https://makerdao.com), use systems similar to this one. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. [_Role-Based Access Control (RBAC)_](https://en.wikipedia.org/wiki/Role-based_access_control) 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`. 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 `AccessControl` + +OpenZeppelin Contracts provides [`AccessControl`](/contracts/3.x/api/access#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 [`ERC20` token](/contracts/3.x/tokens#ERC20) to define a 'minter' role, which allows accounts that have it create new tokens: + +```solidity +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20, AccessControl + // Create a new role identifier for the minter role + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor(address minter) public ERC20("MyToken", "TKN") { + // Grant the minter role to a specified account + _setupRole(MINTER_ROLE, minter); + + + function mint(address to, uint256 amount) public + // Check that the calling account has the minter role + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + _mint(to, amount); + +} +``` + + +Make sure you fully understand how [`AccessControl`](/contracts/3.x/api/access#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 ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens: + +```solidity +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20, AccessControl + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor(address minter, address burner) public ERC20("MyToken", "TKN") { + _setupRole(MINTER_ROLE, minter); + _setupRole(BURNER_ROLE, burner); + + + function mint(address to, uint256 amount) public + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + _mint(to, amount); + + + function burn(address from, uint256 amount) public + require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner"); + _burn(from, amount); + +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 Roles + +The ERC20 token example above uses `_setupRole`, 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. + +Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: + +```solidity +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20, AccessControl + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor() public ERC20("MyToken", "TKN") { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _setupRole(DEFAULT_ADMIN_ROLE, msg.sender); + + + function mint(address to, uint256 amount) public + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + _mint(to, amount); + + + function burn(address from, uint256 amount) public + require(hasRole(BURNER_ROLE, msg.sender), "Caller is not a burner"); + _burn(from, amount); + +} +``` + +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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), 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 + +Because accounts might [grant and revoke roles](#granting-and-revoking-roles) dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows to prove 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. + +Under the hood, `AccessControl` uses `EnumerableSet`, a more powerful variant of Solidity’s `mapping` type, which allows for key enumeration. `getRoleMemberCount` can be used to retrieve the number of accounts that have a particular role, and `getRoleMember` can then be called to get the address of each of these accounts. + +```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 [`Ownable`](/contracts/3.x/api/access#Ownable) and [`AccessControl`](/contracts/3.x/api/access#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 [`TimelockControler`](/contracts/3.x/api/access#TimelockController) is addressing. + +The [`TimelockControler`](/contracts/3.x/api/access#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 `TimelockControler` + +By default, the address that deployed the [`TimelockControler`](/contracts/3.x/api/access#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 [`TimelockControler`](/contracts/3.x/api/access#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 [`AccessControl`](/contracts/3.x/api/access#AccessControl) interface and the `bytes32` values for each role are accessible through the `ADMIN_ROLE`, `PROPOSER_ROLE` and `EXECUTOR_ROLE` constants. + +There is an additional feature built on top of `AccessControl`: giving the proposer or executor role to `address(0)` opens access to anyone. This feature, while potentially useful for testing and in some cases for the executor role, is dangerous and 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 enforce a delay on timelock maintenance operations. + + +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. + + +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 [`TimelockControler`](/contracts/3.x/api/access#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 [`getMinDelay`](/contracts/3.x/api/access#TimelockController-getMinDelay--) method) can be updated by calling the [`updateDelay`](/contracts/3.x/api/access#TimelockController-updateDelay-uint256-) 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. diff --git a/docs/content/contracts/3.x/api/GSN.mdx b/docs/content/contracts/3.x/api/GSN.mdx new file mode 100644 index 00000000..f3bd2b2b --- /dev/null +++ b/docs/content/contracts/3.x/api/GSN.mdx @@ -0,0 +1,648 @@ +--- +title: Gas Station Network (GSN) +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/gsn + + +This set of contracts provide all the tools required to make a contract callable via the [Gas Station Network](https://gsn.openzeppelin.com). + + +If you’re new to the GSN, head over to our [overview of the system](/contracts/5.x/learn/sending-gasless-transactions) and basic guide to [creating a GSN-capable contract](/contracts/3.x/gsn). + + +The core contract a recipient must inherit from is GSNRecipient: it includes all necessary interfaces, as well as some helper methods to make interacting with the GSN easier. + +Utilities to make writing [GSN strategies](/contracts/3.x/gsn-strategies) easy are available in GSNRecipient, or you can simply use one of our pre-made strategies: + +* GSNRecipientERC20Fee charges the end user for gas costs in an application-specific [ERC20 token](/contracts/3.x/tokens#ERC20) +* GSNRecipientSignature accepts all relayed calls that have been signed by a trusted third party (e.g. a private key in a backend) + +You can also take a look at the two contract interfaces that make up the GSN protocol: IRelayRecipient and IRelayHub, but you won’t need to use those directly. + +## Recipient + +### ``GSNRecipient`` + +Base GSN recipient contract: includes the IRelayRecipient interface +and enables GSN support on all contracts in the inheritance tree. + + +This contract is abstract. The functions IRelayRecipient-acceptRelayedCall, + _preRelayedCall, and _postRelayedCall are not implemented and must be +provided by derived contracts. See the +[GSN strategies](/contracts/3.x/gsn-strategies#gsn-strategies) for more +information on how to use the pre-built GSNRecipientSignature and +GSNRecipientERC20Fee, or how to write your own. + + +**Functions** + +* [`getHubAddr()`](#GSNRecipient-getHubAddr) +* [`_upgradeRelayHub(newRelayHub)`](#GSNRecipient-_upgradeRelayHub-address-) +* [`relayHubVersion()`](#GSNRecipient-relayHubVersion) +* [`_withdrawDeposits(amount, payee)`](#GSNRecipient-_withdrawDeposits-uint256-address-payable-) +* [`_msgSender()`](#GSNRecipient-_msgSender) +* [`_msgData()`](#GSNRecipient-_msgData) +* [`preRelayedCall(context)`](#GSNRecipient-preRelayedCall-bytes-) +* [`_preRelayedCall(context)`](#GSNRecipient-_preRelayedCall-bytes-) +* [`postRelayedCall(context, success, actualCharge, preRetVal)`](#GSNRecipient-postRelayedCall-bytes-bool-uint256-bytes32-) +* [`_postRelayedCall(context, success, actualCharge, preRetVal)`](#GSNRecipient-_postRelayedCall-bytes-bool-uint256-bytes32-) +* [`_approveRelayedCall()`](#GSNRecipient-_approveRelayedCall) +* [`_approveRelayedCall(context)`](#GSNRecipient-_approveRelayedCall-bytes-) +* [`_rejectRelayedCall(errorCode)`](#GSNRecipient-_rejectRelayedCall-uint256-) +* [`_computeCharge(gas, gasPrice, serviceFee)`](#GSNRecipient-_computeCharge-uint256-uint256-uint256-) + +**IRelayRecipient** + +* [`acceptRelayedCall(relay, from, encodedFunction, transactionFee, gasPrice, gasLimit, nonce, approvalData, maxPossibleCharge)`](#IRelayRecipient-acceptRelayedCall-address-address-bytes-uint256-uint256-uint256-uint256-bytes-uint256-) + +**Events** + +* [`RelayHubChanged(oldRelayHub, newRelayHub)`](#GSNRecipient-RelayHubChanged-address-address-) + + + +#### `getHubAddr() → address *public*` + +Returns the address of the IRelayHub contract for this recipient. + + + +#### `_upgradeRelayHub(address newRelayHub) *internal*` + +Switches to a new IRelayHub instance. This method is added for future-proofing: there’s no reason to not +use the default instance. + + +After upgrading, the GSNRecipient will no longer be able to receive relayed calls from the old +IRelayHub instance. Additionally, all funds should be previously withdrawn via _withdrawDeposits. + + + + +#### `relayHubVersion() → string *public*` + +Returns the version string of the IRelayHub for which this recipient implementation was built. If +_upgradeRelayHub is used, the new IRelayHub instance should be compatible with this version. + + + +#### `_withdrawDeposits(uint256 amount, address payable payee) *internal*` + +Withdraws the recipient’s deposits in `RelayHub`. + +Derived contracts should expose this in an external interface with proper access control. + + + +#### `_msgSender() → address payable *internal*` + +Replacement for msg.sender. Returns the actual sender of a transaction: msg.sender for regular transactions, +and the end-user for GSN relayed calls (where msg.sender is actually `RelayHub`). + + +Contracts derived from GSNRecipient should never use `msg.sender`, and use _msgSender instead. + + + + +#### `_msgData() → bytes *internal*` + +Replacement for msg.data. Returns the actual calldata of a transaction: msg.data for regular transactions, +and a reduced version for GSN relayed calls (where msg.data contains additional information). + + +Contracts derived from GSNRecipient should never use `msg.data`, and use _msgData instead. + + + + +#### `preRelayedCall(bytes context) → bytes32 *public*` + +See `IRelayRecipient.preRelayedCall`. + +This function should not be overridden directly, use `_preRelayedCall` instead. + +* Requirements: + * the caller must be the `RelayHub` contract. + + + +#### `_preRelayedCall(bytes context) → bytes32 *internal*` + +See `IRelayRecipient.preRelayedCall`. + +Called by `GSNRecipient.preRelayedCall`, which asserts the caller is the `RelayHub` contract. Derived contracts +must implement this function with any relayed-call preprocessing they may wish to do. + + + +#### `postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal) *public*` + +See `IRelayRecipient.postRelayedCall`. + +This function should not be overridden directly, use `_postRelayedCall` instead. + +* Requirements: + * the caller must be the `RelayHub` contract. + + + +#### `_postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal) *internal*` + +See `IRelayRecipient.postRelayedCall`. + +Called by `GSNRecipient.postRelayedCall`, which asserts the caller is the `RelayHub` contract. Derived contracts +must implement this function with any relayed-call postprocessing they may wish to do. + + + +#### `_approveRelayedCall() → uint256, bytes *internal*` + +Return this in acceptRelayedCall to proceed with the execution of a relayed call. Note that this contract +will be charged a fee by RelayHub + + + +#### `_approveRelayedCall(bytes context) → uint256, bytes *internal*` + +See `GSNRecipient._approveRelayedCall`. + +This overload forwards `context` to _preRelayedCall and _postRelayedCall. + + + +#### `_rejectRelayedCall(uint256 errorCode) → uint256, bytes *internal*` + +Return this in acceptRelayedCall to impede execution of a relayed call. No fees will be charged. + + + +#### `_computeCharge(uint256 gas, uint256 gasPrice, uint256 serviceFee) → uint256 *internal*` + + + +#### `RelayHubChanged(address oldRelayHub, address newRelayHub) *event*` + +Emitted when a contract changes its IRelayHub contract to a new one. + +## Strategies + +### ``GSNRecipientSignature`` + +A [GSN strategy](/contracts/3.x/gsn-strategies#gsn-strategies) that allows relayed transactions through when they are +accompanied by the signature of a trusted signer. The intent is for this signature to be generated by a server that +performs validations off-chain. Note that nothing is charged to the user in this scheme. Thus, the server should make +sure to account for this in their economic and threat model. + +**Functions** + +* [`constructor(trustedSigner)`](#GSNRecipientSignature-constructor-address-) +* [`acceptRelayedCall(relay, from, encodedFunction, transactionFee, gasPrice, gasLimit, nonce, approvalData, _)`](#GSNRecipientSignature-acceptRelayedCall-address-address-bytes-uint256-uint256-uint256-uint256-bytes-uint256-) +* [`_preRelayedCall(_)`](#GSNRecipientSignature-_preRelayedCall-bytes-) +* [`_postRelayedCall(_, _, _, _)`](#GSNRecipientSignature-_postRelayedCall-bytes-bool-uint256-bytes32-) + +**GSNRecipient** + +* [`getHubAddr()`](#GSNRecipient-getHubAddr) +* [`_upgradeRelayHub(newRelayHub)`](#GSNRecipient-_upgradeRelayHub-address-) +* [`relayHubVersion()`](#GSNRecipient-relayHubVersion) +* [`_withdrawDeposits(amount, payee)`](#GSNRecipient-_withdrawDeposits-uint256-address-payable-) +* [`_msgSender()`](#GSNRecipient-_msgSender) +* [`_msgData()`](#GSNRecipient-_msgData) +* [`preRelayedCall(context)`](#GSNRecipient-preRelayedCall-bytes-) +* [`postRelayedCall(context, success, actualCharge, preRetVal)`](#GSNRecipient-postRelayedCall-bytes-bool-uint256-bytes32-) +* [`_approveRelayedCall()`](#GSNRecipient-_approveRelayedCall) +* [`_approveRelayedCall(context)`](#GSNRecipient-_approveRelayedCall-bytes-) +* [`_rejectRelayedCall(errorCode)`](#GSNRecipient-_rejectRelayedCall-uint256-) +* [`_computeCharge(gas, gasPrice, serviceFee)`](#GSNRecipient-_computeCharge-uint256-uint256-uint256-) + +**Events** + +**GSNRecipient** + +* [`RelayHubChanged(oldRelayHub, newRelayHub)`](#GSNRecipient-RelayHubChanged-address-address-) + + + +#### `constructor(address trustedSigner) *public*` + +Sets the trusted signer that is going to be producing signatures to approve relayed calls. + + + +#### `acceptRelayedCall(address relay, address from, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes approvalData, uint256) → uint256, bytes *public*` + +Ensures that only transactions with a trusted signature can be relayed through the GSN. + + + +#### `_preRelayedCall(bytes) → bytes32 *internal*` + + + +#### `_postRelayedCall(bytes, bool, uint256, bytes32) *internal*` + +### ``GSNRecipientERC20Fee`` + +A [GSN strategy](/contracts/3.x/gsn-strategies#gsn-strategies) that charges transaction fees in a special purpose ERC20 +token, which we refer to as the gas payment token. The amount charged is exactly the amount of Ether charged to the +recipient. This means that the token is essentially pegged to the value of Ether. + +The distribution strategy of the gas payment token to users is not defined by this contract. It’s a mintable token +whose only minter is the recipient, so the strategy must be implemented in a derived contract, making use of the +internal _mint function. + +**Functions** + +* [`constructor(name, symbol)`](#GSNRecipientERC20Fee-constructor-string-string-) +* [`token()`](#GSNRecipientERC20Fee-token) +* [`_mint(account, amount)`](#GSNRecipientERC20Fee-_mint-address-uint256-) +* [`acceptRelayedCall(_, from, _, transactionFee, gasPrice, _, _, _, maxPossibleCharge)`](#GSNRecipientERC20Fee-acceptRelayedCall-address-address-bytes-uint256-uint256-uint256-uint256-bytes-uint256-) +* [`_preRelayedCall(context)`](#GSNRecipientERC20Fee-_preRelayedCall-bytes-) +* [`_postRelayedCall(context, _, actualCharge, _)`](#GSNRecipientERC20Fee-_postRelayedCall-bytes-bool-uint256-bytes32-) + +**GSNRecipient** + +* [`getHubAddr()`](#GSNRecipient-getHubAddr) +* [`_upgradeRelayHub(newRelayHub)`](#GSNRecipient-_upgradeRelayHub-address-) +* [`relayHubVersion()`](#GSNRecipient-relayHubVersion) +* [`_withdrawDeposits(amount, payee)`](#GSNRecipient-_withdrawDeposits-uint256-address-payable-) +* [`_msgSender()`](#GSNRecipient-_msgSender) +* [`_msgData()`](#GSNRecipient-_msgData) +* [`preRelayedCall(context)`](#GSNRecipient-preRelayedCall-bytes-) +* [`postRelayedCall(context, success, actualCharge, preRetVal)`](#GSNRecipient-postRelayedCall-bytes-bool-uint256-bytes32-) +* [`_approveRelayedCall()`](#GSNRecipient-_approveRelayedCall) +* [`_approveRelayedCall(context)`](#GSNRecipient-_approveRelayedCall-bytes-) +* [`_rejectRelayedCall(errorCode)`](#GSNRecipient-_rejectRelayedCall-uint256-) +* [`_computeCharge(gas, gasPrice, serviceFee)`](#GSNRecipient-_computeCharge-uint256-uint256-uint256-) + +**Events** + +**GSNRecipient** + +* [`RelayHubChanged(oldRelayHub, newRelayHub)`](#GSNRecipient-RelayHubChanged-address-address-) + + + +#### `constructor(string name, string symbol) *public*` + +The arguments to the constructor are the details that the gas payment token will have: `name` and `symbol`. `decimals` is hard-coded to 18. + + + +#### `token() → contract __unstable__ERC20Owned *public*` + +Returns the gas payment token. + + + +#### `_mint(address account, uint256 amount) *internal*` + +Internal function that mints the gas payment token. Derived contracts should expose this function in their public API, with proper access control mechanisms. + + + +#### `acceptRelayedCall(address, address from, bytes, uint256 transactionFee, uint256 gasPrice, uint256, uint256, bytes, uint256 maxPossibleCharge) → uint256, bytes *public*` + +Ensures that only users with enough gas payment token balance can have transactions relayed through the GSN. + + + +#### `_preRelayedCall(bytes context) → bytes32 *internal*` + +Implements the precharge to the user. The maximum possible charge (depending on gas limit, gas price, and +fee) will be deducted from the user balance of gas payment token. Note that this is an overestimation of the +actual charge, necessary because we cannot predict how much gas the execution will actually need. The remainder +is returned to the user in _postRelayedCall. + + + +#### `_postRelayedCall(bytes context, bool, uint256 actualCharge, bytes32) *internal*` + +Returns to the user the extra amount that was previously charged, once the actual execution cost is known. + +## Protocol + +### ``IRelayRecipient`` + +Base interface for a contract that will be called via the GSN from IRelayHub. + + +You don’t need to write an implementation yourself! Inherit from GSNRecipient instead. + + +**Functions** + +* [`getHubAddr()`](#IRelayRecipient-getHubAddr) +* [`acceptRelayedCall(relay, from, encodedFunction, transactionFee, gasPrice, gasLimit, nonce, approvalData, maxPossibleCharge)`](#IRelayRecipient-acceptRelayedCall-address-address-bytes-uint256-uint256-uint256-uint256-bytes-uint256-) +* [`preRelayedCall(context)`](#IRelayRecipient-preRelayedCall-bytes-) +* [`postRelayedCall(context, success, actualCharge, preRetVal)`](#IRelayRecipient-postRelayedCall-bytes-bool-uint256-bytes32-) + + + +#### `getHubAddr() → address *external*` + +Returns the address of the IRelayHub instance this recipient interacts with. + + + +#### `acceptRelayedCall(address relay, address from, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes approvalData, uint256 maxPossibleCharge) → uint256, bytes *external*` + +Called by IRelayHub to validate if this recipient accepts being charged for a relayed call. Note that the +recipient will be charged regardless of the execution result of the relayed call (i.e. if it reverts or not). + +The relay request was originated by `from` and will be served by `relay`. `encodedFunction` is the relayed call +calldata, so its first four bytes are the function selector. The relayed call will be forwarded `gasLimit` gas, +and the transaction executed with a gas price of at least `gasPrice`. ``relay`’s fee is `transactionFee`, and the +recipient will be charged at most `maxPossibleCharge` (in wei). `nonce` is the sender’s (`from`) nonce for +replay attack protection in IRelayHub, and `approvalData` is a optional parameter that can be used to hold a signature +over all or some of the previous values. + +Returns a tuple, where the first value is used to indicate approval (0) or rejection (custom non-zero error code, +values 1 to 10 are reserved) and the second one is data to be passed to the other IRelayRecipient functions. + +acceptRelayedCall is called with 50k gas: if it runs out during execution, the request will be considered +rejected. A regular revert will also trigger a rejection. + + + +#### `preRelayedCall(bytes context) → bytes32 *external*` + +Called by IRelayHub on approved relay call requests, before the relayed call is executed. This allows to e.g. +pre-charge the sender of the transaction. + +`context` is the second value returned in the tuple by acceptRelayedCall. + +Returns a value to be passed to postRelayedCall. + +preRelayedCall is called with 100k gas: if it runs out during execution or otherwise reverts, the relayed call +will not be executed, but the recipient will still be charged for the transaction’s cost. + + + +#### `postRelayedCall(bytes context, bool success, uint256 actualCharge, bytes32 preRetVal) *external*` + +Called by IRelayHub on approved relay call requests, after the relayed call is executed. This allows to e.g. +charge the user for the relayed call costs, return any overcharges from preRelayedCall, or perform +contract-specific bookkeeping. + +`context` is the second value returned in the tuple by acceptRelayedCall. `success` is the execution status of +the relayed call. `actualCharge` is an estimate of how much the recipient will be charged for the transaction, +not including any gas used by postRelayedCall itself. `preRetVal` is preRelayedCall’s return value. + +postRelayedCall is called with 100k gas: if it runs out during execution or otherwise reverts, the relayed call +and the call to preRelayedCall will be reverted retroactively, but the recipient will still be charged for the +transaction’s cost. + +### ``IRelayHub`` + +Interface for `RelayHub`, the core contract of the GSN. Users should not need to interact with this contract +directly. + +See the [OpenZeppelin GSN helpers](https://github.com/OpenZeppelin/openzeppelin-gsn-helpers) for more information on +how to deploy an instance of `RelayHub` on your local test network. + +**Functions** + +* [`stake(relayaddr, unstakeDelay)`](#IRelayHub-stake-address-uint256-) +* [`registerRelay(transactionFee, url)`](#IRelayHub-registerRelay-uint256-string-) +* [`removeRelayByOwner(relay)`](#IRelayHub-removeRelayByOwner-address-) +* [`unstake(relay)`](#IRelayHub-unstake-address-) +* [`getRelay(relay)`](#IRelayHub-getRelay-address-) +* [`depositFor(target)`](#IRelayHub-depositFor-address-) +* [`balanceOf(target)`](#IRelayHub-balanceOf-address-) +* [`withdraw(amount, dest)`](#IRelayHub-withdraw-uint256-address-payable-) +* [`canRelay(relay, from, to, encodedFunction, transactionFee, gasPrice, gasLimit, nonce, signature, approvalData)`](#IRelayHub-canRelay-address-address-address-bytes-uint256-uint256-uint256-uint256-bytes-bytes-) +* [`relayCall(from, to, encodedFunction, transactionFee, gasPrice, gasLimit, nonce, signature, approvalData)`](#IRelayHub-relayCall-address-address-bytes-uint256-uint256-uint256-uint256-bytes-bytes-) +* [`requiredGas(relayedCallStipend)`](#IRelayHub-requiredGas-uint256-) +* [`maxPossibleCharge(relayedCallStipend, gasPrice, transactionFee)`](#IRelayHub-maxPossibleCharge-uint256-uint256-uint256-) +* [`penalizeRepeatedNonce(unsignedTx1, signature1, unsignedTx2, signature2)`](#IRelayHub-penalizeRepeatedNonce-bytes-bytes-bytes-bytes-) +* [`penalizeIllegalTransaction(unsignedTx, signature)`](#IRelayHub-penalizeIllegalTransaction-bytes-bytes-) +* [`getNonce(from)`](#IRelayHub-getNonce-address-) + +**Events** + +* [`Staked(relay, stake, unstakeDelay)`](#IRelayHub-Staked-address-uint256-uint256-) +* [`RelayAdded(relay, owner, transactionFee, stake, unstakeDelay, url)`](#IRelayHub-RelayAdded-address-address-uint256-uint256-uint256-string-) +* [`RelayRemoved(relay, unstakeTime)`](#IRelayHub-RelayRemoved-address-uint256-) +* [`Unstaked(relay, stake)`](#IRelayHub-Unstaked-address-uint256-) +* [`Deposited(recipient, from, amount)`](#IRelayHub-Deposited-address-address-uint256-) +* [`Withdrawn(account, dest, amount)`](#IRelayHub-Withdrawn-address-address-uint256-) +* [`CanRelayFailed(relay, from, to, selector, reason)`](#IRelayHub-CanRelayFailed-address-address-address-bytes4-uint256-) +* [`TransactionRelayed(relay, from, to, selector, status, charge)`](#IRelayHub-TransactionRelayed-address-address-address-bytes4-enum-IRelayHub-RelayCallStatus-uint256-) +* [`Penalized(relay, sender, amount)`](#IRelayHub-Penalized-address-address-uint256-) + + + +#### `stake(address relayaddr, uint256 unstakeDelay) *external*` + +Adds stake to a relay and sets its `unstakeDelay`. If the relay does not exist, it is created, and the caller +of this function becomes its owner. If the relay already exists, only the owner can call this function. A relay +cannot be its own owner. + +All Ether in this function call will be added to the relay’s stake. +Its unstake delay will be assigned to `unstakeDelay`, but the new value must be greater or equal to the current one. + +Emits a Staked event. + + + +#### `registerRelay(uint256 transactionFee, string url) *external*` + +Registers the caller as a relay. +The relay must be staked for, and not be a contract (i.e. this function must be called directly from an EOA). + +This function can be called multiple times, emitting new RelayAdded events. Note that the received +`transactionFee` is not enforced by relayCall. + +Emits a RelayAdded event. + + + +#### `removeRelayByOwner(address relay) *external*` + +Removes (deregisters) a relay. Unregistered (but staked for) relays can also be removed. + +Can only be called by the owner of the relay. After the relay’s `unstakeDelay` has elapsed, unstake will be +callable. + +Emits a RelayRemoved event. + + + +#### `unstake(address relay) *external*` + + + +#### `getRelay(address relay) → uint256 totalStake, uint256 unstakeDelay, uint256 unstakeTime, address payable owner, enum IRelayHub.RelayState state *external*` + +Returns a relay’s status. Note that relays can be deleted when unstaked or penalized, causing this function +to return an empty entry. + + + +#### `depositFor(address target) *external*` + +Deposits Ether for a contract, so that it can receive (and pay for) relayed transactions. + +Unused balance can only be withdrawn by the contract itself, by calling withdraw. + +Emits a Deposited event. + + + +#### `balanceOf(address target) → uint256 *external*` + +Returns an account’s deposits. These can be either a contract’s funds, or a relay owner’s revenue. + + + +#### `withdraw(uint256 amount, address payable dest) *external*` + + + +#### `canRelay(address relay, address from, address to, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes signature, bytes approvalData) → uint256 status, bytes recipientContext *external*` + +Checks if the `RelayHub` will accept a relayed operation. +Multiple things must be true for this to happen: + - all arguments must be signed for by the sender (`from`) + - the sender’s nonce must be the current one + - the recipient must accept this transaction (via acceptRelayedCall) + +Returns a `PreconditionCheck` value (`OK` when the transaction can be relayed), or a recipient-specific error +code if it returns one in acceptRelayedCall. + + + +#### `relayCall(address from, address to, bytes encodedFunction, uint256 transactionFee, uint256 gasPrice, uint256 gasLimit, uint256 nonce, bytes signature, bytes approvalData) *external*` + +Relays a transaction. + +For this to succeed, multiple conditions must be met: + - canRelay must `return PreconditionCheck.OK` + - the sender must be a registered relay + - the transaction’s gas price must be larger or equal to the one that was requested by the sender + - the transaction must have enough gas to not run out of gas if all internal transactions (calls to the +recipient) use all gas available to them + - the recipient must have enough balance to pay the relay for the worst-case scenario (i.e. when all gas is +spent) + +If all conditions are met, the call will be relayed and the recipient charged. preRelayedCall, the encoded +function and postRelayedCall will be called in that order. + +Parameters: + - `from`: the client originating the request + - `to`: the target IRelayRecipient contract + - `encodedFunction`: the function call to relay, including data + - `transactionFee`: fee (%) the relay takes over actual gas cost + - `gasPrice`: gas price the client is willing to pay + - `gasLimit`: gas to forward when calling the encoded function + - `nonce`: client’s nonce + - `signature`: client’s signature over all previous params, plus the relay and RelayHub addresses + - `approvalData`: dapp-specific data forwarded to acceptRelayedCall. This value is **not** verified by the +`RelayHub`, but it still can be used for e.g. a signature. + +Emits a TransactionRelayed event. + + + +#### `requiredGas(uint256 relayedCallStipend) → uint256 *external*` + +Returns how much gas should be forwarded to a call to relayCall, in order to relay a transaction that will +spend up to `relayedCallStipend` gas. + + + +#### `maxPossibleCharge(uint256 relayedCallStipend, uint256 gasPrice, uint256 transactionFee) → uint256 *external*` + +Returns the maximum recipient charge, given the amount of gas forwarded, gas price and relay fee. + + + +#### `penalizeRepeatedNonce(bytes unsignedTx1, bytes signature1, bytes unsignedTx2, bytes signature2) *external*` + +Penalize a relay that signed two transactions using the same nonce (making only the first one valid) and +different data (gas price, gas limit, etc. may be different). + +The (unsigned) transaction data and signature for both transactions must be provided. + + + +#### `penalizeIllegalTransaction(bytes unsignedTx, bytes signature) *external*` + +Penalize a relay that sent a transaction that didn’t target ``RelayHub`’s registerRelay or relayCall. + + + +#### `getNonce(address from) → uint256 *external*` + +Returns an account’s nonce in `RelayHub`. + + + +#### `Staked(address relay, uint256 stake, uint256 unstakeDelay) *event*` + +Emitted when a relay’s stake or unstakeDelay are increased + + + +#### `RelayAdded(address relay, address owner, uint256 transactionFee, uint256 stake, uint256 unstakeDelay, string url) *event*` + +Emitted when a relay is registered or re-registered. Looking at these events (and filtering out +RelayRemoved events) lets a client discover the list of available relays. + + + +#### `RelayRemoved(address relay, uint256 unstakeTime) *event*` + +Emitted when a relay is removed (deregistered). `unstakeTime` is the time when unstake will be callable. + + + +#### `Unstaked(address relay, uint256 stake) *event*` + +Emitted when a relay is unstaked for, including the returned stake. + + + +#### `Deposited(address recipient, address from, uint256 amount) *event*` + +Emitted when depositFor is called, including the amount and account that was funded. + + + +#### `Withdrawn(address account, address dest, uint256 amount) *event*` + +Emitted when an account withdraws funds from `RelayHub`. + + + +#### `CanRelayFailed(address relay, address from, address to, bytes4 selector, uint256 reason) *event*` + +Emitted when an attempt to relay a call failed. + +This can happen due to incorrect relayCall arguments, or the recipient not accepting the relayed call. The +actual relayed call was not executed, and the recipient not charged. + +The `reason` parameter contains an error code: values 1-10 correspond to `PreconditionCheck` entries, and values +over 10 are custom recipient error codes returned from acceptRelayedCall. + + + +#### `TransactionRelayed(address relay, address from, address to, bytes4 selector, enum IRelayHub.RelayCallStatus status, uint256 charge) *event*` + +Emitted when a transaction is relayed. +Useful when monitoring a relay’s operation and relayed calls to a contract + +Note that the actual encoded function might be reverted: this is indicated in the `status` parameter. + +`charge` is the Ether value deducted from the recipient’s balance, paid to the relay’s owner. + + + +#### `Penalized(address relay, address sender, uint256 amount) *event*` + +Emitted when a relay is penalized. diff --git a/docs/content/contracts/3.x/api/access.mdx b/docs/content/contracts/3.x/api/access.mdx new file mode 100644 index 00000000..2f241ac4 --- /dev/null +++ b/docs/content/contracts/3.x/api/access.mdx @@ -0,0 +1,591 @@ +--- +title: Access +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/access + + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +* AccessControl provides a general role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts. +* Ownable is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. +* TimelockController is used in combination with one of the above two mechanisms. By assigning a role to an instance of the `TimelockController` contract, the access to the functions controlled by that role will be delayed by some amount of time. + +## Authorization + +### ``Ownable`` + +Contract module which provides a basic access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +By default, the owner account will be the one that deploys the contract. This +can later be changed with transferOwnership. + +This module is used through inheritance. It will make available the modifier +`onlyOwner`, which can be applied to your functions to restrict their use to +the owner. + +**Modifiers** + +* [`onlyOwner()`](#Ownable-onlyOwner) + +**Functions** + +* [`constructor()`](#Ownable-constructor) +* [`owner()`](#Ownable-owner) +* [`renounceOwnership()`](#Ownable-renounceOwnership) +* [`transferOwnership(newOwner)`](#Ownable-transferOwnership-address-) + +**Events** + +* [`OwnershipTransferred(previousOwner, newOwner)`](#Ownable-OwnershipTransferred-address-address-) + + + +#### `onlyOwner() *modifier*` + +Throws if called by any account other than the owner. + + + +#### `constructor() *internal*` + +Initializes the contract setting the deployer as the initial owner. + + + +#### `owner() → address *public*` + +Returns the address of the current owner. + + + +#### `renounceOwnership() *public*` + +Leaves the contract without owner. It will not be possible to call +`onlyOwner` functions anymore. Can only be called by the current owner. + + +Renouncing ownership will leave the contract without an owner, +thereby removing any functionality that is only available to the owner. + + + + +#### `transferOwnership(address newOwner) *public*` + +Transfers ownership of the contract to a new account (`newOwner`). +Can only be called by the current owner. + + + +#### `OwnershipTransferred(address previousOwner, address newOwner) *event*` + +### ``AccessControl`` + +Contract module that allows children to implement role-based access +control mechanisms. + +Roles are referred to by their `bytes32` identifier. These should be exposed +in the external API and be unique. The best way to achieve this is by +using `public constant` hash digests: + +``` +bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); +``` + +Roles can be used to represent a set of permissions. To restrict access to a +function call, use hasRole: + +``` +function foo() public + require(hasRole(MY_ROLE, msg.sender)); + ... + +``` + +Roles can be granted and revoked dynamically via the grantRole and +revokeRole functions. Each role has an associated admin role, and only +accounts that have a role’s admin role can call grantRole and revokeRole. + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means +that only accounts with this role will be able to grant or revoke other +roles. More complex role relationships can be created by using +_setRoleAdmin. + + +The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to +grant and revoke this role. Extra precautions should be taken to secure +accounts that have been granted it. + + +**Functions** + +* [`hasRole(role, account)`](#AccessControl-hasRole-bytes32-address-) +* [`getRoleMemberCount(role)`](#AccessControl-getRoleMemberCount-bytes32-) +* [`getRoleMember(role, index)`](#AccessControl-getRoleMember-bytes32-uint256-) +* [`getRoleAdmin(role)`](#AccessControl-getRoleAdmin-bytes32-) +* [`grantRole(role, account)`](#AccessControl-grantRole-bytes32-address-) +* [`revokeRole(role, account)`](#AccessControl-revokeRole-bytes32-address-) +* [`renounceRole(role, account)`](#AccessControl-renounceRole-bytes32-address-) +* [`_setupRole(role, account)`](#AccessControl-_setupRole-bytes32-address-) +* [`_setRoleAdmin(role, adminRole)`](#AccessControl-_setRoleAdmin-bytes32-bytes32-) + +**Events** + +* [`RoleAdminChanged(role, previousAdminRole, newAdminRole)`](#AccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +* [`RoleGranted(role, account, sender)`](#AccessControl-RoleGranted-bytes32-address-address-) +* [`RoleRevoked(role, account, sender)`](#AccessControl-RoleRevoked-bytes32-address-address-) + + + +#### `hasRole(bytes32 role, address account) → bool *public*` + +Returns `true` if `account` has been granted `role`. + + + +#### `getRoleMemberCount(bytes32 role) → uint256 *public*` + +Returns the number of accounts that have `role`. Can be used +together with getRoleMember to enumerate all bearers of a role. + + + +#### `getRoleMember(bytes32 role, uint256 index) → address *public*` + +Returns one of the accounts that have `role`. `index` must be a +value between 0 and getRoleMemberCount, non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + + +When using getRoleMember and getRoleMemberCount, make sure +you perform all queries on the same block. See the following +[forum post](https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296) +for more information. + + + + +#### `getRoleAdmin(bytes32 role) → bytes32 *public*` + +Returns the admin role that controls `role`. See grantRole and +revokeRole. + +To change a role’s admin, use _setRoleAdmin. + + + +#### `grantRole(bytes32 role, address account) *public*` + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a RoleGranted +event. + +Requirements: + +* the caller must have ``role`’s admin role. + + + +#### `revokeRole(bytes32 role, address account) *public*` + +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a RoleRevoked event. + +Requirements: + +* the caller must have ``role`’s admin role. + + + +#### `renounceRole(bytes32 role, address account) *public*` + +Revokes `role` from the calling account. + +Roles are often managed via grantRole and revokeRole: this function’s +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been granted `role`, emits a RoleRevoked +event. + +Requirements: + +* the caller must be `account`. + + + +#### `_setupRole(bytes32 role, address account) *internal*` + +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a RoleGranted +event. Note that unlike grantRole, this function doesn’t perform any +checks on the calling account. + + + +This function should only be called from the constructor when setting +up the initial roles for the system. + +Using this function in any other way is effectively circumventing the admin +system imposed by AccessControl. + + + + +#### `_setRoleAdmin(bytes32 role, bytes32 adminRole) *internal*` + +Sets `adminRole` as ``role`’s admin role. + +Emits a RoleAdminChanged event. + + + +#### `RoleAdminChanged(bytes32 role, bytes32 previousAdminRole, bytes32 newAdminRole) *event*` + +Emitted when `newAdminRole` is set as ``role`’s admin role, replacing `previousAdminRole` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite +RoleAdminChanged not being emitted signaling this. + +_Available since v3.1._ + + + +#### `RoleGranted(bytes32 role, address account, address sender) *event*` + +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an admin role +bearer except when using _setupRole. + + + +#### `RoleRevoked(bytes32 role, address account, address sender) *event*` + +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + - if using `revokeRole`, it is the admin role bearer + - if using `renounceRole`, it is the role bearer (i.e. `account`) + +## Timelock + +### ``TimelockController`` + +Contract module which acts as a timelocked controller. When set as the +owner of an `Ownable` smart contract, it enforces a timelock on all +`onlyOwner` maintenance operations. This gives time for users of the +controlled contract to exit before a potentially dangerous maintenance +operation is applied. + +By default, this contract is self administered, meaning administration tasks +have to go through the timelock process. The proposer (resp executor) role +is in charge of proposing (resp executing) operations. A common use case is +to position this TimelockController as the owner of a smart contract, with +a multisig or a DAO as the sole proposer. + +_Available since v3.3._ + +**Modifiers** + +* [`onlyRole(role)`](#TimelockController-onlyRole-bytes32-) + +**Functions** + +* [`constructor(minDelay, proposers, executors)`](#TimelockController-constructor-uint256-address---address-) +* [`receive()`](#TimelockController-receive) +* [`isOperation(id)`](#TimelockController-isOperation-bytes32-) +* [`isOperationPending(id)`](#TimelockController-isOperationPending-bytes32-) +* [`isOperationReady(id)`](#TimelockController-isOperationReady-bytes32-) +* [`isOperationDone(id)`](#TimelockController-isOperationDone-bytes32-) +* [`getTimestamp(id)`](#TimelockController-getTimestamp-bytes32-) +* [`getMinDelay()`](#TimelockController-getMinDelay) +* [`hashOperation(target, value, data, predecessor, salt)`](#TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-) +* [`hashOperationBatch(targets, values, datas, predecessor, salt)`](#TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-) +* [`schedule(target, value, data, predecessor, salt, delay)`](#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-) +* [`scheduleBatch(targets, values, datas, predecessor, salt, delay)`](#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-) +* [`cancel(id)`](#TimelockController-cancel-bytes32-) +* [`execute(target, value, data, predecessor, salt)`](#TimelockController-execute-address-uint256-bytes-bytes32-bytes32-) +* [`executeBatch(targets, values, datas, predecessor, salt)`](#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-) +* [`updateDelay(newDelay)`](#TimelockController-updateDelay-uint256-) + +**AccessControl** + +* [`hasRole(role, account)`](#AccessControl-hasRole-bytes32-address-) +* [`getRoleMemberCount(role)`](#AccessControl-getRoleMemberCount-bytes32-) +* [`getRoleMember(role, index)`](#AccessControl-getRoleMember-bytes32-uint256-) +* [`getRoleAdmin(role)`](#AccessControl-getRoleAdmin-bytes32-) +* [`grantRole(role, account)`](#AccessControl-grantRole-bytes32-address-) +* [`revokeRole(role, account)`](#AccessControl-revokeRole-bytes32-address-) +* [`renounceRole(role, account)`](#AccessControl-renounceRole-bytes32-address-) +* [`_setupRole(role, account)`](#AccessControl-_setupRole-bytes32-address-) +* [`_setRoleAdmin(role, adminRole)`](#AccessControl-_setRoleAdmin-bytes32-bytes32-) + +**Events** + +* [`CallScheduled(id, index, target, value, data, predecessor, delay)`](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-) +* [`CallExecuted(id, index, target, value, data)`](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) +* [`Cancelled(id)`](#TimelockController-Cancelled-bytes32-) +* [`MinDelayChange(oldDuration, newDuration)`](#TimelockController-MinDelayChange-uint256-uint256-) + +**AccessControl** + +* [`RoleAdminChanged(role, previousAdminRole, newAdminRole)`](#AccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +* [`RoleGranted(role, account, sender)`](#AccessControl-RoleGranted-bytes32-address-address-) +* [`RoleRevoked(role, account, sender)`](#AccessControl-RoleRevoked-bytes32-address-address-) + + + +#### `onlyRole(bytes32 role) *modifier*` + +Modifier to make a function callable only by a certain role. In +addition to checking the sender’s role, `address(0)` 's role is also +considered. Granting a role to `address(0)` is equivalent to enabling +this role for everyone. + + + +#### `constructor(uint256 minDelay, address[] proposers, address[] executors) *public*` + +Initializes the contract with a given `minDelay`. + + + +#### `receive() *external*` + +Contract might receive/hold ETH as part of the maintenance process. + + + +#### `isOperation(bytes32 id) → bool pending *public*` + +Returns whether an id correspond to a registered operation. This +includes both Pending, Ready and Done operations. + + + +#### `isOperationPending(bytes32 id) → bool pending *public*` + +Returns whether an operation is pending or not. + + + +#### `isOperationReady(bytes32 id) → bool ready *public*` + +Returns whether an operation is ready or not. + + + +#### `isOperationDone(bytes32 id) → bool done *public*` + +Returns whether an operation is done or not. + + + +#### `getTimestamp(bytes32 id) → uint256 timestamp *public*` + +Returns the timestamp at with an operation becomes ready (0 for +unset operations, 1 for done operations). + + + +#### `getMinDelay() → uint256 duration *public*` + +Returns the minimum delay for an operation to become valid. + +This value can be changed by executing an operation that calls `updateDelay`. + + + +#### `hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) → bytes32 hash *public*` + +Returns the identifier of an operation containing a single +transaction. + + + +#### `hashOperationBatch(address[] targets, uint256[] values, bytes[] datas, bytes32 predecessor, bytes32 salt) → bytes32 hash *public*` + +Returns the identifier of an operation containing a batch of +transactions. + + + +#### `schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay) *public* + +Schedule an operation containing a single transaction. + +Emits a CallScheduled event. + +Requirements: + +*` the caller must have the 'proposer' role. + + + +#### `scheduleBatch(address[] targets, uint256[] values, bytes[] datas, bytes32 predecessor, bytes32 salt, uint256 delay) *public* + +Schedule an operation containing a batch of transactions. + +Emits one CallScheduled event per transaction in the batch. + +Requirements: + +*` the caller must have the 'proposer' role. + + + +#### `cancel(bytes32 id) *public* + +Cancel an operation. + +Requirements: + +*` the caller must have the 'proposer' role. + + + +#### `execute(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) *public* + +Execute an (ready) operation containing a single transaction. + +Emits a CallExecuted event. + +Requirements: + +*` the caller must have the 'executor' role. + + + +#### `executeBatch(address[] targets, uint256[] values, bytes[] datas, bytes32 predecessor, bytes32 salt) *public* + +Execute an (ready) operation containing a batch of transactions. + +Emits one CallExecuted event per transaction in the batch. + +Requirements: + +*` the caller must have the 'executor' role. + + + +#### `updateDelay(uint256 newDelay) *external* + +Changes the minimum timelock duration for future operations. + +Emits a MinDelayChange event. + +Requirements: + +*` the caller must be the timelock itself. This can only be achieved by scheduling and later executing +an operation where the timelock is the target and the data is the ABI-encoded call to this function. + + + +#### `CallScheduled(bytes32 id, uint256 index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay) *event*` + +Emitted when a call is scheduled as part of operation `id`. + + + +#### `CallExecuted(bytes32 id, uint256 index, address target, uint256 value, bytes data) *event*` + +Emitted when a call is performed as part of operation `id`. + + + +#### `Cancelled(bytes32 id) *event*` + +Emitted when operation `id` is cancelled. + + + +#### `MinDelayChange(uint256 oldDuration, uint256 newDuration) *event* + +Emitted when the minimum delay for future operations is modified. + +#### Terminology + +* **Operation:** A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see [operation lifecycle](/contracts/3.x/access-control#operation_lifecycle)). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. +* **Operation status:** + * **Unset:** An operation that is not part of the timelock mechanism. + * **Pending:** An operation that has been scheduled, before the timer expires. + * **Ready:** An operation that has been scheduled, after the timer expires. + * **Done:** An operation that has been executed. +* **Predecessor**: An (optional) dependency between operations. An operation can depend on another operation (its predecessor), forcing the execution order of these two operations. +* **Role**: + * **Proposer:** An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. + * **Executor:**` An address (smart contract or EOA) that is in charge of executing operations. + +#### Operation structure + +Operation executed by the [`TimelockControler`](/contracts/5.x/api/access#TimelockController) can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. + +Both operations contain: + +* **Target**, the address of the smart contract that the timelock should operate on. +* **Value**, in wei, that should be sent with the transaction. Most of the time this will be 0. Ether can be deposited before-end or passed along when executing the transaction. +* **Data**, containing the encoded function selector and parameters of the call. This can be produced using a number of tools. For example, a maintenance operation granting role `ROLE` to `ACCOUNT` can be encode using web3js as follows: + +```javascript +const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() +``` + +* **Predecessor**, that specifies a dependency between operations. This dependency is optional. Use `bytes32(0)` if the operation does not have any dependency. +* **Salt**, used to disambiguate two otherwise identical operations. This can be any random value. + +In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. + +#### Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: + +`Unset` -> `Pending` -> `Pending` + `Ready` -> `Done` + +* By calling [`schedule`](/contracts/5.x/api/access#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-) (or [`scheduleBatch`](/contracts/5.x/api/access#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-)), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the [`getTimestamp`](/contracts/5.x/api/access#TimelockController-getTimestamp-bytes32-) method. +* Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. +* By calling [`execute`](/contracts/5.x/api/access#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-) (or [`executeBatch`](/contracts/5.x/api/access#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-)), an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. +* [`cancel`](/contracts/5.x/api/access#TimelockController-TimelockController-cancel-bytes32-) allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is re-scheduled. + +Operations status can be queried using the functions: + +* [`isOperationPending(bytes32)`](/contracts/5.x/api/access#TimelockController-isOperationPending-bytes32-) +* [`isOperationReady(bytes32)`](/contracts/5.x/api/access#TimelockController-isOperationReady-bytes32-) +* [`isOperationDone(bytes32)`](/contracts/5.x/api/access#TimelockController-isOperationDone-bytes32-) + +#### Roles + +##### Admin + +The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, both the timelock and the deployer have this role. After further configuration and testing, the deployer can renounce this role such that all further maintenance operations have to go through the timelock process. + +This role is identified by the **TIMELOCK_ADMIN_ROLE** value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5` + +##### Proposer + +The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. + + +**Proposer fight:** Having multiple proposers, while providing redundancy in case one becomes unavailable, can be dangerous. As proposer have their say on all operations, they could cancel operations they disagree with, including operations to remove them for the proposers. + + +This role is identified by the **PROPOSER_ROLE** value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` + +##### Executor + +The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executor can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. + +This role is identified by the **EXECUTOR_ROLE** value: `0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63` + + +A live contract without at least one proposer and one executor is locked. Make sure these roles are filled by reliable entities before the deployer renounces its administrative rights in favour of the timelock contract itself. See the AccessControl documentation to learn more about role management. + diff --git a/docs/content/contracts/3.x/api/cryptography.mdx b/docs/content/contracts/3.x/api/cryptography.mdx new file mode 100644 index 00000000..736c7d95 --- /dev/null +++ b/docs/content/contracts/3.x/api/cryptography.mdx @@ -0,0 +1,83 @@ +--- +title: Cryptography +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/cryptography + + +This collection of libraries provides simple and safe ways to use different cryptographic primitives. + +The following related EIPs are in draft status and can be found in the drafts directory. + +* EIP712 + +## Libraries + +### ``ECDSA`` + +Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + +These functions can be used to verify that a message was signed by the holder +of the private keys of a given address. + +**Functions** + +* [`recover(hash, signature)`](#ECDSA-recover-bytes32-bytes-) +* [`recover(hash, v, r, s)`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) +* [`toEthSignedMessageHash(hash)`](#ECDSA-toEthSignedMessageHash-bytes32-) + + + +#### `recover(bytes32 hash, bytes signature) → address *internal*` + +Returns the address that signed a hashed message (`hash`) with +`signature`. This address can then be used for verification purposes. + +The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: +this function rejects them by requiring the `s` value to be in the lower +half order, and the `v` value to be either 27 or 28. + + +`hash` _must_ be the result of a hash operation for the +verification to be secure: it is possible to craft signatures that +recover to arbitrary addresses for non-hashed data. A safe way to ensure +this is by receiving a hash of the original message (which may otherwise +be too long), and then calling toEthSignedMessageHash on it. + + + + +#### `recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address *internal*` + +Overload of ECDSA-recover-bytes32-bytes- that receives the `v`, +`r` and `s` signature fields separately. + + + +#### `toEthSignedMessageHash(bytes32 hash) → bytes32 *internal*` + +Returns an Ethereum Signed Message, created from a `hash`. This +replicates the behavior of the +[`eth_sign`](https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign) +JSON-RPC method. + +See recover. +/ + +### ``MerkleProof`` + +These functions deal with verification of Merkle trees (hash trees), + +**Functions** + +* [`verify(proof, root, leaf)`](#MerkleProof-verify-bytes32---bytes32-bytes32-) + + + +#### `verify(bytes32[] proof, bytes32 root, bytes32 leaf) → bool *internal*` + +Returns true if a `leaf` can be proved to be a part of a Merkle tree +defined by `root`. For this, a `proof` must be provided, containing +sibling hashes on the branch from the leaf to the root of the tree. Each +pair of leaves and each pair of pre-images are assumed to be sorted. diff --git a/docs/content/contracts/3.x/api/drafts.mdx b/docs/content/contracts/3.x/api/drafts.mdx new file mode 100644 index 00000000..b05ea61d --- /dev/null +++ b/docs/content/contracts/3.x/api/drafts.mdx @@ -0,0 +1,213 @@ +--- +title: Draft EIPs +--- + +This directory contains implementations of EIPs that are still in Draft status. + +Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their [stability](/contracts/3.x/releases-stability). Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts in this directory, which will be duly announced in the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md). The EIPs included here are used by projects in production and this may make them less likely to change significantly. + +## Cryptography + +### ``EIP712`` + +[EIP 712](https://eips.ethereum.org/EIPS/eip-712) is a standard for hashing and signing of typed structured data. + +The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, +thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding +they need in their contracts using a combination of `abi.encode` and `keccak256`. + +This contract implements the EIP 712 domain separator (_domainSeparatorV4) that is used as part of the encoding +scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA +(_hashTypedDataV4). + +The implementation of the domain separator was designed to be as efficient as possible while still properly updating +the chain id to protect against replay attacks on an eventual fork of the chain. + + +This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method +[`eth_signTypedDataV4` in MetaMask](https://docs.metamask.io/guide/signing-data.html). + + +_Available since v3.4._ + +**Functions** + +* [`constructor(name, version)`](#EIP712-constructor-string-string-) +* [`_domainSeparatorV4()`](#EIP712-_domainSeparatorV4) +* [`_hashTypedDataV4(structHash)`](#EIP712-_hashTypedDataV4-bytes32-) + + + +#### `constructor(string name, string version) *internal*` + +Initializes the domain separator and parameter caches. + +The meaning of `name` and `version` is specified in +[EIP 712](https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator): + +* `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. +* `version`: the current major version of the signing domain. + + +These parameters cannot be changed except through a [smart +contract upgrade](/contracts/5.x/learn/upgrading-smart-contracts). + + + + +#### `_domainSeparatorV4() → bytes32 *internal*` + +Returns the domain separator for the current chain. + + + +#### `_hashTypedDataV4(bytes32 structHash) → bytes32 *internal*` + +Given an already [hashed struct](https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct), this +function returns the hash of the fully encoded EIP712 message for this domain. + +This hash can be used together with ECDSA-recover to obtain the signer of a message. For example: + +```solidity +bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + keccak256("Mail(address to,string contents)"), + mailTo, + keccak256(bytes(mailContents)) +))); +address signer = ECDSA.recover(digest, signature); +``` + +## ERC 20 + +### ``IERC20Permit`` + +Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in +[EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Adds the permit method, which can be used to change an account’s ERC20 allowance (see IERC20-allowance) by +presenting a message signed by the account. By not relying on `IERC20-approve`, the token holder account doesn’t +need to send a transaction, and thus is not required to hold Ether at all. + +**Functions** + +* [`permit(owner, spender, value, deadline, v, r, s)`](#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +* [`nonces(owner)`](#IERC20Permit-nonces-address-) +* [`DOMAIN_SEPARATOR()`](#IERC20Permit-DOMAIN_SEPARATOR) + + + +#### `permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) *external*` + +Sets `value` as the allowance of `spender` over `owner’s tokens, +given `owner’s signed approval. + + +The same issues IERC20-approve has related to transaction +ordering also apply here. + + +Emits an Approval event. + +Requirements: + +* `spender` cannot be the zero address. +* `deadline` must be a timestamp in the future. +* `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +* the signature must use ``owner`’s current nonce (see nonces). + +For more information on the signature format, see the +https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP +section]. + + + +#### `nonces(address owner) → uint256 *external*` + +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for permit. + +Every successful call to permit increases ``owner`’s nonce by one. This +prevents a signature from being used multiple times. + + + +#### `DOMAIN_SEPARATOR() → bytes32 *external*` + +Returns the domain separator used in the encoding of the signature for `permit`, as defined by EIP712. + +### ``ERC20Permit`` + +Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in +[EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Adds the permit method, which can be used to change an account’s ERC20 allowance (see IERC20-allowance) by +presenting a message signed by the account. By not relying on `IERC20-approve`, the token holder account doesn’t +need to send a transaction, and thus is not required to hold Ether at all. + +_Available since v3.4._ + +**Functions** + +* [`constructor(name)`](#ERC20Permit-constructor-string-) +* [`permit(owner, spender, value, deadline, v, r, s)`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +* [`nonces(owner)`](#ERC20Permit-nonces-address-) +* [`DOMAIN_SEPARATOR()`](#ERC20Permit-DOMAIN_SEPARATOR) + +**EIP712** + +* [`_domainSeparatorV4()`](#EIP712-_domainSeparatorV4) +* [`_hashTypedDataV4(structHash)`](#EIP712-_hashTypedDataV4-bytes32-) + +**ERC20** + +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20-_beforeTokenTransfer-address-address-uint256-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `constructor(string name) *internal*` + +Initializes the EIP712 domain separator using the `name` parameter, and setting `version` to `"1"`. + +It’s a good idea to use the same `name` that is defined as the ERC20 token name. + + + +#### `permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) *public*` + +See IERC20Permit-permit. + + + +#### `nonces(address owner) → uint256 *public*` + +See IERC20Permit-nonces. + + + +#### `DOMAIN_SEPARATOR() → bytes32 *external*` + +See IERC20Permit-DOMAIN_SEPARATOR. diff --git a/docs/content/contracts/3.x/api/index.mdx b/docs/content/contracts/3.x/api/index.mdx new file mode 100644 index 00000000..9c8a5d78 --- /dev/null +++ b/docs/content/contracts/3.x/api/index.mdx @@ -0,0 +1,25 @@ +--- +title: API Reference +--- + +# OpenZeppelin Contracts API Reference + +This API reference is automatically generated from the OpenZeppelin Contracts repository. + +## Contract Categories + +### Access Control +- [Access Control](/contracts/3.x/api/access) - Role-based access control mechanisms +- [Ownable](/contracts/3.x/api/access#ownable) - Simple ownership access control + +### Tokens +- [ERC20](/contracts/3.x/api/token/ERC20) - Fungible token standard implementation +- [ERC721](/contracts/3.x/api/token/ERC721) - Non-fungible token standard implementation +- [ERC1155](/contracts/3.x/api/token/ERC1155) - Multi-token standard implementation + +### Utilities +- [Utils](/contracts/3.x/api/utils) - General utility functions and contracts +- [Cryptography](/contracts/3.x/api/cryptography) - Cryptographic utilities + +### Proxy Patterns +- [Proxy](/contracts/3.x/api/proxy) - Upgradeable proxy patterns diff --git a/docs/content/contracts/3.x/api/introspection.mdx b/docs/content/contracts/3.x/api/introspection.mdx new file mode 100644 index 00000000..1b297343 --- /dev/null +++ b/docs/content/contracts/3.x/api/introspection.mdx @@ -0,0 +1,307 @@ +--- +title: Introspection +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/introspection + + +This set of interfaces and contracts deal with [type introspection](https://en.wikipedia.org/wiki/Type_introspection) of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s _interface_. + +Ethereum contracts have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. `ERC20` tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract _declaring_ its interface can be very helpful in preventing errors. + +There are two main ways to approach this. + +* Locally, where a contract implements `IERC165` and declares an interface, and a second one queries it directly via `ERC165Checker`. +* Globally, where a global and unique registry (`IERC1820Registry`) is used to register implementers of a certain interface (`IERC1820Implementer`). It is then the registry that is queried, which allows for more complex setups, like contracts implementing interfaces for externally-owned accounts. + +Note that, in all cases, accounts simply _declare_ their interfaces, but they are not required to actually implement them. This mechanism can therefore be used to both prevent errors and allow for complex interactions (see `ERC777`), but it must not be relied on for security. + +## Local + +### ``IERC165`` + +Interface of the ERC165 standard, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-165). + +Implementers can declare support of contract interfaces, which can then be +queried by others (ERC165Checker). + +For an implementation, see ERC165. + +**Functions** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + + + +#### `supportsInterface(bytes4 interfaceId) → bool *external*` + +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +### ``ERC165`` + +Implementation of the IERC165 interface. + +Contracts may inherit from this and call _registerInterface to declare +their support of an interface. + +**Functions** + +* [`constructor()`](#ERC165-constructor) +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + + + +#### `constructor() *internal*` + + + +#### `supportsInterface(bytes4 interfaceId) → bool *public*` + +See IERC165-supportsInterface. + +Time complexity O(1), guaranteed to always use less than 30 000 gas. + + + +#### `_registerInterface(bytes4 interfaceId) *internal*` + +Registers the contract as an implementer of the interface defined by +`interfaceId`. Support of the actual ERC165 interface is automatic and +registering its interface id is not required. + +See IERC165-supportsInterface. + +Requirements: + +* `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). + +### ``ERC165Checker`` + +Library used to query support of an interface declared via IERC165. + +Note that these functions return the actual result of the query: they do not +`revert` if an interface is not supported. It is up to the caller to decide +what to do in these cases. + +**Functions** + +* [`supportsERC165(account)`](#ERC165Checker-supportsERC165-address-) +* [`supportsInterface(account, interfaceId)`](#ERC165Checker-supportsInterface-address-bytes4-) +* [`getSupportedInterfaces(account, interfaceIds)`](#ERC165Checker-getSupportedInterfaces-address-bytes4-) +* [`supportsAllInterfaces(account, interfaceIds)`](#ERC165Checker-supportsAllInterfaces-address-bytes4-) + + + +#### `supportsERC165(address account) → bool *internal*` + +Returns true if `account` supports the IERC165 interface, + + + +#### `supportsInterface(address account, bytes4 interfaceId) → bool *internal*` + +Returns true if `account` supports the interface defined by +`interfaceId`. Support for IERC165 itself is queried automatically. + +See IERC165-supportsInterface. + + + +#### `getSupportedInterfaces(address account, bytes4[] interfaceIds) → bool[] *internal*` + +Returns a boolean array where each value corresponds to the +interfaces passed in and whether they’re supported or not. This allows +you to batch check interfaces for a contract where your expectation +is that some interfaces may not be supported. + +See IERC165-supportsInterface. + +_Available since v3.4._ + + + +#### `supportsAllInterfaces(address account, bytes4[] interfaceIds) → bool *internal*` + +Returns true if `account` supports all the interfaces defined in +`interfaceIds`. Support for IERC165 itself is queried automatically. + +Batch-querying can lead to gas savings by skipping repeated checks for +IERC165 support. + +See IERC165-supportsInterface. + +## Global + +### ``IERC1820Registry`` + +Interface of the global ERC1820 Registry, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1820). Accounts may register +implementers for interfaces in this registry, as well as query support. + +Implementers may be shared by multiple accounts, and can also implement more +than a single interface for each account. Contracts can implement interfaces +for themselves, but externally-owned accounts (EOA) must delegate this to a +contract. + +IERC165 interfaces can also be queried via the registry. + +For an in-depth explanation and source code analysis, see the EIP text. + +**Functions** + +* [`setManager(account, newManager)`](#IERC1820Registry-setManager-address-address-) +* [`getManager(account)`](#IERC1820Registry-getManager-address-) +* [`setInterfaceImplementer(account, _interfaceHash, implementer)`](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-) +* [`getInterfaceImplementer(account, _interfaceHash)`](#IERC1820Registry-getInterfaceImplementer-address-bytes32-) +* [`interfaceHash(interfaceName)`](#IERC1820Registry-interfaceHash-string-) +* [`updateERC165Cache(account, interfaceId)`](#IERC1820Registry-updateERC165Cache-address-bytes4-) +* [`implementsERC165Interface(account, interfaceId)`](#IERC1820Registry-implementsERC165Interface-address-bytes4-) +* [`implementsERC165InterfaceNoCache(account, interfaceId)`](#IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-) + +**Events** + +* [`InterfaceImplementerSet(account, interfaceHash, implementer)`](#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-) +* [`ManagerChanged(account, newManager)`](#IERC1820Registry-ManagerChanged-address-address-) + + + +#### `setManager(address account, address newManager) *external*` + +Sets `newManager` as the manager for `account`. A manager of an +account is able to set interface implementers for it. + +By default, each account is its own manager. Passing a value of `0x0` in +`newManager` will reset the manager to this initial state. + +Emits a ManagerChanged event. + +Requirements: + +* the caller must be the current manager for `account`. + + + +#### `getManager(address account) → address *external*` + +Returns the manager for `account`. + +See setManager. + + + +#### `setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) *external*` + +Sets the `implementer` contract as ``account`’s implementer for +`interfaceHash`. + +`account` being the zero address is an alias for the caller’s address. +The zero address can also be used in `implementer` to remove an old one. + +See interfaceHash to learn how these are created. + +Emits an InterfaceImplementerSet event. + +Requirements: + +* the caller must be the current manager for `account`. +* `interfaceHash` must not be an IERC165 interface id (i.e. it must not +end in 28 zeroes). +* `implementer` must implement IERC1820Implementer and return true when +queried for support, unless `implementer` is the caller. See +IERC1820Implementer-canImplementInterfaceForAddress. + + + +#### `getInterfaceImplementer(address account, bytes32 _interfaceHash) → address *external*` + +Returns the implementer of `interfaceHash` for `account`. If no such +implementer is registered, returns the zero address. + +If `interfaceHash` is an IERC165 interface id (i.e. it ends with 28 +zeroes), `account` will be queried for support of it. + +`account` being the zero address is an alias for the caller’s address. + + + +#### `interfaceHash(string interfaceName) → bytes32 *external*` + +Returns the interface hash for an `interfaceName`, as defined in the +corresponding +[section of the EIP](https://eips.ethereum.org/EIPS/eip-1820#interface-name). + + + +#### `updateERC165Cache(address account, bytes4 interfaceId) *external*` + + + +#### `implementsERC165Interface(address account, bytes4 interfaceId) → bool *external*` + + + +#### `implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) → bool *external*` + + + +#### `InterfaceImplementerSet(address account, bytes32 interfaceHash, address implementer) *event*` + + + +#### `ManagerChanged(address account, address newManager) *event*` + +### ``IERC1820Implementer`` + +Interface for an ERC1820 implementer, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface). +Used by contracts that will be registered as implementers in the +IERC1820Registry. + +**Functions** + +* [`canImplementInterfaceForAddress(interfaceHash, account)`](#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-) + + + +#### `canImplementInterfaceForAddress(bytes32 interfaceHash, address account) → bytes32 *external*` + +Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract +implements `interfaceHash` for `account`. + +See IERC1820Registry-setInterfaceImplementer. + +### ``ERC1820Implementer`` + +Implementation of the IERC1820Implementer interface. + +Contracts may inherit from this and call _registerInterfaceForAddress to +declare their willingness to be implementers. +IERC1820Registry-setInterfaceImplementer should then be called for the +registration to be complete. + +**Functions** + +* [`canImplementInterfaceForAddress(interfaceHash, account)`](#ERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-) +* [`_registerInterfaceForAddress(interfaceHash, account)`](#ERC1820Implementer-_registerInterfaceForAddress-bytes32-address-) + + + +#### `canImplementInterfaceForAddress(bytes32 interfaceHash, address account) → bytes32 *public*` + + + +#### `_registerInterfaceForAddress(bytes32 interfaceHash, address account) *internal*` + +Declares the contract as willing to be an implementer of +`interfaceHash` for `account`. + +See IERC1820Registry-setInterfaceImplementer and +IERC1820Registry-interfaceHash. diff --git a/docs/content/contracts/3.x/api/math.mdx b/docs/content/contracts/3.x/api/math.mdx new file mode 100644 index 00000000..268c3d0f --- /dev/null +++ b/docs/content/contracts/3.x/api/math.mdx @@ -0,0 +1,302 @@ +--- +title: Math +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/math + + +These are math-related utilities. + +## Libraries + +### ``SafeMath`` + +Wrappers over Solidity’s arithmetic operations with added overflow +checks. + +Arithmetic operations in Solidity wrap on overflow. This can easily result +in bugs, because programmers usually assume that an overflow raises an +error, which is the standard behavior in high level programming languages. +`SafeMath` restores this intuition by reverting the transaction when an +operation overflows. + +Using this library instead of the unchecked operations eliminates an entire +class of bugs, so it’s recommended to use it always. + +**Functions** + +* [`tryAdd(a, b)`](#SafeMath-tryAdd-uint256-uint256-) +* [`trySub(a, b)`](#SafeMath-trySub-uint256-uint256-) +* [`tryMul(a, b)`](#SafeMath-tryMul-uint256-uint256-) +* [`tryDiv(a, b)`](#SafeMath-tryDiv-uint256-uint256-) +* [`tryMod(a, b)`](#SafeMath-tryMod-uint256-uint256-) +* [`add(a, b)`](#SafeMath-add-uint256-uint256-) +* [`sub(a, b)`](#SafeMath-sub-uint256-uint256-) +* [`mul(a, b)`](#SafeMath-mul-uint256-uint256-) +* [`div(a, b)`](#SafeMath-div-uint256-uint256-) +* [`mod(a, b)`](#SafeMath-mod-uint256-uint256-) +* [`sub(a, b, errorMessage)`](#SafeMath-sub-uint256-uint256-string-) +* [`div(a, b, errorMessage)`](#SafeMath-div-uint256-uint256-string-) +* [`mod(a, b, errorMessage)`](#SafeMath-mod-uint256-uint256-string-) + + + +#### `tryAdd(uint256 a, uint256 b) → bool, uint256 *internal*` + +Returns the addition of two unsigned integers, with an overflow flag. + +_Available since v3.4._ + + + +#### `trySub(uint256 a, uint256 b) → bool, uint256 *internal*` + +Returns the substraction of two unsigned integers, with an overflow flag. + +_Available since v3.4._ + + + +#### `tryMul(uint256 a, uint256 b) → bool, uint256 *internal*` + +Returns the multiplication of two unsigned integers, with an overflow flag. + +_Available since v3.4._ + + + +#### `tryDiv(uint256 a, uint256 b) → bool, uint256 *internal*` + +Returns the division of two unsigned integers, with a division by zero flag. + +_Available since v3.4._ + + + +#### `tryMod(uint256 a, uint256 b) → bool, uint256 *internal*` + +Returns the remainder of dividing two unsigned integers, with a division by zero flag. + +_Available since v3.4._ + + + +#### `add(uint256 a, uint256 b) → uint256 *internal*` + +Returns the addition of two unsigned integers, reverting on +overflow. + +Counterpart to Solidity’s `+` operator. + +Requirements: + +* Addition cannot overflow. + + + +#### `sub(uint256 a, uint256 b) → uint256 *internal*` + +Returns the subtraction of two unsigned integers, reverting on +overflow (when the result is negative). + +Counterpart to Solidity’s `-` operator. + +Requirements: + +* Subtraction cannot overflow. + + + +#### `mul(uint256 a, uint256 b) → uint256 *internal*` + +Returns the multiplication of two unsigned integers, reverting on +overflow. + +Counterpart to Solidity’s `*` operator. + +Requirements: + +* Multiplication cannot overflow. + + + +#### `div(uint256 a, uint256 b) → uint256 *internal*` + +Returns the integer division of two unsigned integers, reverting on +division by zero. The result is rounded towards zero. + +Counterpart to Solidity’s `/` operator. Note: this function uses a +`revert` opcode (which leaves remaining gas untouched) while Solidity +uses an invalid opcode to revert (consuming all remaining gas). + +Requirements: + +* The divisor cannot be zero. + + + +#### `mod(uint256 a, uint256 b) → uint256 *internal*` + +Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), +reverting when dividing by zero. + +Counterpart to Solidity’s `%` operator. This function uses a `revert` +opcode (which leaves remaining gas untouched) while Solidity uses an +invalid opcode to revert (consuming all remaining gas). + +Requirements: + +* The divisor cannot be zero. + + + +#### `sub(uint256 a, uint256 b, string errorMessage) → uint256 *internal*` + +Returns the subtraction of two unsigned integers, reverting with custom message on +overflow (when the result is negative). + + +This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use trySub. + + +Counterpart to Solidity’s `-` operator. + +Requirements: + +* Subtraction cannot overflow. + + + +#### `div(uint256 a, uint256 b, string errorMessage) → uint256 *internal*` + +Returns the integer division of two unsigned integers, reverting with custom message on +division by zero. The result is rounded towards zero. + + +This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use tryDiv. + + +Counterpart to Solidity’s `/` operator. Note: this function uses a +`revert` opcode (which leaves remaining gas untouched) while Solidity +uses an invalid opcode to revert (consuming all remaining gas). + +Requirements: + +* The divisor cannot be zero. + + + +#### `mod(uint256 a, uint256 b, string errorMessage) → uint256 *internal*` + +Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), +reverting with custom message when dividing by zero. + + +This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use tryMod. + + +Counterpart to Solidity’s `%` operator. This function uses a `revert` +opcode (which leaves remaining gas untouched) while Solidity uses an +invalid opcode to revert (consuming all remaining gas). + +Requirements: + +* The divisor cannot be zero. + +### ``SignedSafeMath`` + +Signed math operations with safety checks that revert on error. + +**Functions** + +* [`mul(a, b)`](#SignedSafeMath-mul-int256-int256-) +* [`div(a, b)`](#SignedSafeMath-div-int256-int256-) +* [`sub(a, b)`](#SignedSafeMath-sub-int256-int256-) +* [`add(a, b)`](#SignedSafeMath-add-int256-int256-) + + + +#### `mul(int256 a, int256 b) → int256 *internal*` + +Returns the multiplication of two signed integers, reverting on +overflow. + +Counterpart to Solidity’s `*` operator. + +Requirements: + +* Multiplication cannot overflow. + + + +#### `div(int256 a, int256 b) → int256 *internal*` + +Returns the integer division of two signed integers. Reverts on +division by zero. The result is rounded towards zero. + +Counterpart to Solidity’s `/` operator. Note: this function uses a +`revert` opcode (which leaves remaining gas untouched) while Solidity +uses an invalid opcode to revert (consuming all remaining gas). + +Requirements: + +* The divisor cannot be zero. + + + +#### `sub(int256 a, int256 b) → int256 *internal*` + +Returns the subtraction of two signed integers, reverting on +overflow. + +Counterpart to Solidity’s `-` operator. + +Requirements: + +* Subtraction cannot overflow. + + + +#### `add(int256 a, int256 b) → int256 *internal*` + +Returns the addition of two signed integers, reverting on +overflow. + +Counterpart to Solidity’s `+` operator. + +Requirements: + +* Addition cannot overflow. + +### ``Math`` + +Standard math utilities missing in the Solidity language. + +**Functions** + +* [`max(a, b)`](#Math-max-uint256-uint256-) +* [`min(a, b)`](#Math-min-uint256-uint256-) +* [`average(a, b)`](#Math-average-uint256-uint256-) + + + +#### `max(uint256 a, uint256 b) → uint256 *internal*` + +Returns the largest of two numbers. + + + +#### `min(uint256 a, uint256 b) → uint256 *internal*` + +Returns the smallest of two numbers. + + + +#### `average(uint256 a, uint256 b) → uint256 *internal*` + +Returns the average of two numbers. The result is rounded towards +zero. diff --git a/docs/content/contracts/3.x/api/payment.mdx b/docs/content/contracts/3.x/api/payment.mdx new file mode 100644 index 00000000..dc86f4f7 --- /dev/null +++ b/docs/content/contracts/3.x/api/payment.mdx @@ -0,0 +1,394 @@ +--- +title: Payment +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/payment + + +Utilities related to sending and receiving payments. Examples are PullPayment, which implements the best security practices when sending funds to third parties, and PaymentSplitter to receive incoming payments among a number of beneficiaries. + + +When transferring funds to and from untrusted third parties, there is always a security risk of reentrancy. If you would like to learn more about this and ways to protect against it, check out our blog post [Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + + +## Utilities + +### ``PaymentSplitter`` + +This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware +that the Ether will be split in this way, since it is handled transparently by the contract. + +The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each +account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim +an amount proportional to the percentage of total shares they were assigned. + +`PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the +accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the release +function. + +**Functions** + +* [`constructor(payees, shares_)`](#PaymentSplitter-constructor-address---uint256-) +* [`receive()`](#PaymentSplitter-receive) +* [`totalShares()`](#PaymentSplitter-totalShares) +* [`totalReleased()`](#PaymentSplitter-totalReleased) +* [`shares(account)`](#PaymentSplitter-shares-address-) +* [`released(account)`](#PaymentSplitter-released-address-) +* [`payee(index)`](#PaymentSplitter-payee-uint256-) +* [`release(account)`](#PaymentSplitter-release-address-payable-) + +**Events** + +* [`PayeeAdded(account, shares)`](#PaymentSplitter-PayeeAdded-address-uint256-) +* [`PaymentReleased(to, amount)`](#PaymentSplitter-PaymentReleased-address-uint256-) +* [`PaymentReceived(from, amount)`](#PaymentSplitter-PaymentReceived-address-uint256-) + + + +#### `constructor(address[] payees, uint256[] shares_) *public*` + +Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at +the matching position in the `shares` array. + +All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no +duplicates in `payees`. + + + +#### `receive() *external*` + +The Ether received will be logged with PaymentReceived events. Note that these events are not fully +reliable: it’s possible for a contract to receive Ether without triggering this function. This only affects the +reliability of the events, and not the actual splitting of Ether. + +To learn more about this see the Solidity documentation for +https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function[fallback +functions]. + + + +#### `totalShares() → uint256 *public*` + +Getter for the total shares held by payees. + + + +#### `totalReleased() → uint256 *public*` + +Getter for the total amount of Ether already released. + + + +#### `shares(address account) → uint256 *public*` + +Getter for the amount of shares held by an account. + + + +#### `released(address account) → uint256 *public*` + +Getter for the amount of Ether already released to a payee. + + + +#### `payee(uint256 index) → address *public*` + +Getter for the address of the payee number `index`. + + + +#### `release(address payable account) *public*` + +Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the +total shares and their previous withdrawals. + + + +#### `PayeeAdded(address account, uint256 shares) *event*` + + + +#### `PaymentReleased(address to, uint256 amount) *event*` + + + +#### `PaymentReceived(address from, uint256 amount) *event*` + +### ``PullPayment`` + +Simple implementation of a +[pull-payment](https://consensys.github.io/smart-contract-best-practices/recommendations/#favor-pull-over-push-for-external-calls) +strategy, where the paying contract doesn’t interact directly with the +receiver account, which must withdraw its payments itself. + +Pull-payments are often considered the best practice when it comes to sending +Ether, security-wise. It prevents recipients from blocking execution, and +eliminates reentrancy concerns. + + +If you would like to learn more about reentrancy and alternative ways +to protect against it, check out our blog post +[Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + + +To use, derive from the `PullPayment` contract, and use _asyncTransfer +instead of Solidity’s `transfer` function. Payees can query their due +payments with payments, and retrieve them with withdrawPayments. + +**Functions** + +* [`constructor()`](#PullPayment-constructor) +* [`withdrawPayments(payee)`](#PullPayment-withdrawPayments-address-payable-) +* [`payments(dest)`](#PullPayment-payments-address-) +* [`_asyncTransfer(dest, amount)`](#PullPayment-_asyncTransfer-address-uint256-) + + + +#### `constructor() *internal*` + + + +#### `withdrawPayments(address payable payee) *public*` + +Withdraw accumulated payments, forwarding all gas to the recipient. + +Note that _any_ account can call this function, not just the `payee`. +This means that contracts unaware of the `PullPayment` protocol can still +receive funds this way, by having a separate account call +withdrawPayments. + + +Forwarding all gas opens the door to reentrancy vulnerabilities. +Make sure you trust the recipient, or are either following the +checks-effects-interactions pattern or using ReentrancyGuard. + + + + +#### `payments(address dest) → uint256 *public*` + +Returns the payments owed to an address. + + + +#### `_asyncTransfer(address dest, uint256 amount) *internal*` + +Called by the payer to store the sent amount as credit to be pulled. +Funds sent in this way are stored in an intermediate Escrow contract, so +there is no danger of them being spent before withdrawal. + +## Escrow + +### ``Escrow`` + +Base escrow contract, holds funds designated for a payee until they +withdraw them. + +Intended usage: This contract (and derived escrow contracts) should be a +standalone contract, that only interacts with the contract that instantiated +it. That way, it is guaranteed that all Ether will be handled according to +the `Escrow` rules, and there is no need to check for payable functions or +transfers in the inheritance tree. The contract that uses the escrow as its +payment method should be its owner, and provide public methods redirecting +to the escrow’s deposit and withdraw. + +**Functions** + +* [`depositsOf(payee)`](#Escrow-depositsOf-address-) +* [`deposit(payee)`](#Escrow-deposit-address-) +* [`withdraw(payee)`](#Escrow-withdraw-address-payable-) + +**Ownable** + +* [`constructor()`](#Ownable-constructor) +* [`owner()`](#Ownable-owner) +* [`renounceOwnership()`](#Ownable-renounceOwnership) +* [`transferOwnership(newOwner)`](#Ownable-transferOwnership-address-) + +**Events** + +* [`Deposited(payee, weiAmount)`](#Escrow-Deposited-address-uint256-) +* [`Withdrawn(payee, weiAmount)`](#Escrow-Withdrawn-address-uint256-) + +**Ownable** + +* [`OwnershipTransferred(previousOwner, newOwner)`](#Ownable-OwnershipTransferred-address-address-) + + + +#### `depositsOf(address payee) → uint256 *public*` + + + +#### `deposit(address payee) *public*` + +Stores the sent amount as credit to be withdrawn. + + + +#### `withdraw(address payable payee) *public*` + +Withdraw accumulated balance for a payee, forwarding all gas to the +recipient. + + +Forwarding all gas opens the door to reentrancy vulnerabilities. +Make sure you trust the recipient, or are either following the +checks-effects-interactions pattern or using ReentrancyGuard. + + + + +#### `Deposited(address payee, uint256 weiAmount) *event*` + + + +#### `Withdrawn(address payee, uint256 weiAmount) *event*` + +### ``ConditionalEscrow`` + +Base abstract escrow to only allow withdrawal if a condition is met. +Intended usage: See Escrow. Same usage guidelines apply here. + +**Functions** + +* [`withdrawalAllowed(payee)`](#ConditionalEscrow-withdrawalAllowed-address-) +* [`withdraw(payee)`](#ConditionalEscrow-withdraw-address-payable-) + +**Escrow** + +* [`depositsOf(payee)`](#Escrow-depositsOf-address-) +* [`deposit(payee)`](#Escrow-deposit-address-) + +**Ownable** + +* [`constructor()`](#Ownable-constructor) +* [`owner()`](#Ownable-owner) +* [`renounceOwnership()`](#Ownable-renounceOwnership) +* [`transferOwnership(newOwner)`](#Ownable-transferOwnership-address-) + +**Events** + +**Escrow** + +* [`Deposited(payee, weiAmount)`](#Escrow-Deposited-address-uint256-) +* [`Withdrawn(payee, weiAmount)`](#Escrow-Withdrawn-address-uint256-) + +**Ownable** + +* [`OwnershipTransferred(previousOwner, newOwner)`](#Ownable-OwnershipTransferred-address-address-) + + + +#### `withdrawalAllowed(address payee) → bool *public*` + +Returns whether an address is allowed to withdraw their funds. To be +implemented by derived contracts. + + + +#### `withdraw(address payable payee) *public*` + +### ``RefundEscrow`` + +Escrow that holds funds for a beneficiary, deposited from multiple +parties. +Intended usage: See Escrow. Same usage guidelines apply here. +The owner account (that is, the contract that instantiates this +contract) may deposit, close the deposit period, and allow for either +withdrawal by the beneficiary, or refunds to the depositors. All interactions +with `RefundEscrow` will be made through the owner contract. + +**Functions** + +* [`constructor(beneficiary_)`](#RefundEscrow-constructor-address-payable-) +* [`state()`](#RefundEscrow-state) +* [`beneficiary()`](#RefundEscrow-beneficiary) +* [`deposit(refundee)`](#RefundEscrow-deposit-address-) +* [`close()`](#RefundEscrow-close) +* [`enableRefunds()`](#RefundEscrow-enableRefunds) +* [`beneficiaryWithdraw()`](#RefundEscrow-beneficiaryWithdraw) +* [`withdrawalAllowed(_)`](#RefundEscrow-withdrawalAllowed-address-) + +**ConditionalEscrow** + +* [`withdraw(payee)`](#ConditionalEscrow-withdraw-address-payable-) + +**Escrow** + +* [`depositsOf(payee)`](#Escrow-depositsOf-address-) + +**Ownable** + +* [`owner()`](#Ownable-owner) +* [`renounceOwnership()`](#Ownable-renounceOwnership) +* [`transferOwnership(newOwner)`](#Ownable-transferOwnership-address-) + +**Events** + +* [`RefundsClosed()`](#RefundEscrow-RefundsClosed) +* [`RefundsEnabled()`](#RefundEscrow-RefundsEnabled) + +**Escrow** + +* [`Deposited(payee, weiAmount)`](#Escrow-Deposited-address-uint256-) +* [`Withdrawn(payee, weiAmount)`](#Escrow-Withdrawn-address-uint256-) + +**Ownable** + +* [`OwnershipTransferred(previousOwner, newOwner)`](#Ownable-OwnershipTransferred-address-address-) + + + +#### `constructor(address payable beneficiary_) *public*` + +Constructor. + + + +#### `state() → enum RefundEscrow.State *public*` + + + +#### `beneficiary() → address payable *public*` + + + +#### `deposit(address refundee) *public*` + +Stores funds that may later be refunded. + + + +#### `close() *public*` + +Allows for the beneficiary to withdraw their funds, rejecting +further deposits. + + + +#### `enableRefunds() *public*` + +Allows for refunds to take place, rejecting further deposits. + + + +#### `beneficiaryWithdraw() *public*` + +Withdraws the beneficiary’s funds. + + + +#### `withdrawalAllowed(address) → bool *public*` + +Returns whether refundees can withdraw their deposits (be refunded). The overridden function receives a +'payee' argument, but we ignore it here since the condition is global, not per-payee. + + + +#### `RefundsClosed() *event*` + + + +#### `RefundsEnabled() *event*` diff --git a/docs/content/contracts/3.x/api/presets.mdx b/docs/content/contracts/3.x/api/presets.mdx new file mode 100644 index 00000000..b4110077 --- /dev/null +++ b/docs/content/contracts/3.x/api/presets.mdx @@ -0,0 +1,560 @@ +--- +title: Presets +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/presets + + +These contracts integrate different Ethereum standards (ERCs) with custom extensions and modules, showcasing common configurations that are ready to deploy ***without having to write any Solidity code***. + +They can be used as-is for quick prototyping and testing, but are ***also suitable for production environments***. + + +Intermediate and advanced users can use these as starting points when writing their own contracts, extending them with custom functionality as they see fit. + + +## Tokens + +### ``ERC20PresetMinterPauser`` + +ERC20 token, including: + +* ability for holders to burn (destroy) their tokens +* a minter role that allows for token minting (creation) +* a pauser role that allows to stop all token transfers + +This contract uses AccessControl to lock permissioned functions using the +different roles - head to its documentation for details. + +The account that deploys the contract will be granted the minter and pauser +roles, as well as the default admin role, which will let it grant both minter +and pauser roles to other accounts. + +**Functions** + +* [`constructor(name, symbol)`](#ERC20PresetMinterPauser-constructor-string-string-) +* [`mint(to, amount)`](#ERC20PresetMinterPauser-mint-address-uint256-) +* [`pause()`](#ERC20PresetMinterPauser-pause) +* [`unpause()`](#ERC20PresetMinterPauser-unpause) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20PresetMinterPauser-_beforeTokenTransfer-address-address-uint256-) + +**Pausable** + +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**ERC20Burnable** + +* [`burn(amount)`](#ERC20Burnable-burn-uint256-) +* [`burnFrom(account, amount)`](#ERC20Burnable-burnFrom-address-uint256-) + +**ERC20** + +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) + +**AccessControl** + +* [`hasRole(role, account)`](#AccessControl-hasRole-bytes32-address-) +* [`getRoleMemberCount(role)`](#AccessControl-getRoleMemberCount-bytes32-) +* [`getRoleMember(role, index)`](#AccessControl-getRoleMember-bytes32-uint256-) +* [`getRoleAdmin(role)`](#AccessControl-getRoleAdmin-bytes32-) +* [`grantRole(role, account)`](#AccessControl-grantRole-bytes32-address-) +* [`revokeRole(role, account)`](#AccessControl-revokeRole-bytes32-address-) +* [`renounceRole(role, account)`](#AccessControl-renounceRole-bytes32-address-) +* [`_setupRole(role, account)`](#AccessControl-_setupRole-bytes32-address-) +* [`_setRoleAdmin(role, adminRole)`](#AccessControl-_setRoleAdmin-bytes32-bytes32-) + +**Events** + +**Pausable** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + +**AccessControl** + +* [`RoleAdminChanged(role, previousAdminRole, newAdminRole)`](#AccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +* [`RoleGranted(role, account, sender)`](#AccessControl-RoleGranted-bytes32-address-address-) +* [`RoleRevoked(role, account, sender)`](#AccessControl-RoleRevoked-bytes32-address-address-) + + + +#### `constructor(string name, string symbol) *public*` + +Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the +account that deploys the contract. + +See ERC20-constructor. + + + +#### `mint(address to, uint256 amount) *public*` + +Creates `amount` new tokens for `to`. + +See ERC20-_mint. + +Requirements: + +* the caller must have the `MINTER_ROLE`. + + + +#### `pause() *public* + +Pauses all token transfers. + +See ERC20Pausable and Pausable-_pause. + +Requirements: + +*` the caller must have the `PAUSER_ROLE`. + + + +#### `unpause() *public* + +Unpauses all token transfers. + +See ERC20Pausable and Pausable-_unpause. + +Requirements: + +*` the caller must have the `PAUSER_ROLE`. + + + +#### `_beforeTokenTransfer(address from, address to, uint256 amount) *internal*` + +### ``ERC721PresetMinterPauserAutoId`` + +ERC721 token, including: + +* ability for holders to burn (destroy) their tokens +* a minter role that allows for token minting (creation) +* a pauser role that allows to stop all token transfers +* token ID and URI autogeneration + +This contract uses AccessControl to lock permissioned functions using the +different roles - head to its documentation for details. + +The account that deploys the contract will be granted the minter and pauser +roles, as well as the default admin role, which will let it grant both minter +and pauser roles to other accounts. + +**Functions** + +* [`constructor(name, symbol, baseURI)`](#ERC721PresetMinterPauserAutoId-constructor-string-string-string-) +* [`mint(to)`](#ERC721PresetMinterPauserAutoId-mint-address-) +* [`pause()`](#ERC721PresetMinterPauserAutoId-pause) +* [`unpause()`](#ERC721PresetMinterPauserAutoId-unpause) +* [`_beforeTokenTransfer(from, to, tokenId)`](#ERC721PresetMinterPauserAutoId-_beforeTokenTransfer-address-address-uint256-) + +**Pausable** + +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**ERC721Burnable** + +* [`burn(tokenId)`](#ERC721Burnable-burn-uint256-) + +**ERC721** + +* [`balanceOf(owner)`](#ERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#ERC721-ownerOf-uint256-) +* [`name()`](#ERC721-name) +* [`symbol()`](#ERC721-symbol) +* [`tokenURI(tokenId)`](#ERC721-tokenURI-uint256-) +* [`baseURI()`](#ERC721-baseURI) +* [`tokenOfOwnerByIndex(owner, index)`](#ERC721-tokenOfOwnerByIndex-address-uint256-) +* [`totalSupply()`](#ERC721-totalSupply) +* [`tokenByIndex(index)`](#ERC721-tokenByIndex-uint256-) +* [`approve(to, tokenId)`](#ERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#ERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#ERC721-isApprovedForAll-address-address-) +* [`transferFrom(from, to, tokenId)`](#ERC721-transferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#ERC721-safeTransferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId, _data)`](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +* [`_safeTransfer(from, to, tokenId, _data)`](#ERC721-_safeTransfer-address-address-uint256-bytes-) +* [`_exists(tokenId)`](#ERC721-_exists-uint256-) +* [`_isApprovedOrOwner(spender, tokenId)`](#ERC721-_isApprovedOrOwner-address-uint256-) +* [`_safeMint(to, tokenId)`](#ERC721-_safeMint-address-uint256-) +* [`_safeMint(to, tokenId, _data)`](#ERC721-_safeMint-address-uint256-bytes-) +* [`_mint(to, tokenId)`](#ERC721-_mint-address-uint256-) +* [`_burn(tokenId)`](#ERC721-_burn-uint256-) +* [`_transfer(from, to, tokenId)`](#ERC721-_transfer-address-address-uint256-) +* [`_setTokenURI(tokenId, _tokenURI)`](#ERC721-_setTokenURI-uint256-string-) +* [`_setBaseURI(baseURI_)`](#ERC721-_setBaseURI-string-) +* [`_approve(to, tokenId)`](#ERC721-_approve-address-uint256-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**AccessControl** + +* [`hasRole(role, account)`](#AccessControl-hasRole-bytes32-address-) +* [`getRoleMemberCount(role)`](#AccessControl-getRoleMemberCount-bytes32-) +* [`getRoleMember(role, index)`](#AccessControl-getRoleMember-bytes32-uint256-) +* [`getRoleAdmin(role)`](#AccessControl-getRoleAdmin-bytes32-) +* [`grantRole(role, account)`](#AccessControl-grantRole-bytes32-address-) +* [`revokeRole(role, account)`](#AccessControl-revokeRole-bytes32-address-) +* [`renounceRole(role, account)`](#AccessControl-renounceRole-bytes32-address-) +* [`_setupRole(role, account)`](#AccessControl-_setupRole-bytes32-address-) +* [`_setRoleAdmin(role, adminRole)`](#AccessControl-_setRoleAdmin-bytes32-bytes32-) + +**Events** + +**Pausable** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + +**IERC721** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + +**AccessControl** + +* [`RoleAdminChanged(role, previousAdminRole, newAdminRole)`](#AccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +* [`RoleGranted(role, account, sender)`](#AccessControl-RoleGranted-bytes32-address-address-) +* [`RoleRevoked(role, account, sender)`](#AccessControl-RoleRevoked-bytes32-address-address-) + + + +#### `constructor(string name, string symbol, string baseURI) *public*` + +Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the +account that deploys the contract. + +Token URIs will be autogenerated based on `baseURI` and their token IDs. +See ERC721-tokenURI. + + + +#### `mint(address to) *public*` + +Creates a new token for `to`. Its token ID will be automatically +assigned (and available on the emitted IERC721-Transfer event), and the token +URI autogenerated based on the base URI passed at construction. + +See ERC721-_mint. + +Requirements: + +* the caller must have the `MINTER_ROLE`. + + + +#### `pause() *public* + +Pauses all token transfers. + +See ERC721Pausable and Pausable-_pause. + +Requirements: + +*` the caller must have the `PAUSER_ROLE`. + + + +#### `unpause() *public* + +Unpauses all token transfers. + +See ERC721Pausable and Pausable-_unpause. + +Requirements: + +*` the caller must have the `PAUSER_ROLE`. + + + +#### `_beforeTokenTransfer(address from, address to, uint256 tokenId) *internal*` + +### ``ERC1155PresetMinterPauser`` + +ERC1155 token, including: + +* ability for holders to burn (destroy) their tokens +* a minter role that allows for token minting (creation) +* a pauser role that allows to stop all token transfers + +This contract uses AccessControl to lock permissioned functions using the +different roles - head to its documentation for details. + +The account that deploys the contract will be granted the minter and pauser +roles, as well as the default admin role, which will let it grant both minter +and pauser roles to other accounts. + +**Functions** + +* [`constructor(uri)`](#ERC1155PresetMinterPauser-constructor-string-) +* [`mint(to, id, amount, data)`](#ERC1155PresetMinterPauser-mint-address-uint256-uint256-bytes-) +* [`mintBatch(to, ids, amounts, data)`](#ERC1155PresetMinterPauser-mintBatch-address-uint256---uint256---bytes-) +* [`pause()`](#ERC1155PresetMinterPauser-pause) +* [`unpause()`](#ERC1155PresetMinterPauser-unpause) +* [`_beforeTokenTransfer(operator, from, to, ids, amounts, data)`](#ERC1155PresetMinterPauser-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) + +**Pausable** + +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**ERC1155Burnable** + +* [`burn(account, id, value)`](#ERC1155Burnable-burn-address-uint256-uint256-) +* [`burnBatch(account, ids, values)`](#ERC1155Burnable-burnBatch-address-uint256---uint256-) + +**ERC1155** + +* [`uri(_)`](#ERC1155-uri-uint256-) +* [`balanceOf(account, id)`](#ERC1155-balanceOf-address-uint256-) +* [`balanceOfBatch(accounts, ids)`](#ERC1155-balanceOfBatch-address---uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC1155-setApprovalForAll-address-bool-) +* [`isApprovedForAll(account, operator)`](#ERC1155-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, id, amount, data)`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +* [`safeBatchTransferFrom(from, to, ids, amounts, data)`](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +* [`_setURI(newuri)`](#ERC1155-_setURI-string-) +* [`_mint(account, id, amount, data)`](#ERC1155-_mint-address-uint256-uint256-bytes-) +* [`_mintBatch(to, ids, amounts, data)`](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +* [`_burn(account, id, amount)`](#ERC1155-_burn-address-uint256-uint256-) +* [`_burnBatch(account, ids, amounts)`](#ERC1155-_burnBatch-address-uint256---uint256-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**AccessControl** + +* [`hasRole(role, account)`](#AccessControl-hasRole-bytes32-address-) +* [`getRoleMemberCount(role)`](#AccessControl-getRoleMemberCount-bytes32-) +* [`getRoleMember(role, index)`](#AccessControl-getRoleMember-bytes32-uint256-) +* [`getRoleAdmin(role)`](#AccessControl-getRoleAdmin-bytes32-) +* [`grantRole(role, account)`](#AccessControl-grantRole-bytes32-address-) +* [`revokeRole(role, account)`](#AccessControl-revokeRole-bytes32-address-) +* [`renounceRole(role, account)`](#AccessControl-renounceRole-bytes32-address-) +* [`_setupRole(role, account)`](#AccessControl-_setupRole-bytes32-address-) +* [`_setRoleAdmin(role, adminRole)`](#AccessControl-_setRoleAdmin-bytes32-bytes32-) + +**Events** + +**Pausable** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + +**IERC1155** + +* [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +* [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch-address-address-address-uint256---uint256-) +* [`ApprovalForAll(account, operator, approved)`](#IERC1155-ApprovalForAll-address-address-bool-) +* [`URI(value, id)`](#IERC1155-URI-string-uint256-) + +**AccessControl** + +* [`RoleAdminChanged(role, previousAdminRole, newAdminRole)`](#AccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +* [`RoleGranted(role, account, sender)`](#AccessControl-RoleGranted-bytes32-address-address-) +* [`RoleRevoked(role, account, sender)`](#AccessControl-RoleRevoked-bytes32-address-address-) + + + +#### `constructor(string uri) *public*` + +Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, and `PAUSER_ROLE` to the account that +deploys the contract. + + + +#### `mint(address to, uint256 id, uint256 amount, bytes data) *public*` + +Creates `amount` new tokens for `to`, of token type `id`. + +See ERC1155-_mint. + +Requirements: + +* the caller must have the `MINTER_ROLE`. + + + +#### `mintBatch(address to, uint256[] ids, uint256[] amounts, bytes data) *public*` + +[Batched](/contracts/3.x/erc1155#batch-operations) variant of mint. + + + +#### `pause() *public* + +Pauses all token transfers. + +See ERC1155Pausable and Pausable-_pause. + +Requirements: + +*` the caller must have the `PAUSER_ROLE`. + + + +#### `unpause() *public* + +Unpauses all token transfers. + +See ERC1155Pausable and Pausable-_unpause. + +Requirements: + +*` the caller must have the `PAUSER_ROLE`. + + + +#### `_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data) *internal*` + +### ``ERC20PresetFixedSupply`` + +ERC20 token, including: + +* Preminted initial supply +* Ability for holders to burn (destroy) their tokens +* No access control mechanism (for minting/pausing) and hence no governance + +This contract uses ERC20Burnable to include burn capabilities - head to +its documentation for details. + +_Available since v3.4._ + +**Functions** + +* [`constructor(name, symbol, initialSupply, owner)`](#ERC20PresetFixedSupply-constructor-string-string-uint256-address-) + +**ERC20Burnable** + +* [`burn(amount)`](#ERC20Burnable-burn-uint256-) +* [`burnFrom(account, amount)`](#ERC20Burnable-burnFrom-address-uint256-) + +**ERC20** + +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20-_beforeTokenTransfer-address-address-uint256-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `constructor(string name, string symbol, uint256 initialSupply, address owner) *public*` + +Mints `initialSupply` amount of token and transfers them to `owner`. + +See ERC20-constructor. + +### ``ERC777PresetFixedSupply`` + +ERC777 token, including: + +* Preminted initial supply +* No access control mechanism (for minting/pausing) and hence no governance + +_Available since v3.4._ + +**Functions** + +* [`constructor(name, symbol, defaultOperators, initialSupply, owner)`](#ERC777PresetFixedSupply-constructor-string-string-address---uint256-address-) + +**ERC777** + +* [`name()`](#ERC777-name) +* [`symbol()`](#ERC777-symbol) +* [`decimals()`](#ERC777-decimals) +* [`granularity()`](#ERC777-granularity) +* [`totalSupply()`](#ERC777-totalSupply) +* [`balanceOf(tokenHolder)`](#ERC777-balanceOf-address-) +* [`send(recipient, amount, data)`](#ERC777-send-address-uint256-bytes-) +* [`transfer(recipient, amount)`](#ERC777-transfer-address-uint256-) +* [`burn(amount, data)`](#ERC777-burn-uint256-bytes-) +* [`isOperatorFor(operator, tokenHolder)`](#ERC777-isOperatorFor-address-address-) +* [`authorizeOperator(operator)`](#ERC777-authorizeOperator-address-) +* [`revokeOperator(operator)`](#ERC777-revokeOperator-address-) +* [`defaultOperators()`](#ERC777-defaultOperators) +* [`operatorSend(sender, recipient, amount, data, operatorData)`](#ERC777-operatorSend-address-address-uint256-bytes-bytes-) +* [`operatorBurn(account, amount, data, operatorData)`](#ERC777-operatorBurn-address-uint256-bytes-bytes-) +* [`allowance(holder, spender)`](#ERC777-allowance-address-address-) +* [`approve(spender, value)`](#ERC777-approve-address-uint256-) +* [`transferFrom(holder, recipient, amount)`](#ERC777-transferFrom-address-address-uint256-) +* [`_mint(account, amount, userData, operatorData)`](#ERC777-_mint-address-uint256-bytes-bytes-) +* [`_send(from, to, amount, userData, operatorData, requireReceptionAck)`](#ERC777-_send-address-address-uint256-bytes-bytes-bool-) +* [`_burn(from, amount, data, operatorData)`](#ERC777-_burn-address-uint256-bytes-bytes-) +* [`_approve(holder, spender, value)`](#ERC777-_approve-address-address-uint256-) +* [`_beforeTokenTransfer(operator, from, to, amount)`](#ERC777-_beforeTokenTransfer-address-address-address-uint256-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + +**IERC777** + +* [`Sent(operator, from, to, amount, data, operatorData)`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +* [`Minted(operator, to, amount, data, operatorData)`](#IERC777-Minted-address-address-uint256-bytes-bytes-) +* [`Burned(operator, from, amount, data, operatorData)`](#IERC777-Burned-address-address-uint256-bytes-bytes-) +* [`AuthorizedOperator(operator, tokenHolder)`](#IERC777-AuthorizedOperator-address-address-) +* [`RevokedOperator(operator, tokenHolder)`](#IERC777-RevokedOperator-address-address-) + + + +#### `constructor(string name, string symbol, address[] defaultOperators, uint256 initialSupply, address owner) *public*` + +Mints `initialSupply` amount of token and transfers them to `owner`. + +See ERC777-constructor. diff --git a/docs/content/contracts/3.x/api/proxy.mdx b/docs/content/contracts/3.x/api/proxy.mdx new file mode 100644 index 00000000..14679d1d --- /dev/null +++ b/docs/content/contracts/3.x/api/proxy.mdx @@ -0,0 +1,599 @@ +--- +title: Proxies +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/proxy + + +This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the [Proxy Upgrade Pattern](/upgrades-plugins/proxies) page. + +The abstract Proxy contract implements the core delegation functionality. If the concrete proxies that we provide below are not suitable, we encourage building on top of this base contract since it contains an assembly block that may be hard to get right. + +Upgradeability is implemented in the UpgradeableProxy contract, although it provides only an internal upgrade interface. For an upgrade interface exposed externally to an admin, we provide TransparentUpgradeableProxy. Both of these contracts use the storage slots specified in [EIP1967](https://eips.ethereum.org/EIPS/eip-1967) to avoid clashes with the storage of the implementation contract behind the proxy. + +An alternative upgradeability mechanism is provided in [Beacon](#beacon). This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. In this pattern, the proxy contract doesn’t hold the implementation address in storage like UpgradeableProxy, but the address of a UpgradeableBeacon contract, which is where the implementation address is actually stored and retrieved from. The `upgrade` operations that change the implementation contract address are then sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. + +The Clones library provides a way to deploy minimal non-upgradeable proxies for cheap. This can be useful for applications that require deploying many instances of the same contract (for example one per user, or one per task). These instances are designed to be both cheap to deploy, and cheap to call. The drawback being that they are not upgradeable. + + +Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the [OpenZeppelin Upgrades Plugins](/upgrades-plugins) for Truffle and Buidler. + + +## Core + +### ``Proxy`` + +This abstract contract provides a fallback function that delegates all calls to another contract using the EVM +instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to +be specified by overriding the virtual _implementation function. + +Additionally, delegation to the implementation can be triggered manually through the _fallback function, or to a +different contract through the _delegate function. + +The success and return data of the delegated call will be returned back to the caller of the proxy. + +**Functions** + +* [`_delegate(implementation)`](#Proxy-_delegate-address-) +* [`_implementation()`](#Proxy-_implementation) +* [`_fallback()`](#Proxy-_fallback) +* [`fallback()`](#Proxy-fallback) +* [`receive()`](#Proxy-receive) +* [`_beforeFallback()`](#Proxy-_beforeFallback) + + + +#### `_delegate(address implementation) *internal*` + +Delegates the current call to `implementation`. + +This function does not return to its internall call site, it will return directly to the external caller. + + + +#### `_implementation() → address *internal*` + +This is a virtual function that should be overriden so it returns the address to which the fallback function +and _fallback should delegate. + + + +#### `_fallback() *internal*` + +Delegates the current call to the address returned by `_implementation()`. + +This function does not return to its internall call site, it will return directly to the external caller. + + + +#### `fallback() *external*` + +Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other +function in the contract matches the call data. + + + +#### `receive() *external*` + +Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data +is empty. + + + +#### `_beforeFallback() *internal*` + +Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` +call, or as part of the Solidity `fallback` or `receive` functions. + +If overriden should call `super._beforeFallback()`. + +### ``UpgradeableProxy`` + +This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an +implementation address that can be changed. This address is stored in storage in the location specified by +[EIP1967](https://eips.ethereum.org/EIPS/eip-1967), so that it doesn’t conflict with the storage layout of the +implementation behind the proxy. + +Upgradeability is only provided internally through _upgradeTo. For an externally upgradeable proxy see +TransparentUpgradeableProxy. + +**Functions** + +* [`constructor(_logic, _data)`](#UpgradeableProxy-constructor-address-bytes-) +* [`_implementation()`](#UpgradeableProxy-_implementation) +* [`_upgradeTo(newImplementation)`](#UpgradeableProxy-_upgradeTo-address-) + +**Proxy** + +* [`_delegate(implementation)`](#Proxy-_delegate-address-) +* [`_fallback()`](#Proxy-_fallback) +* [`fallback()`](#Proxy-fallback) +* [`receive()`](#Proxy-receive) +* [`_beforeFallback()`](#Proxy-_beforeFallback) + +**Events** + +* [`Upgraded(implementation)`](#UpgradeableProxy-Upgraded-address-) + + + +#### `constructor(address _logic, bytes _data) *public*` + +Initializes the upgradeable proxy with an initial implementation specified by `_logic`. + +If `_data` is nonempty, it’s used as data in a delegate call to `_logic`. This will typically be an encoded +function call, and allows initializating the storage of the proxy like a Solidity constructor. + + + +#### `_implementation() → address impl *internal*` + +Returns the current implementation address. + + + +#### `_upgradeTo(address newImplementation) *internal*` + +Upgrades the proxy to a new implementation. + +Emits an Upgraded event. + + + +#### `Upgraded(address implementation) *event*` + +Emitted when the implementation is upgraded. + +### ``TransparentUpgradeableProxy`` + +This contract implements a proxy that is upgradeable by an admin. + +To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector +clashing], which can potentially be used in an attack, this contract uses the +[transparent proxy pattern](https://blog.openzeppelin.com/the-transparent-proxy-pattern/). This pattern implies two +things that go hand in hand: + +1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if +that call matches one of the admin functions exposed by the proxy itself. +2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the +implementation. If the admin tries to call a function on the implementation it will fail with an error that says +"admin cannot fallback to proxy target". + +These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing +the admin, so it’s best if it’s a dedicated account that is not used for anything else. This will avoid headaches due +to sudden errors when trying to call a function from the proxy implementation. + +Our recommendation is for the dedicated account to be an instance of the ProxyAdmin contract. If set up this way, +you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. + +**Modifiers** + +* [`ifAdmin()`](#TransparentUpgradeableProxy-ifAdmin) + +**Functions** + +* [`constructor(_logic, admin_, _data)`](#TransparentUpgradeableProxy-constructor-address-address-bytes-) +* [`admin()`](#TransparentUpgradeableProxy-admin) +* [`implementation()`](#TransparentUpgradeableProxy-implementation) +* [`changeAdmin(newAdmin)`](#TransparentUpgradeableProxy-changeAdmin-address-) +* [`upgradeTo(newImplementation)`](#TransparentUpgradeableProxy-upgradeTo-address-) +* [`upgradeToAndCall(newImplementation, data)`](#TransparentUpgradeableProxy-upgradeToAndCall-address-bytes-) +* [`_admin()`](#TransparentUpgradeableProxy-_admin) +* [`_beforeFallback()`](#TransparentUpgradeableProxy-_beforeFallback) + +**UpgradeableProxy** + +* [`_implementation()`](#UpgradeableProxy-_implementation) +* [`_upgradeTo(newImplementation)`](#UpgradeableProxy-_upgradeTo-address-) + +**Proxy** + +* [`_delegate(implementation)`](#Proxy-_delegate-address-) +* [`_fallback()`](#Proxy-_fallback) +* [`fallback()`](#Proxy-fallback) +* [`receive()`](#Proxy-receive) + +**Events** + +* [`AdminChanged(previousAdmin, newAdmin)`](#TransparentUpgradeableProxy-AdminChanged-address-address-) + +**UpgradeableProxy** + +* [`Upgraded(implementation)`](#UpgradeableProxy-Upgraded-address-) + + + +#### `ifAdmin() *modifier*` + +Modifier used internally that will delegate the call to the implementation unless the sender is the admin. + + + +#### `constructor(address _logic, address admin_, bytes _data) *public*` + +Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and +optionally initialized with `_data` as explained in UpgradeableProxy-constructor. + + + +#### `admin() → address admin_ *external*` + +Returns the current admin. + + +Only the admin can call this function. See ProxyAdmin-getProxyAdmin. + + + +To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the +[`eth_getStorageAt`](https://eth.wiki/json-rpc/API#eth_getstorageat) RPC call. +`0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + + + + +#### `implementation() → address implementation_ *external*` + +Returns the current implementation. + + +Only the admin can call this function. See ProxyAdmin-getProxyImplementation. + + + +To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the +[`eth_getStorageAt`](https://eth.wiki/json-rpc/API#eth_getstorageat) RPC call. +`0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` + + + + +#### `changeAdmin(address newAdmin) *external*` + +Changes the admin of the proxy. + +Emits an AdminChanged event. + + +Only the admin can call this function. See ProxyAdmin-changeProxyAdmin. + + + + +#### `upgradeTo(address newImplementation) *external*` + +Upgrade the implementation of the proxy. + + +Only the admin can call this function. See ProxyAdmin-upgrade. + + + + +#### `upgradeToAndCall(address newImplementation, bytes data) *external*` + +Upgrade the implementation of the proxy, and then call a function from the new implementation as specified +by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the +proxied contract. + + +Only the admin can call this function. See ProxyAdmin-upgradeAndCall. + + + + +#### `_admin() → address adm *internal*` + +Returns the current admin. + + + +#### `_beforeFallback() *internal*` + +Makes sure the admin cannot access the fallback function. See Proxy-_beforeFallback. + + + +#### `AdminChanged(address previousAdmin, address newAdmin) *event*` + +Emitted when the admin account has changed. + +## Beacon + +### ``BeaconProxy`` + +This contract implements a proxy that gets the implementation address for each call from a UpgradeableBeacon. + +The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn’t +conflict with the storage layout of the implementation behind the proxy. + +_Available since v3.4._ + +**Functions** + +* [`constructor(beacon, data)`](#BeaconProxy-constructor-address-bytes-) +* [`_beacon()`](#BeaconProxy-_beacon) +* [`_implementation()`](#BeaconProxy-_implementation) +* [`_setBeacon(beacon, data)`](#BeaconProxy-_setBeacon-address-bytes-) + +**Proxy** + +* [`_delegate(implementation)`](#Proxy-_delegate-address-) +* [`_fallback()`](#Proxy-_fallback) +* [`fallback()`](#Proxy-fallback) +* [`receive()`](#Proxy-receive) +* [`_beforeFallback()`](#Proxy-_beforeFallback) + + + +#### `constructor(address beacon, bytes data) *public*` + +Initializes the proxy with `beacon`. + +If `data` is nonempty, it’s used as data in a delegate call to the implementation returned by the beacon. This +will typically be an encoded function call, and allows initializating the storage of the proxy like a Solidity +constructor. + +Requirements: + +* `beacon` must be a contract with the interface IBeacon. + + + +#### `_beacon() → address beacon *internal*` + +Returns the current beacon address. + + + +#### `_implementation() → address *internal*` + +Returns the current implementation address of the associated beacon. + + + +#### `_setBeacon(address beacon, bytes data) *internal*` + +Changes the proxy to use a new beacon. + +If `data` is nonempty, it’s used as data in a delegate call to the implementation returned by the beacon. + +Requirements: + +* `beacon` must be a contract. +* The implementation returned by `beacon` must be a contract. + +### ``IBeacon`` + +This is the interface that BeaconProxy expects of its beacon. + +**Functions** + +* [`implementation()`](#IBeacon-implementation) + + + +#### `implementation() → address *external*` + +Must return an address that can be used as a delegate call target. + +BeaconProxy will check that this address is a contract. + +### ``UpgradeableBeacon`` + +This contract is used in conjunction with one or more instances of BeaconProxy to determine their +implementation contract, which is where they will delegate all function calls. + +An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. + +**Functions** + +* [`constructor(implementation_)`](#UpgradeableBeacon-constructor-address-) +* [`implementation()`](#UpgradeableBeacon-implementation) +* [`upgradeTo(newImplementation)`](#UpgradeableBeacon-upgradeTo-address-) + +**Ownable** + +* [`owner()`](#Ownable-owner) +* [`renounceOwnership()`](#Ownable-renounceOwnership) +* [`transferOwnership(newOwner)`](#Ownable-transferOwnership-address-) + +**Events** + +* [`Upgraded(implementation)`](#UpgradeableBeacon-Upgraded-address-) + +**Ownable** + +* [`OwnershipTransferred(previousOwner, newOwner)`](#Ownable-OwnershipTransferred-address-address-) + + + +#### `constructor(address implementation_) *public*` + +Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the +beacon. + + + +#### `implementation() → address *public*` + +Returns the current implementation address. + + + +#### `upgradeTo(address newImplementation) *public* + +Upgrades the beacon to a new implementation. + +Emits an Upgraded event. + +Requirements: + +* msg.sender must be the owner of the contract. +*` `newImplementation` must be a contract. + + + +#### `Upgraded(address implementation) *event*` + +Emitted when the implementation returned by the beacon is changed. + +## Minimal Clones + +### ``Clones`` + +[EIP 1167](https://eips.ethereum.org/EIPS/eip-1167) is a standard for +deploying minimal proxy contracts, also known as "clones". + +> To simply and cheaply clone contract functionality in an immutable way, this standard specifies +> a minimal bytecode implementation that delegates all calls to a known, fixed address. + +The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` +(salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the +deterministic method. + +_Available since v3.4._ + +**Functions** + +* [`clone(master)`](#Clones-clone-address-) +* [`cloneDeterministic(master, salt)`](#Clones-cloneDeterministic-address-bytes32-) +* [`predictDeterministicAddress(master, salt, deployer)`](#Clones-predictDeterministicAddress-address-bytes32-address-) +* [`predictDeterministicAddress(master, salt)`](#Clones-predictDeterministicAddress-address-bytes32-) + + + +#### `clone(address master) → address instance *internal*` + +Deploys and returns the address of a clone that mimics the behaviour of `master`. + +This function uses the create opcode, which should never revert. + + + +#### `cloneDeterministic(address master, bytes32 salt) → address instance *internal*` + +Deploys and returns the address of a clone that mimics the behaviour of `master`. + +This function uses the create2 opcode and a `salt` to deterministically deploy +the clone. Using the same `master` and `salt` multiple time will revert, since +the clones cannot be deployed twice at the same address. + + + +#### `predictDeterministicAddress(address master, bytes32 salt, address deployer) → address predicted *internal*` + +Computes the address of a clone deployed using Clones-cloneDeterministic. + + + +#### `predictDeterministicAddress(address master, bytes32 salt) → address predicted *internal*` + +Computes the address of a clone deployed using Clones-cloneDeterministic. + +## Utilities + +### ``Initializable`` + +This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed +behind a proxy. Since a proxied contract can’t have a constructor, it’s common to move constructor logic to an +external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer +function so it can only be called once. The initializer modifier provided by this contract will have this effect. + + +To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as +possible by providing the encoded function call as the `_data` argument to UpgradeableProxy-constructor. + + + +When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure +that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + + +**Modifiers** + +* [`initializer()`](#Initializable-initializer) + + + +#### `initializer() *modifier*` + +Modifier to protect an initializer function from being invoked twice. + +### ``ProxyAdmin`` + +This is an auxiliary contract meant to be assigned as the admin of a TransparentUpgradeableProxy. For an +explanation of why you would want to use this see the documentation for TransparentUpgradeableProxy. + +**Functions** + +* [`getProxyImplementation(proxy)`](#ProxyAdmin-getProxyImplementation-contract-TransparentUpgradeableProxy-) +* [`getProxyAdmin(proxy)`](#ProxyAdmin-getProxyAdmin-contract-TransparentUpgradeableProxy-) +* [`changeProxyAdmin(proxy, newAdmin)`](#ProxyAdmin-changeProxyAdmin-contract-TransparentUpgradeableProxy-address-) +* [`upgrade(proxy, implementation)`](#ProxyAdmin-upgrade-contract-TransparentUpgradeableProxy-address-) +* [`upgradeAndCall(proxy, implementation, data)`](#ProxyAdmin-upgradeAndCall-contract-TransparentUpgradeableProxy-address-bytes-) + +**Ownable** + +* [`constructor()`](#Ownable-constructor) +* [`owner()`](#Ownable-owner) +* [`renounceOwnership()`](#Ownable-renounceOwnership) +* [`transferOwnership(newOwner)`](#Ownable-transferOwnership-address-) + +**Events** + +**Ownable** + +* [`OwnershipTransferred(previousOwner, newOwner)`](#Ownable-OwnershipTransferred-address-address-) + + + +#### `getProxyImplementation(contract TransparentUpgradeableProxy proxy) → address *public*` + +Returns the current implementation of `proxy`. + +Requirements: + +* This contract must be the admin of `proxy`. + + + +#### `getProxyAdmin(contract TransparentUpgradeableProxy proxy) → address *public*` + +Returns the current admin of `proxy`. + +Requirements: + +* This contract must be the admin of `proxy`. + + + +#### `changeProxyAdmin(contract TransparentUpgradeableProxy proxy, address newAdmin) *public*` + +Changes the admin of `proxy` to `newAdmin`. + +Requirements: + +* This contract must be the current admin of `proxy`. + + + +#### `upgrade(contract TransparentUpgradeableProxy proxy, address implementation) *public*` + +Upgrades `proxy` to `implementation`. See TransparentUpgradeableProxy-upgradeTo. + +Requirements: + +* This contract must be the admin of `proxy`. + + + +#### `upgradeAndCall(contract TransparentUpgradeableProxy proxy, address implementation, bytes data) *public*` + +Upgrades `proxy` to `implementation` and calls a function on the new implementation. See +TransparentUpgradeableProxy-upgradeToAndCall. + +Requirements: + +* This contract must be the admin of `proxy`. diff --git a/docs/content/contracts/3.x/api/token/ERC1155.mdx b/docs/content/contracts/3.x/api/token/ERC1155.mdx new file mode 100644 index 00000000..2a73ea75 --- /dev/null +++ b/docs/content/contracts/3.x/api/token/ERC1155.mdx @@ -0,0 +1,576 @@ +--- +title: ERC 1155 +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc1155 + + +This set of interfaces and contracts are all related to the [ERC1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155). + +The EIP consists of three interfaces which fulfill different roles, found here as IERC1155, IERC1155MetadataURI and IERC1155Receiver. + +ERC1155 implements the mandatory IERC1155 interface, as well as the optional extension IERC1155MetadataURI, by relying on the substitution mechanism to use the same URI for all token types, dramatically reducing gas costs. + +Additionally there are multiple custom extensions, including: + +* designation of addresses that can pause token transfers for all users (ERC1155Pausable). +* destruction of own tokens (ERC1155Burnable). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC1155 (such as [`_mint`](#`_mint(address-account,-uint256-id,-uint256-amount,-bytes-data) *internal*`)) and expose them as external functions in the way they prefer. On the other hand, [ERC1155 Presets](/contracts/3.x/erc1155#preset-erc1155-contract) (such as ERC1155PresetMinterPauser) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. + + +## Core + +### ``IERC1155`` + +Required interface of an ERC1155 compliant contract, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1155). + +_Available since v3.1._ + +**Functions** + +* [`balanceOf(account, id)`](#IERC1155-balanceOf-address-uint256-) +* [`balanceOfBatch(accounts, ids)`](#IERC1155-balanceOfBatch-address---uint256-) +* [`setApprovalForAll(operator, approved)`](#IERC1155-setApprovalForAll-address-bool-) +* [`isApprovedForAll(account, operator)`](#IERC1155-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, id, amount, data)`](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +* [`safeBatchTransferFrom(from, to, ids, amounts, data)`](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) + +**IERC165** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + +**Events** + +* [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +* [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch-address-address-address-uint256---uint256-) +* [`ApprovalForAll(account, operator, approved)`](#IERC1155-ApprovalForAll-address-address-bool-) +* [`URI(value, id)`](#IERC1155-URI-string-uint256-) + + + +#### `balanceOf(address account, uint256 id) → uint256 *external*` + +Returns the amount of tokens of token type `id` owned by `account`. + +Requirements: + +* `account` cannot be the zero address. + + + +#### `balanceOfBatch(address[] accounts, uint256[] ids) → uint256[] *external* + +[Batched](/contracts/3.x/erc1155#batch-operations) version of balanceOf. + +Requirements: + +*` `accounts` and `ids` must have the same length. + + + +#### `setApprovalForAll(address operator, bool approved) *external*` + +Grants or revokes permission to `operator` to transfer the caller’s tokens, according to `approved`, + +Emits an ApprovalForAll event. + +Requirements: + +* `operator` cannot be the caller. + + + +#### `isApprovedForAll(address account, address operator) → bool *external*` + +Returns true if `operator` is approved to transfer ``account`’s tokens. + +See setApprovalForAll. + + + +#### `safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) *external*` + +Transfers `amount` tokens of token type `id` from `from` to `to`. + +Emits a TransferSingle event. + +Requirements: + +* `to` cannot be the zero address. +* If the caller is not `from`, it must be have been approved to spend ``from`’s tokens via setApprovalForAll. +* `from` must have a balance of tokens of type `id` of at least `amount`. +* If `to` refers to a smart contract, it must implement IERC1155Receiver-onERC1155Received and return the +acceptance magic value. + + + +#### `safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data) *external* + +[Batched](/contracts/3.x/erc1155#batch-operations) version of safeTransferFrom. + +Emits a TransferBatch event. + +Requirements: + +*` `ids` and `amounts` must have the same length. +* If `to` refers to a smart contract, it must implement IERC1155Receiver-onERC1155BatchReceived and return the +acceptance magic value. + + + +#### `TransferSingle(address operator, address from, address to, uint256 id, uint256 value) *event*` + +Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + + + +#### `TransferBatch(address operator, address from, address to, uint256[] ids, uint256[] values) *event*` + +Equivalent to multiple TransferSingle events, where `operator`, `from` and `to` are the same for all +transfers. + + + +#### `ApprovalForAll(address account, address operator, bool approved) *event*` + +Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to +`approved`. + + + +#### `URI(string value, uint256 id) *event*` + +Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + +If an URI event was emitted for `id`, the standard +[guarantees](https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions) that `value` will equal the value +returned by IERC1155MetadataURI-uri. + +### ``IERC1155MetadataURI`` + +Interface of the optional ERC1155MetadataExtension interface, as defined +in the [EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions). + +_Available since v3.1._ + +**Functions** + +* [`uri(id)`](#IERC1155MetadataURI-uri-uint256-) + +**IERC1155** + +* [`balanceOf(account, id)`](#IERC1155-balanceOf-address-uint256-) +* [`balanceOfBatch(accounts, ids)`](#IERC1155-balanceOfBatch-address---uint256-) +* [`setApprovalForAll(operator, approved)`](#IERC1155-setApprovalForAll-address-bool-) +* [`isApprovedForAll(account, operator)`](#IERC1155-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, id, amount, data)`](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +* [`safeBatchTransferFrom(from, to, ids, amounts, data)`](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) + +**IERC165** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + +**Events** + +**IERC1155** + +* [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +* [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch-address-address-address-uint256---uint256-) +* [`ApprovalForAll(account, operator, approved)`](#IERC1155-ApprovalForAll-address-address-bool-) +* [`URI(value, id)`](#IERC1155-URI-string-uint256-) + + + +#### `uri(uint256 id) → string *external*` + +Returns the URI for token type `id`. + +If the `\id\` substring is present in the URI, it must be replaced by +clients with the actual token type ID. + +### ``ERC1155`` + +Implementation of the basic standard multi-token. +See https://eips.ethereum.org/EIPS/eip-1155 +Originally based on code by Enjin: https://github.com/enjin/erc-1155 + +_Available since v3.1._ + +**Functions** + +* [`constructor(uri_)`](#ERC1155-constructor-string-) +* [`uri(_)`](#ERC1155-uri-uint256-) +* [`balanceOf(account, id)`](#ERC1155-balanceOf-address-uint256-) +* [`balanceOfBatch(accounts, ids)`](#ERC1155-balanceOfBatch-address---uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC1155-setApprovalForAll-address-bool-) +* [`isApprovedForAll(account, operator)`](#ERC1155-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, id, amount, data)`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +* [`safeBatchTransferFrom(from, to, ids, amounts, data)`](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +* [`_setURI(newuri)`](#ERC1155-_setURI-string-) +* [`_mint(account, id, amount, data)`](#ERC1155-_mint-address-uint256-uint256-bytes-) +* [`_mintBatch(to, ids, amounts, data)`](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +* [`_burn(account, id, amount)`](#ERC1155-_burn-address-uint256-uint256-) +* [`_burnBatch(account, ids, amounts)`](#ERC1155-_burnBatch-address-uint256---uint256-) +* [`_beforeTokenTransfer(operator, from, to, ids, amounts, data)`](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**Events** + +**IERC1155** + +* [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +* [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch-address-address-address-uint256---uint256-) +* [`ApprovalForAll(account, operator, approved)`](#IERC1155-ApprovalForAll-address-address-bool-) +* [`URI(value, id)`](#IERC1155-URI-string-uint256-) + + + +#### `constructor(string uri_) *public*` + +See _setURI. + + + +#### `uri(uint256) → string *external* + +See IERC1155MetadataURI-uri. + +This implementation returns the same URI for **all**` token types. It relies +on the token type ID substitution mechanism +[defined in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +Clients calling this function must replace the `\id\` substring with the +actual token type ID. + + + +#### `balanceOf(address account, uint256 id) → uint256 *public* + +See IERC1155-balanceOf. + +Requirements: + +*` `account` cannot be the zero address. + + + +#### `balanceOfBatch(address[] accounts, uint256[] ids) → uint256[] *public* + +See IERC1155-balanceOfBatch. + +Requirements: + +*` `accounts` and `ids` must have the same length. + + + +#### `setApprovalForAll(address operator, bool approved) *public*` + +See IERC1155-setApprovalForAll. + + + +#### `isApprovedForAll(address account, address operator) → bool *public*` + +See IERC1155-isApprovedForAll. + + + +#### `safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data) *public*` + +See IERC1155-safeTransferFrom. + + + +#### `safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data) *public*` + +See IERC1155-safeBatchTransferFrom. + + + +#### `_setURI(string newuri) *internal*` + +Sets a new URI for all token types, by relying on the token type ID +substitution mechanism +[defined in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +By this mechanism, any occurrence of the `\id\` substring in either the +URI or any of the amounts in the JSON file at said URI will be replaced by +clients with the token type ID. + +For example, the `https://token-cdn-domain/\id\.json` URI would be +interpreted by clients as +`https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` +for token type ID 0x4cce0. + +See uri. + +Because these URIs cannot be meaningfully represented by the URI event, +this function emits no events. + + + +#### `_mint(address account, uint256 id, uint256 amount, bytes data) *internal*` + +Creates `amount` tokens of token type `id`, and assigns them to `account`. + +Emits a TransferSingle event. + +Requirements: + +* `account` cannot be the zero address. +* If `account` refers to a smart contract, it must implement IERC1155Receiver-onERC1155Received and return the +acceptance magic value. + + + +#### `_mintBatch(address to, uint256[] ids, uint256[] amounts, bytes data) *internal* + +[Batched](/contracts/3.x/erc1155#batch-operations) version of _mint. + +Requirements: + +*` `ids` and `amounts` must have the same length. +* If `to` refers to a smart contract, it must implement IERC1155Receiver-onERC1155BatchReceived and return the +acceptance magic value. + + + +#### `_burn(address account, uint256 id, uint256 amount) *internal*` + +Destroys `amount` tokens of token type `id` from `account` + +Requirements: + +* `account` cannot be the zero address. +* `account` must have at least `amount` tokens of token type `id`. + + + +#### `_burnBatch(address account, uint256[] ids, uint256[] amounts) *internal* + +[Batched](/contracts/3.x/erc1155#batch-operations) version of _burn. + +Requirements: + +*` `ids` and `amounts` must have the same length. + + + +#### `_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data) *internal*` + +Hook that is called before any token transfer. This includes minting +and burning, as well as batched variants. + +The same hook is called on both single and batched variants. For single +transfers, the length of the `id` and `amount` arrays will be 1. + +Calling conditions (for each `id` and `amount` pair): + +* When `from` and `to` are both non-zero, `amount` of ``from`’s tokens +of token type `id` will be transferred to `to`. +* When `from` is zero, `amount` tokens of token type `id` will be minted +for `to`. +* when `to` is zero, `amount` of ``from`’s tokens of token type `id` +will be burned. +* `from` and `to` are never both zero. +* `ids` and `amounts` have the same, non-zero length. + +To learn more about hooks, head to [Using Hooks](/contracts/3.x/extending-contracts#using-hooks). + +### ``IERC1155Receiver`` + +**Functions** + +* [`onERC1155Received(operator, from, id, value, data)`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) +* [`onERC1155BatchReceived(operator, from, ids, values, data)`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) + +**IERC165** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + + + +#### `onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes data) → bytes4 *external*` + +Handles the receipt of a single ERC1155 token type. This function is + called at the end of a `safeTransferFrom` after the balance has been updated. + To accept the transfer, this must return + `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + (i.e. 0xf23a6e61, or its own function selector). + @param operator The address which initiated the transfer (i.e. msg.sender) + @param from The address which previously owned the token + @param id The ID of the token being transferred + @param value The amount of tokens being transferred + @param data Additional data with no specified format + @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed + + + +#### `onERC1155BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data) → bytes4 *external*` + +Handles the receipt of a multiple ERC1155 token types. This function + is called at the end of a `safeBatchTransferFrom` after the balances have + been updated. To accept the transfer(s), this must return + `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + (i.e. 0xbc197c81, or its own function selector). + @param operator The address which initiated the batch transfer (i.e. msg.sender) + @param from The address which previously owned the token + @param ids An array containing ids of each token being transferred (order and length must match values array) + @param values An array containing amounts of each token being transferred (order and length must match ids array) + @param data Additional data with no specified format + @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed + +## Extensions + +### ``ERC1155Pausable`` + +ERC1155 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + +_Available since v3.1._ + +**Functions** + +* [`_beforeTokenTransfer(operator, from, to, ids, amounts, data)`](#ERC1155Pausable-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) + +**Pausable** + +* [`constructor()`](#Pausable-constructor) +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**ERC1155** + +* [`uri(_)`](#ERC1155-uri-uint256-) +* [`balanceOf(account, id)`](#ERC1155-balanceOf-address-uint256-) +* [`balanceOfBatch(accounts, ids)`](#ERC1155-balanceOfBatch-address---uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC1155-setApprovalForAll-address-bool-) +* [`isApprovedForAll(account, operator)`](#ERC1155-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, id, amount, data)`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +* [`safeBatchTransferFrom(from, to, ids, amounts, data)`](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +* [`_setURI(newuri)`](#ERC1155-_setURI-string-) +* [`_mint(account, id, amount, data)`](#ERC1155-_mint-address-uint256-uint256-bytes-) +* [`_mintBatch(to, ids, amounts, data)`](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +* [`_burn(account, id, amount)`](#ERC1155-_burn-address-uint256-uint256-) +* [`_burnBatch(account, ids, amounts)`](#ERC1155-_burnBatch-address-uint256---uint256-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**Events** + +**Pausable** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + +**IERC1155** + +* [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +* [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch-address-address-address-uint256---uint256-) +* [`ApprovalForAll(account, operator, approved)`](#IERC1155-ApprovalForAll-address-address-bool-) +* [`URI(value, id)`](#IERC1155-URI-string-uint256-) + + + +#### `_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data) *internal* + +See ERC1155-_beforeTokenTransfer. + +Requirements: + +*` the contract must not be paused. + +### ``ERC1155Burnable`` + +Extension of ERC1155 that allows token holders to destroy both their +own tokens and those that they have been approved to use. + +_Available since v3.1._ + +**Functions** + +* [`burn(account, id, value)`](#ERC1155Burnable-burn-address-uint256-uint256-) +* [`burnBatch(account, ids, values)`](#ERC1155Burnable-burnBatch-address-uint256---uint256-) + +**ERC1155** + +* [`constructor(uri_)`](#ERC1155-constructor-string-) +* [`uri(_)`](#ERC1155-uri-uint256-) +* [`balanceOf(account, id)`](#ERC1155-balanceOf-address-uint256-) +* [`balanceOfBatch(accounts, ids)`](#ERC1155-balanceOfBatch-address---uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC1155-setApprovalForAll-address-bool-) +* [`isApprovedForAll(account, operator)`](#ERC1155-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, id, amount, data)`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +* [`safeBatchTransferFrom(from, to, ids, amounts, data)`](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +* [`_setURI(newuri)`](#ERC1155-_setURI-string-) +* [`_mint(account, id, amount, data)`](#ERC1155-_mint-address-uint256-uint256-bytes-) +* [`_mintBatch(to, ids, amounts, data)`](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +* [`_burn(account, id, amount)`](#ERC1155-_burn-address-uint256-uint256-) +* [`_burnBatch(account, ids, amounts)`](#ERC1155-_burnBatch-address-uint256---uint256-) +* [`_beforeTokenTransfer(operator, from, to, ids, amounts, data)`](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**Events** + +**IERC1155** + +* [`TransferSingle(operator, from, to, id, value)`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +* [`TransferBatch(operator, from, to, ids, values)`](#IERC1155-TransferBatch-address-address-address-uint256---uint256-) +* [`ApprovalForAll(account, operator, approved)`](#IERC1155-ApprovalForAll-address-address-bool-) +* [`URI(value, id)`](#IERC1155-URI-string-uint256-) + + + +#### `burn(address account, uint256 id, uint256 value) *public*` + + + +#### `burnBatch(address account, uint256[] ids, uint256[] values) *public*` + +## Convenience + +### ``ERC1155Holder`` + +_Available since v3.1._ + +**Functions** + +* [`onERC1155Received(_, _, _, _, _)`](#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-) +* [`onERC1155BatchReceived(_, _, _, _, _)`](#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) + +**ERC1155Receiver** + +* [`constructor()`](#ERC1155Receiver-constructor) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + + + +#### `onERC1155Received(address, address, uint256, uint256, bytes) → bytes4 *public*` + + + +#### `onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) → bytes4 *public*` diff --git a/docs/content/contracts/3.x/api/token/ERC20.mdx b/docs/content/contracts/3.x/api/token/ERC20.mdx new file mode 100644 index 00000000..17cd31ce --- /dev/null +++ b/docs/content/contracts/3.x/api/token/ERC20.mdx @@ -0,0 +1,783 @@ +--- +title: ERC 20 +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc20 + + +This set of interfaces, contracts, and utilities are all related to the [ERC20 Token Standard](https://eips.ethereum.org/EIPS/eip-20). + + +For an overview of ERC20 tokens and a walk through on how to create a token contract read our [ERC20 guide](/contracts/3.x/erc20). + + +There a few core contracts that implement the behavior specified in the EIP: + +* IERC20: the interface all ERC20 implementations should conform to. +* ERC20: the implementation of the ERC20 interface, including the [`name`](#ERC20-name), [`symbol`](#ERC20-symbol) and [`decimals`](#ERC20-decimals) optional standard extension to the base interface. + +Additionally there are multiple custom extensions, including: + +* ERC20Permit: gasless approval of tokens. +* ERC20Snapshot: efficient storage of past token balances to be later queried at any point in time. +* ERC20Burnable: destruction of own tokens. +* ERC20Capped: enforcement of a cap to the total supply when minting tokens. +* ERC20Pausable: ability to pause token transfers. + +Finally, there are some utilities to interact with ERC20 contracts in various ways. + +* SafeERC20: a wrapper around the interface that eliminates the need to handle boolean return values. +* TokenTimelock: hold tokens for a beneficiary until a specified time. + +The following related EIPs are in draft status and can be found in the drafts directory. + +* IERC20Permit +* ERC20Permit + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as [`_mint`](#`_mint(address-account,-uint256-amount) *internal*`)) and expose them as external functions in the way they prefer. On the other hand, [ERC20 Presets](/contracts/3.x/erc20#preset-erc20-contract) (such as ERC20PresetMinterPauser) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. + + +## Core + +### ``IERC20`` + +Interface of the ERC20 standard as defined in the EIP. + +**Functions** + +* [`totalSupply()`](#IERC20-totalSupply) +* [`balanceOf(account)`](#IERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#IERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#IERC20-allowance-address-address-) +* [`approve(spender, amount)`](#IERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#IERC20-transferFrom-address-address-uint256-) + +**Events** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `totalSupply() → uint256 *external*` + +Returns the amount of tokens in existence. + + + +#### `balanceOf(address account) → uint256 *external*` + +Returns the amount of tokens owned by `account`. + + + +#### `transfer(address recipient, uint256 amount) → bool *external*` + +Moves `amount` tokens from the caller’s account to `recipient`. + +Returns a boolean value indicating whether the operation succeeded. + +Emits a Transfer event. + + + +#### `allowance(address owner, address spender) → uint256 *external*` + +Returns the remaining number of tokens that `spender` will be +allowed to spend on behalf of `owner` through transferFrom. This is +zero by default. + +This value changes when approve or transferFrom are called. + + + +#### `approve(address spender, uint256 amount) → bool *external*` + +Sets `amount` as the allowance of `spender` over the caller’s tokens. + +Returns a boolean value indicating whether the operation succeeded. + + +Beware that changing an allowance with this method brings the risk +that someone may use both the old and the new allowance by unfortunate +transaction ordering. One possible solution to mitigate this race +condition is to first reduce the spender’s allowance to 0 and set the +desired value afterwards: +https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + + +Emits an Approval event. + + + +#### `transferFrom(address sender, address recipient, uint256 amount) → bool *external*` + +Moves `amount` tokens from `sender` to `recipient` using the +allowance mechanism. `amount` is then deducted from the caller’s +allowance. + +Returns a boolean value indicating whether the operation succeeded. + +Emits a Transfer event. + + + +#### `Transfer(address from, address to, uint256 value) *event*` + +Emitted when `value` tokens are moved from one account (`from`) to +another (`to`). + +Note that `value` may be zero. + + + +#### `Approval(address owner, address spender, uint256 value) *event*` + +Emitted when the allowance of a `spender` for an `owner` is set by +a call to approve. `value` is the new allowance. + +### ``ERC20`` + +Implementation of the IERC20 interface. + +This implementation is agnostic to the way tokens are created. This means +that a supply mechanism has to be added in a derived contract using _mint. +For a generic mechanism see ERC20PresetMinterPauser. + + +For a detailed writeup see our guide +https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How +to implement supply mechanisms]. + + +We have followed general OpenZeppelin guidelines: functions revert instead +of returning `false` on failure. This behavior is nonetheless conventional +and does not conflict with the expectations of ERC20 applications. + +Additionally, an Approval event is emitted on calls to transferFrom. +This allows applications to reconstruct the allowance for all accounts just +by listening to said events. Other implementations of the EIP may not emit +these events, as it isn’t required by the specification. + +Finally, the non-standard decreaseAllowance and increaseAllowance +functions have been added to mitigate the well-known issues around setting +allowances. See IERC20-approve. + +**Functions** + +* [`constructor(name_, symbol_)`](#ERC20-constructor-string-string-) +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20-_beforeTokenTransfer-address-address-uint256-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `constructor(string name_, string symbol_) *public*` + +Sets the values for name and symbol, initializes decimals with +a default value of 18. + +To select a different value for decimals, use _setupDecimals. + +All three of these values are immutable: they can only be set once during +construction. + + + +#### `name() → string *public*` + +Returns the name of the token. + + + +#### `symbol() → string *public*` + +Returns the symbol of the token, usually a shorter version of the +name. + + + +#### `decimals() → uint8 *public*` + +Returns the number of decimals used to get its user representation. +For example, if `decimals` equals `2`, a balance of `505` tokens should +be displayed to a user as `5,05` (`505 / 10 ** 2`). + +Tokens usually opt for a value of 18, imitating the relationship between +Ether and Wei. This is the value ERC20 uses, unless _setupDecimals is +called. + + +This information is only used for _display_ purposes: it in +no way affects any of the arithmetic of the contract, including +IERC20-balanceOf and IERC20-transfer. + + + + +#### `totalSupply() → uint256 *public*` + +See IERC20-totalSupply. + + + +#### `balanceOf(address account) → uint256 *public*` + +See IERC20-balanceOf. + + + +#### `transfer(address recipient, uint256 amount) → bool *public* + +See IERC20-transfer. + +Requirements: + +*` `recipient` cannot be the zero address. +* the caller must have a balance of at least `amount`. + + + +#### `allowance(address owner, address spender) → uint256 *public*` + +See IERC20-allowance. + + + +#### `approve(address spender, uint256 amount) → bool *public* + +See IERC20-approve. + +Requirements: + +*` `spender` cannot be the zero address. + + + +#### `transferFrom(address sender, address recipient, uint256 amount) → bool *public* + +See IERC20-transferFrom. + +Emits an Approval event indicating the updated allowance. This is not +required by the EIP. See the note at the beginning of ERC20. + +Requirements: + +*` `sender` and `recipient` cannot be the zero address. +* `sender` must have a balance of at least `amount`. +* the caller must have allowance for ``sender`’s tokens of at least +`amount`. + + + +#### `increaseAllowance(address spender, uint256 addedValue) → bool *public*` + +Atomically increases the allowance granted to `spender` by the caller. + +This is an alternative to approve that can be used as a mitigation for +problems described in IERC20-approve. + +Emits an Approval event indicating the updated allowance. + +Requirements: + +* `spender` cannot be the zero address. + + + +#### `decreaseAllowance(address spender, uint256 subtractedValue) → bool *public*` + +Atomically decreases the allowance granted to `spender` by the caller. + +This is an alternative to approve that can be used as a mitigation for +problems described in IERC20-approve. + +Emits an Approval event indicating the updated allowance. + +Requirements: + +* `spender` cannot be the zero address. +* `spender` must have allowance for the caller of at least +`subtractedValue`. + + + +#### `_transfer(address sender, address recipient, uint256 amount) *internal*` + +Moves tokens `amount` from `sender` to `recipient`. + +This is internal function is equivalent to transfer, and can be used to +e.g. implement automatic token fees, slashing mechanisms, etc. + +Emits a Transfer event. + +Requirements: + +* `sender` cannot be the zero address. +* `recipient` cannot be the zero address. +* `sender` must have a balance of at least `amount`. + + + +#### `_mint(address account, uint256 amount) *internal*` + +Creates `amount` tokens and assigns them to `account`, increasing +the total supply. + +Emits a Transfer event with `from` set to the zero address. + +Requirements: + +* `to` cannot be the zero address. + + + +#### `_burn(address account, uint256 amount) *internal*` + +Destroys `amount` tokens from `account`, reducing the +total supply. + +Emits a Transfer event with `to` set to the zero address. + +Requirements: + +* `account` cannot be the zero address. +* `account` must have at least `amount` tokens. + + + +#### `_approve(address owner, address spender, uint256 amount) *internal*` + +Sets `amount` as the allowance of `spender` over the `owner` s tokens. + +This internal function is equivalent to `approve`, and can be used to +e.g. set automatic allowances for certain subsystems, etc. + +Emits an Approval event. + +Requirements: + +* `owner` cannot be the zero address. +* `spender` cannot be the zero address. + + + +#### `_setupDecimals(uint8 decimals_) *internal*` + +Sets decimals to a value other than the default one of 18. + + +This function should only be called from the constructor. Most +applications that interact with token contracts will not expect +decimals to ever change, and may work incorrectly if it does. + + + + +#### `_beforeTokenTransfer(address from, address to, uint256 amount) *internal* + +Hook that is called before any transfer of tokens. This includes +minting and burning. + +Calling conditions: + +*` when `from` and `to` are both non-zero, `amount` of ``from`’s tokens +will be to transferred to `to`. +* when `from` is zero, `amount` tokens will be minted for `to`. +* when `to` is zero, `amount` of ``from`’s tokens will be burned. +* `from` and `to` are never both zero. + +To learn more about hooks, head to [Using Hooks](/contracts/3.x/extending-contracts#using-hooks). + +## Extensions + +### ``ERC20Snapshot`` + +This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and +total supply at the time are recorded for later access. + +This can be used to safely create mechanisms based on token balances such as trustless dividends or weighted voting. +In naive implementations it’s possible to perform a "double spend" attack by reusing the same balance from different +accounts. By using snapshots to calculate dividends or voting power, those attacks no longer apply. It can also be +used to create an efficient ERC20 forking mechanism. + +Snapshots are created by the internal _snapshot function, which will emit the Snapshot event and return a +snapshot id. To get the total supply at the time of a snapshot, call the function totalSupplyAt with the snapshot +id. To get the balance of an account at the time of a snapshot, call the balanceOfAt function with the snapshot id +and the account address. + +#### Gas Costs + +Snapshots are efficient. Snapshot creation is _O(1)_. Retrieval of balances or total supply from a snapshot is _O(log +n)_ in the number of snapshots that have been created, although _n_ for a specific account will generally be much +smaller since identical balances in subsequent snapshots are stored as a single entry. + +There is a constant overhead for normal ERC20 transfers due to the additional snapshot bookkeeping. This overhead is +only significant for the first transfer that immediately follows a snapshot for a particular account. Subsequent +transfers will have normal cost until the next snapshot, and so on. + +**Functions** + +* [`_snapshot()`](#ERC20Snapshot-_snapshot) +* [`balanceOfAt(account, snapshotId)`](#ERC20Snapshot-balanceOfAt-address-uint256-) +* [`totalSupplyAt(snapshotId)`](#ERC20Snapshot-totalSupplyAt-uint256-) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20Snapshot-_beforeTokenTransfer-address-address-uint256-) + +**ERC20** + +* [`constructor(name_, symbol_)`](#ERC20-constructor-string-string-) +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) + +**Events** + +* [`Snapshot(id)`](#ERC20Snapshot-Snapshot-uint256-) + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `_snapshot() → uint256 *internal*` + +Creates a new snapshot and returns its snapshot id. + +Emits a Snapshot event that contains the same id. + +_snapshot is `internal` and you have to decide how to expose it externally. Its usage may be restricted to a +set of accounts, for example using AccessControl, or it may be open to the public. + + + +While an open way of calling _snapshot is required for certain trust minimization mechanisms such as forking, +you must consider that it can potentially be used by attackers in two ways. + +First, it can be used to increase the cost of retrieval of values from snapshots, although it will grow +logarithmically thus rendering this attack ineffective in the long term. Second, it can be used to target +specific accounts and increase the cost of ERC20 transfers for them, in the ways specified in the Gas Costs +section above. + +We haven’t measured the actual numbers; if this is something you’re interested in please reach out to us. + + + + +#### `balanceOfAt(address account, uint256 snapshotId) → uint256 *public*` + +Retrieves the balance of `account` at the time `snapshotId` was created. + + + +#### `totalSupplyAt(uint256 snapshotId) → uint256 *public*` + +Retrieves the total supply at the time `snapshotId` was created. + + + +#### `_beforeTokenTransfer(address from, address to, uint256 amount) *internal*` + + + +#### `Snapshot(uint256 id) *event*` + +Emitted by _snapshot when a snapshot identified by `id` is created. + +### ``ERC20Pausable`` + +ERC20 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + +**Functions** + +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20Pausable-_beforeTokenTransfer-address-address-uint256-) + +**Pausable** + +* [`constructor()`](#Pausable-constructor) +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**ERC20** + +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) + +**Events** + +**Pausable** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `_beforeTokenTransfer(address from, address to, uint256 amount) *internal* + +See ERC20-_beforeTokenTransfer. + +Requirements: + +*` the contract must not be paused. + +### ``ERC20Burnable`` + +Extension of ERC20 that allows token holders to destroy both their own +tokens and those that they have an allowance for, in a way that can be +recognized off-chain (via event analysis). + +**Functions** + +* [`burn(amount)`](#ERC20Burnable-burn-uint256-) +* [`burnFrom(account, amount)`](#ERC20Burnable-burnFrom-address-uint256-) + +**ERC20** + +* [`constructor(name_, symbol_)`](#ERC20-constructor-string-string-) +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20-_beforeTokenTransfer-address-address-uint256-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `burn(uint256 amount) *public*` + +Destroys `amount` tokens from the caller. + +See ERC20-_burn. + + + +#### `burnFrom(address account, uint256 amount) *public*` + +Destroys `amount` tokens from `account`, deducting from the caller’s +allowance. + +See ERC20-_burn and ERC20-allowance. + +Requirements: + +* the caller must have allowance for ``accounts`’s tokens of at least +`amount`. + +### ``ERC20Capped`` + +Extension of ERC20 that adds a cap to the supply of tokens. + +**Functions** + +* [`constructor(cap_)`](#ERC20Capped-constructor-uint256-) +* [`cap()`](#ERC20Capped-cap) +* [`_beforeTokenTransfer(from, to, amount)`](#ERC20Capped-_beforeTokenTransfer-address-address-uint256-) + +**ERC20** + +* [`name()`](#ERC20-name) +* [`symbol()`](#ERC20-symbol) +* [`decimals()`](#ERC20-decimals) +* [`totalSupply()`](#ERC20-totalSupply) +* [`balanceOf(account)`](#ERC20-balanceOf-address-) +* [`transfer(recipient, amount)`](#ERC20-transfer-address-uint256-) +* [`allowance(owner, spender)`](#ERC20-allowance-address-address-) +* [`approve(spender, amount)`](#ERC20-approve-address-uint256-) +* [`transferFrom(sender, recipient, amount)`](#ERC20-transferFrom-address-address-uint256-) +* [`increaseAllowance(spender, addedValue)`](#ERC20-increaseAllowance-address-uint256-) +* [`decreaseAllowance(spender, subtractedValue)`](#ERC20-decreaseAllowance-address-uint256-) +* [`_transfer(sender, recipient, amount)`](#ERC20-_transfer-address-address-uint256-) +* [`_mint(account, amount)`](#ERC20-_mint-address-uint256-) +* [`_burn(account, amount)`](#ERC20-_burn-address-uint256-) +* [`_approve(owner, spender, amount)`](#ERC20-_approve-address-address-uint256-) +* [`_setupDecimals(decimals_)`](#ERC20-_setupDecimals-uint8-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + + + +#### `constructor(uint256 cap_) *internal*` + +Sets the value of the `cap`. This value is immutable, it can only be +set once during construction. + + + +#### `cap() → uint256 *public*` + +Returns the cap on the token’s total supply. + + + +#### `_beforeTokenTransfer(address from, address to, uint256 amount) *internal* + +See ERC20-_beforeTokenTransfer. + +Requirements: + +*` minted tokens must not cause the total supply to go over the cap. + +## Utilities + +### ``SafeERC20`` + +Wrappers around ERC20 operations that throw on failure (when the token +contract returns false). Tokens that return no value (and instead revert or +throw on failure) are also supported, non-reverting calls are assumed to be +successful. +To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, +which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + +**Functions** + +* [`safeTransfer(token, to, value)`](#SafeERC20-safeTransfer-contract-IERC20-address-uint256-) +* [`safeTransferFrom(token, from, to, value)`](#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-) +* [`safeApprove(token, spender, value)`](#SafeERC20-safeApprove-contract-IERC20-address-uint256-) +* [`safeIncreaseAllowance(token, spender, value)`](#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-) +* [`safeDecreaseAllowance(token, spender, value)`](#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-) + + + +#### `safeTransfer(contract IERC20 token, address to, uint256 value) *internal*` + + + +#### `safeTransferFrom(contract IERC20 token, address from, address to, uint256 value) *internal*` + + + +#### `safeApprove(contract IERC20 token, address spender, uint256 value) *internal*` + +Deprecated. This function has issues similar to the ones found in +IERC20-approve, and its usage is discouraged. + +Whenever possible, use safeIncreaseAllowance and +safeDecreaseAllowance instead. + + + +#### `safeIncreaseAllowance(contract IERC20 token, address spender, uint256 value) *internal*` + + + +#### `safeDecreaseAllowance(contract IERC20 token, address spender, uint256 value) *internal*` + +### ``TokenTimelock`` + +A token holder contract that will allow a beneficiary to extract the +tokens after a given release time. + +Useful for simple vesting schedules like "advisors get all of their tokens +after 1 year". + +**Functions** + +* [`constructor(token_, beneficiary_, releaseTime_)`](#TokenTimelock-constructor-contract-IERC20-address-uint256-) +* [`token()`](#TokenTimelock-token) +* [`beneficiary()`](#TokenTimelock-beneficiary) +* [`releaseTime()`](#TokenTimelock-releaseTime) +* [`release()`](#TokenTimelock-release) + + + +#### `constructor(contract IERC20 token_, address beneficiary_, uint256 releaseTime_) *public*` + + + +#### `token() → contract IERC20 *public*` + + + +#### `beneficiary() → address *public*` + + + +#### `releaseTime() → uint256 *public*` + + + +#### `release() *public*` diff --git a/docs/content/contracts/3.x/api/token/ERC721.mdx b/docs/content/contracts/3.x/api/token/ERC721.mdx new file mode 100644 index 00000000..954b34d7 --- /dev/null +++ b/docs/content/contracts/3.x/api/token/ERC721.mdx @@ -0,0 +1,780 @@ +--- +title: ERC 721 +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc721 + + +This set of interfaces, contracts, and utilities are all related to the [ERC721 Non-Fungible Token Standard](https://eips.ethereum.org/EIPS/eip-721). + + +For a walk through on how to create an ERC721 token read our [ERC721 guide](/contracts/3.x/erc721). + + +The EIP consists of three interfaces, found here as IERC721, IERC721Metadata, and IERC721Enumerable. Only the first one is required in a contract to be ERC721 compliant. However, all three are implemented in ERC721. + +Additionally, IERC721Receiver can be used to prevent tokens from becoming forever locked in contracts. Imagine sending an in-game item to an exchange address that can’t send it back!. When using [`safeTransferFrom`](#IERC721-safeTransferFrom), the token contract checks to see that the receiver is an IERC721Receiver, which implies that it knows how to handle ERC721 tokens. If you’re writing a contract that needs to receive ERC721 tokens, you’ll want to include this interface. + +Additionally there are multiple custom extensions, including: + +* designation of addresses that can pause token transfers for all users (ERC721Pausable). +* destruction of own tokens (ERC721Burnable). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC721 (such as [`_mint`](#`_mint(address-to,-uint256-tokenid) *internal*`)) and expose them as external functions in the way they prefer. On the other hand, [ERC721 Presets](/contracts/3.x/erc721#preset-erc721-contract) (such as ERC721PresetMinterPauserAutoId) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. + + +## Core + +### ``IERC721`` + +Required interface of an ERC721 compliant contract. + +**Functions** + +* [`balanceOf(owner)`](#IERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#IERC721-ownerOf-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#IERC721-safeTransferFrom-address-address-uint256-) +* [`transferFrom(from, to, tokenId)`](#IERC721-transferFrom-address-address-uint256-) +* [`approve(to, tokenId)`](#IERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#IERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, _approved)`](#IERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#IERC721-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, tokenId, data)`](#IERC721-safeTransferFrom-address-address-uint256-bytes-) + +**IERC165** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + +**Events** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + + + +#### `balanceOf(address owner) → uint256 balance *external*` + +Returns the number of tokens in ``owner`’s account. + + + +#### `ownerOf(uint256 tokenId) → address owner *external*` + +Returns the owner of the `tokenId` token. + +Requirements: + +* `tokenId` must exist. + + + +#### `safeTransferFrom(address from, address to, uint256 tokenId) *external*` + +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC721 protocol to prevent tokens from being forever locked. + +Requirements: + +* `from` cannot be the zero address. +* `to` cannot be the zero address. +* `tokenId` token must exist and be owned by `from`. +* If the caller is not `from`, it must be have been allowed to move this token by either approve or setApprovalForAll. +* If `to` refers to a smart contract, it must implement IERC721Receiver-onERC721Received, which is called upon a safe transfer. + +Emits a Transfer event. + + + +#### `transferFrom(address from, address to, uint256 tokenId) *external*` + +Transfers `tokenId` token from `from` to `to`. + + +Usage of this method is discouraged, use safeTransferFrom whenever possible. + + +Requirements: + +* `from` cannot be the zero address. +* `to` cannot be the zero address. +* `tokenId` token must be owned by `from`. +* If the caller is not `from`, it must be approved to move this token by either approve or setApprovalForAll. + +Emits a Transfer event. + + + +#### `approve(address to, uint256 tokenId) *external*` + +Gives permission to `to` to transfer `tokenId` token to another account. +The approval is cleared when the token is transferred. + +Only a single account can be approved at a time, so approving the zero address clears previous approvals. + +Requirements: + +* The caller must own the token or be an approved operator. +* `tokenId` must exist. + +Emits an Approval event. + + + +#### `getApproved(uint256 tokenId) → address operator *external*` + +Returns the account approved for `tokenId` token. + +Requirements: + +* `tokenId` must exist. + + + +#### `setApprovalForAll(address operator, bool _approved) *external*` + +Approve or remove `operator` as an operator for the caller. +Operators can call transferFrom or safeTransferFrom for any token owned by the caller. + +Requirements: + +* The `operator` cannot be the caller. + +Emits an ApprovalForAll event. + + + +#### `isApprovedForAll(address owner, address operator) → bool *external*` + +Returns if the `operator` is allowed to manage all of the assets of `owner`. + +See setApprovalForAll + + + +#### `safeTransferFrom(address from, address to, uint256 tokenId, bytes data) *external*` + +Safely transfers `tokenId` token from `from` to `to`. + +Requirements: + +* `from` cannot be the zero address. +* `to` cannot be the zero address. +* `tokenId` token must exist and be owned by `from`. +* If the caller is not `from`, it must be approved to move this token by either approve or setApprovalForAll. +* If `to` refers to a smart contract, it must implement IERC721Receiver-onERC721Received, which is called upon a safe transfer. + +Emits a Transfer event. + + + +#### `Transfer(address from, address to, uint256 tokenId) *event*` + +Emitted when `tokenId` token is transferred from `from` to `to`. + + + +#### `Approval(address owner, address approved, uint256 tokenId) *event*` + +Emitted when `owner` enables `approved` to manage the `tokenId` token. + + + +#### `ApprovalForAll(address owner, address operator, bool approved) *event*` + +Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + +### ``IERC721Metadata`` + +See https://eips.ethereum.org/EIPS/eip-721 + +**Functions** + +* [`name()`](#IERC721Metadata-name) +* [`symbol()`](#IERC721Metadata-symbol) +* [`tokenURI(tokenId)`](#IERC721Metadata-tokenURI-uint256-) + +**IERC721** + +* [`balanceOf(owner)`](#IERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#IERC721-ownerOf-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#IERC721-safeTransferFrom-address-address-uint256-) +* [`transferFrom(from, to, tokenId)`](#IERC721-transferFrom-address-address-uint256-) +* [`approve(to, tokenId)`](#IERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#IERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, _approved)`](#IERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#IERC721-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, tokenId, data)`](#IERC721-safeTransferFrom-address-address-uint256-bytes-) + +**IERC165** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + +**Events** + +**IERC721** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + + + +#### `name() → string *external*` + +Returns the token collection name. + + + +#### `symbol() → string *external*` + +Returns the token collection symbol. + + + +#### `tokenURI(uint256 tokenId) → string *external*` + +Returns the Uniform Resource Identifier (URI) for `tokenId` token. + +### ``IERC721Enumerable`` + +See https://eips.ethereum.org/EIPS/eip-721 + +**Functions** + +* [`totalSupply()`](#IERC721Enumerable-totalSupply) +* [`tokenOfOwnerByIndex(owner, index)`](#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) +* [`tokenByIndex(index)`](#IERC721Enumerable-tokenByIndex-uint256-) + +**IERC721** + +* [`balanceOf(owner)`](#IERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#IERC721-ownerOf-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#IERC721-safeTransferFrom-address-address-uint256-) +* [`transferFrom(from, to, tokenId)`](#IERC721-transferFrom-address-address-uint256-) +* [`approve(to, tokenId)`](#IERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#IERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, _approved)`](#IERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#IERC721-isApprovedForAll-address-address-) +* [`safeTransferFrom(from, to, tokenId, data)`](#IERC721-safeTransferFrom-address-address-uint256-bytes-) + +**IERC165** + +* [`supportsInterface(interfaceId)`](#IERC165-supportsInterface-bytes4-) + +**Events** + +**IERC721** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + + + +#### `totalSupply() → uint256 *external*` + +Returns the total amount of tokens stored by the contract. + + + +#### `tokenOfOwnerByIndex(address owner, uint256 index) → uint256 tokenId *external*` + +Returns a token ID owned by `owner` at a given `index` of its token list. +Use along with balanceOf to enumerate all of ``owner`’s tokens. + + + +#### `tokenByIndex(uint256 index) → uint256 *external*` + +Returns a token ID at a given `index` of all the tokens stored by the contract. +Use along with totalSupply to enumerate all tokens. + +### ``ERC721`` + +see https://eips.ethereum.org/EIPS/eip-721 + +**Functions** + +* [`constructor(name_, symbol_)`](#ERC721-constructor-string-string-) +* [`balanceOf(owner)`](#ERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#ERC721-ownerOf-uint256-) +* [`name()`](#ERC721-name) +* [`symbol()`](#ERC721-symbol) +* [`tokenURI(tokenId)`](#ERC721-tokenURI-uint256-) +* [`baseURI()`](#ERC721-baseURI) +* [`tokenOfOwnerByIndex(owner, index)`](#ERC721-tokenOfOwnerByIndex-address-uint256-) +* [`totalSupply()`](#ERC721-totalSupply) +* [`tokenByIndex(index)`](#ERC721-tokenByIndex-uint256-) +* [`approve(to, tokenId)`](#ERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#ERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#ERC721-isApprovedForAll-address-address-) +* [`transferFrom(from, to, tokenId)`](#ERC721-transferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#ERC721-safeTransferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId, _data)`](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +* [`_safeTransfer(from, to, tokenId, _data)`](#ERC721-_safeTransfer-address-address-uint256-bytes-) +* [`_exists(tokenId)`](#ERC721-_exists-uint256-) +* [`_isApprovedOrOwner(spender, tokenId)`](#ERC721-_isApprovedOrOwner-address-uint256-) +* [`_safeMint(to, tokenId)`](#ERC721-_safeMint-address-uint256-) +* [`_safeMint(to, tokenId, _data)`](#ERC721-_safeMint-address-uint256-bytes-) +* [`_mint(to, tokenId)`](#ERC721-_mint-address-uint256-) +* [`_burn(tokenId)`](#ERC721-_burn-uint256-) +* [`_transfer(from, to, tokenId)`](#ERC721-_transfer-address-address-uint256-) +* [`_setTokenURI(tokenId, _tokenURI)`](#ERC721-_setTokenURI-uint256-string-) +* [`_setBaseURI(baseURI_)`](#ERC721-_setBaseURI-string-) +* [`_approve(to, tokenId)`](#ERC721-_approve-address-uint256-) +* [`_beforeTokenTransfer(from, to, tokenId)`](#ERC721-_beforeTokenTransfer-address-address-uint256-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**Events** + +**IERC721** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + + + +#### `constructor(string name_, string symbol_) *public*` + +Initializes the contract by setting a `name` and a `symbol` to the token collection. + + + +#### `balanceOf(address owner) → uint256 *public*` + +See IERC721-balanceOf. + + + +#### `ownerOf(uint256 tokenId) → address *public*` + +See IERC721-ownerOf. + + + +#### `name() → string *public*` + +See IERC721Metadata-name. + + + +#### `symbol() → string *public*` + +See IERC721Metadata-symbol. + + + +#### `tokenURI(uint256 tokenId) → string *public*` + +See IERC721Metadata-tokenURI. + + + +#### `baseURI() → string *public*` + +Returns the base URI set via _setBaseURI. This will be +automatically added as a prefix in tokenURI to each token’s URI, or +to the token ID if no specific URI is set for that token ID. + + + +#### `tokenOfOwnerByIndex(address owner, uint256 index) → uint256 *public*` + +See IERC721Enumerable-tokenOfOwnerByIndex. + + + +#### `totalSupply() → uint256 *public*` + +See IERC721Enumerable-totalSupply. + + + +#### `tokenByIndex(uint256 index) → uint256 *public*` + +See IERC721Enumerable-tokenByIndex. + + + +#### `approve(address to, uint256 tokenId) *public*` + +See IERC721-approve. + + + +#### `getApproved(uint256 tokenId) → address *public*` + +See IERC721-getApproved. + + + +#### `setApprovalForAll(address operator, bool approved) *public*` + +See IERC721-setApprovalForAll. + + + +#### `isApprovedForAll(address owner, address operator) → bool *public*` + +See IERC721-isApprovedForAll. + + + +#### `transferFrom(address from, address to, uint256 tokenId) *public*` + +See IERC721-transferFrom. + + + +#### `safeTransferFrom(address from, address to, uint256 tokenId) *public*` + +See IERC721-safeTransferFrom. + + + +#### `safeTransferFrom(address from, address to, uint256 tokenId, bytes _data) *public*` + +See IERC721-safeTransferFrom. + + + +#### `_safeTransfer(address from, address to, uint256 tokenId, bytes _data) *internal*` + +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC721 protocol to prevent tokens from being forever locked. + +`_data` is additional data, it has no specified format and it is sent in call to `to`. + +This internal function is equivalent to safeTransferFrom, and can be used to e.g. +implement alternative mechanisms to perform token transfer, such as signature-based. + +Requirements: + +* `from` cannot be the zero address. +* `to` cannot be the zero address. +* `tokenId` token must exist and be owned by `from`. +* If `to` refers to a smart contract, it must implement IERC721Receiver-onERC721Received, which is called upon a safe transfer. + +Emits a Transfer event. + + + +#### `_exists(uint256 tokenId) → bool *internal*` + +Returns whether `tokenId` exists. + +Tokens can be managed by their owner or approved accounts via approve or setApprovalForAll. + +Tokens start existing when they are minted (`_mint`), +and stop existing when they are burned (`_burn`). + + + +#### `_isApprovedOrOwner(address spender, uint256 tokenId) → bool *internal*` + +Returns whether `spender` is allowed to manage `tokenId`. + +Requirements: + +* `tokenId` must exist. + + + +#### `_safeMint(address to, uint256 tokenId) *internal*` + +Safely mints `tokenId` and transfers it to `to`. + +Requirements: + d* +- `tokenId` must not exist. +- If `to` refers to a smart contract, it must implement IERC721Receiver-onERC721Received, which is called upon a safe transfer. + +Emits a Transfer event. + + + +#### `_safeMint(address to, uint256 tokenId, bytes _data) *internal*` + +Same as [`_safeMint`](#ERC721-_safeMint-address-uint256-), with an additional `data` parameter which is +forwarded in IERC721Receiver-onERC721Received to contract recipients. + + + +#### `_mint(address to, uint256 tokenId) *internal*` + +Mints `tokenId` and transfers it to `to`. + + +Usage of this method is discouraged, use _safeMint whenever possible + + +Requirements: + +* `tokenId` must not exist. +* `to` cannot be the zero address. + +Emits a Transfer event. + + + +#### `_burn(uint256 tokenId) *internal*` + +Destroys `tokenId`. +The approval is cleared when the token is burned. + +Requirements: + +* `tokenId` must exist. + +Emits a Transfer event. + + + +#### `_transfer(address from, address to, uint256 tokenId) *internal*` + +Transfers `tokenId` from `from` to `to`. + As opposed to transferFrom, this imposes no restrictions on msg.sender. + +Requirements: + +* `to` cannot be the zero address. +* `tokenId` token must be owned by `from`. + +Emits a Transfer event. + + + +#### `_setTokenURI(uint256 tokenId, string _tokenURI) *internal*` + +Sets `_tokenURI` as the tokenURI of `tokenId`. + +Requirements: + +* `tokenId` must exist. + + + +#### `_setBaseURI(string baseURI_) *internal*` + +Internal function to set the base URI for all token IDs. It is +automatically added as a prefix to the value returned in tokenURI, +or to the token ID if tokenURI is empty. + + + +#### `_approve(address to, uint256 tokenId) *internal*` + +Approve `to` to operate on `tokenId` + +Emits an Approval event. + + + +#### `_beforeTokenTransfer(address from, address to, uint256 tokenId) *internal* + +Hook that is called before any token transfer. This includes minting +and burning. + +Calling conditions: + +*` When `from` and `to` are both non-zero, ``from`’s `tokenId` will be +transferred to `to`. +* When `from` is zero, `tokenId` will be minted for `to`. +* When `to` is zero, ``from`’s `tokenId` will be burned. +* `from` cannot be the zero address. +* `to` cannot be the zero address. + +To learn more about hooks, head to [Using Hooks](/contracts/3.x/extending-contracts#using-hooks). + +### ``IERC721Receiver`` + +Interface for any contract that wants to support safeTransfers +from ERC721 asset contracts. + +**Functions** + +* [`onERC721Received(operator, from, tokenId, data)`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) + + + +#### `onERC721Received(address operator, address from, uint256 tokenId, bytes data) → bytes4 *external*` + +Whenever an IERC721 `tokenId` token is transferred to this contract via IERC721-safeTransferFrom +by `operator` from `from`, this function is called. + +It must return its Solidity selector to confirm the token transfer. +If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + +The selector can be obtained in Solidity with `IERC721.onERC721Received.selector`. + +## Extensions + +### ``ERC721Pausable`` + +ERC721 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + +**Functions** + +* [`_beforeTokenTransfer(from, to, tokenId)`](#ERC721Pausable-_beforeTokenTransfer-address-address-uint256-) + +**Pausable** + +* [`constructor()`](#Pausable-constructor) +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**ERC721** + +* [`balanceOf(owner)`](#ERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#ERC721-ownerOf-uint256-) +* [`name()`](#ERC721-name) +* [`symbol()`](#ERC721-symbol) +* [`tokenURI(tokenId)`](#ERC721-tokenURI-uint256-) +* [`baseURI()`](#ERC721-baseURI) +* [`tokenOfOwnerByIndex(owner, index)`](#ERC721-tokenOfOwnerByIndex-address-uint256-) +* [`totalSupply()`](#ERC721-totalSupply) +* [`tokenByIndex(index)`](#ERC721-tokenByIndex-uint256-) +* [`approve(to, tokenId)`](#ERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#ERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#ERC721-isApprovedForAll-address-address-) +* [`transferFrom(from, to, tokenId)`](#ERC721-transferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#ERC721-safeTransferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId, _data)`](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +* [`_safeTransfer(from, to, tokenId, _data)`](#ERC721-_safeTransfer-address-address-uint256-bytes-) +* [`_exists(tokenId)`](#ERC721-_exists-uint256-) +* [`_isApprovedOrOwner(spender, tokenId)`](#ERC721-_isApprovedOrOwner-address-uint256-) +* [`_safeMint(to, tokenId)`](#ERC721-_safeMint-address-uint256-) +* [`_safeMint(to, tokenId, _data)`](#ERC721-_safeMint-address-uint256-bytes-) +* [`_mint(to, tokenId)`](#ERC721-_mint-address-uint256-) +* [`_burn(tokenId)`](#ERC721-_burn-uint256-) +* [`_transfer(from, to, tokenId)`](#ERC721-_transfer-address-address-uint256-) +* [`_setTokenURI(tokenId, _tokenURI)`](#ERC721-_setTokenURI-uint256-string-) +* [`_setBaseURI(baseURI_)`](#ERC721-_setBaseURI-string-) +* [`_approve(to, tokenId)`](#ERC721-_approve-address-uint256-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**Events** + +**Pausable** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + +**IERC721** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + + + +#### `_beforeTokenTransfer(address from, address to, uint256 tokenId) *internal* + +See ERC721-_beforeTokenTransfer. + +Requirements: + +*` the contract must not be paused. + +### ``ERC721Burnable`` + +ERC721 Token that can be irreversibly burned (destroyed). + +**Functions** + +* [`burn(tokenId)`](#ERC721Burnable-burn-uint256-) + +**ERC721** + +* [`constructor(name_, symbol_)`](#ERC721-constructor-string-string-) +* [`balanceOf(owner)`](#ERC721-balanceOf-address-) +* [`ownerOf(tokenId)`](#ERC721-ownerOf-uint256-) +* [`name()`](#ERC721-name) +* [`symbol()`](#ERC721-symbol) +* [`tokenURI(tokenId)`](#ERC721-tokenURI-uint256-) +* [`baseURI()`](#ERC721-baseURI) +* [`tokenOfOwnerByIndex(owner, index)`](#ERC721-tokenOfOwnerByIndex-address-uint256-) +* [`totalSupply()`](#ERC721-totalSupply) +* [`tokenByIndex(index)`](#ERC721-tokenByIndex-uint256-) +* [`approve(to, tokenId)`](#ERC721-approve-address-uint256-) +* [`getApproved(tokenId)`](#ERC721-getApproved-uint256-) +* [`setApprovalForAll(operator, approved)`](#ERC721-setApprovalForAll-address-bool-) +* [`isApprovedForAll(owner, operator)`](#ERC721-isApprovedForAll-address-address-) +* [`transferFrom(from, to, tokenId)`](#ERC721-transferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId)`](#ERC721-safeTransferFrom-address-address-uint256-) +* [`safeTransferFrom(from, to, tokenId, _data)`](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +* [`_safeTransfer(from, to, tokenId, _data)`](#ERC721-_safeTransfer-address-address-uint256-bytes-) +* [`_exists(tokenId)`](#ERC721-_exists-uint256-) +* [`_isApprovedOrOwner(spender, tokenId)`](#ERC721-_isApprovedOrOwner-address-uint256-) +* [`_safeMint(to, tokenId)`](#ERC721-_safeMint-address-uint256-) +* [`_safeMint(to, tokenId, _data)`](#ERC721-_safeMint-address-uint256-bytes-) +* [`_mint(to, tokenId)`](#ERC721-_mint-address-uint256-) +* [`_burn(tokenId)`](#ERC721-_burn-uint256-) +* [`_transfer(from, to, tokenId)`](#ERC721-_transfer-address-address-uint256-) +* [`_setTokenURI(tokenId, _tokenURI)`](#ERC721-_setTokenURI-uint256-string-) +* [`_setBaseURI(baseURI_)`](#ERC721-_setBaseURI-string-) +* [`_approve(to, tokenId)`](#ERC721-_approve-address-uint256-) +* [`_beforeTokenTransfer(from, to, tokenId)`](#ERC721-_beforeTokenTransfer-address-address-uint256-) + +**ERC165** + +* [`supportsInterface(interfaceId)`](#ERC165-supportsInterface-bytes4-) +* [`_registerInterface(interfaceId)`](#ERC165-_registerInterface-bytes4-) + +**Events** + +**IERC721** + +* [`Transfer(from, to, tokenId)`](#IERC721-Transfer-address-address-uint256-) +* [`Approval(owner, approved, tokenId)`](#IERC721-Approval-address-address-uint256-) +* [`ApprovalForAll(owner, operator, approved)`](#IERC721-ApprovalForAll-address-address-bool-) + + + +#### `burn(uint256 tokenId) *public*` + +Burns `tokenId`. See ERC721-_burn. + +Requirements: + +* The caller must own `tokenId` or be an approved operator. + +## Convenience + +### ``ERC721Holder`` + +Implementation of the IERC721Receiver interface. + +Accepts all token transfers. +Make sure the contract is able to use its token with IERC721-safeTransferFrom, IERC721-approve or IERC721-setApprovalForAll. + +**Functions** + +* [`onERC721Received(_, _, _, _)`](#ERC721Holder-onERC721Received-address-address-uint256-bytes-) + + + +#### `onERC721Received(address, address, uint256, bytes) → bytes4 *public*` + +See IERC721Receiver-onERC721Received. + +Always returns `IERC721Receiver.onERC721Received.selector`. diff --git a/docs/content/contracts/3.x/api/token/ERC777.mdx b/docs/content/contracts/3.x/api/token/ERC777.mdx new file mode 100644 index 00000000..fcccbab0 --- /dev/null +++ b/docs/content/contracts/3.x/api/token/ERC777.mdx @@ -0,0 +1,549 @@ +--- +title: ERC 777 +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/token/erc777 + + +This set of interfaces and contracts are all related to the [ERC777 token standard](https://eips.ethereum.org/EIPS/eip-777). + + +For an overview of ERC777 tokens and a walk through on how to create a token contract read our [ERC777 guide](/contracts/3.x/erc777). + + +The token behavior itself is implemented in the core contracts: IERC777, ERC777. + +Additionally there are interfaces used to develop contracts that react to token movements: IERC777Sender, IERC777Recipient. + +## Core + +### ``IERC777`` + +Interface of the ERC777Token standard as defined in the EIP. + +This contract uses the +[ERC1820 registry standard](https://eips.ethereum.org/EIPS/eip-1820) to let +token holders and recipients react to token movements by using setting implementers +for the associated interfaces in said registry. See IERC1820Registry and +ERC1820Implementer. + +**Functions** + +* [`name()`](#IERC777-name) +* [`symbol()`](#IERC777-symbol) +* [`granularity()`](#IERC777-granularity) +* [`totalSupply()`](#IERC777-totalSupply) +* [`balanceOf(owner)`](#IERC777-balanceOf-address-) +* [`send(recipient, amount, data)`](#IERC777-send-address-uint256-bytes-) +* [`burn(amount, data)`](#IERC777-burn-uint256-bytes-) +* [`isOperatorFor(operator, tokenHolder)`](#IERC777-isOperatorFor-address-address-) +* [`authorizeOperator(operator)`](#IERC777-authorizeOperator-address-) +* [`revokeOperator(operator)`](#IERC777-revokeOperator-address-) +* [`defaultOperators()`](#IERC777-defaultOperators) +* [`operatorSend(sender, recipient, amount, data, operatorData)`](#IERC777-operatorSend-address-address-uint256-bytes-bytes-) +* [`operatorBurn(account, amount, data, operatorData)`](#IERC777-operatorBurn-address-uint256-bytes-bytes-) + +**Events** + +* [`Sent(operator, from, to, amount, data, operatorData)`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +* [`Minted(operator, to, amount, data, operatorData)`](#IERC777-Minted-address-address-uint256-bytes-bytes-) +* [`Burned(operator, from, amount, data, operatorData)`](#IERC777-Burned-address-address-uint256-bytes-bytes-) +* [`AuthorizedOperator(operator, tokenHolder)`](#IERC777-AuthorizedOperator-address-address-) +* [`RevokedOperator(operator, tokenHolder)`](#IERC777-RevokedOperator-address-address-) + + + +#### `name() → string *external*` + +Returns the name of the token. + + + +#### `symbol() → string *external*` + +Returns the symbol of the token, usually a shorter version of the +name. + + + +#### `granularity() → uint256 *external*` + +Returns the smallest part of the token that is not divisible. This +means all token operations (creation, movement and destruction) must have +amounts that are a multiple of this number. + +For most token contracts, this value will equal 1. + + + +#### `totalSupply() → uint256 *external*` + +Returns the amount of tokens in existence. + + + +#### `balanceOf(address owner) → uint256 *external*` + +Returns the amount of tokens owned by an account (`owner`). + + + +#### `send(address recipient, uint256 amount, bytes data) *external*` + +Moves `amount` tokens from the caller’s account to `recipient`. + +If send or receive hooks are registered for the caller and `recipient`, +the corresponding functions will be called with `data` and empty +`operatorData`. See IERC777Sender and IERC777Recipient. + +Emits a Sent event. + +Requirements + +* the caller must have at least `amount` tokens. +* `recipient` cannot be the zero address. +* if `recipient` is a contract, it must implement the IERC777Recipient +interface. + + + +#### `burn(uint256 amount, bytes data) *external*` + +Destroys `amount` tokens from the caller’s account, reducing the +total supply. + +If a send hook is registered for the caller, the corresponding function +will be called with `data` and empty `operatorData`. See IERC777Sender. + +Emits a Burned event. + +Requirements + +* the caller must have at least `amount` tokens. + + + +#### `isOperatorFor(address operator, address tokenHolder) → bool *external*` + +Returns true if an account is an operator of `tokenHolder`. +Operators can send and burn tokens on behalf of their owners. All +accounts are their own operator. + +See operatorSend and operatorBurn. + + + +#### `authorizeOperator(address operator) *external* + +Make an account an operator of the caller. + +See isOperatorFor. + +Emits an AuthorizedOperator event. + +Requirements + +*` `operator` cannot be calling address. + + + +#### `revokeOperator(address operator) *external* + +Revoke an account’s operator status for the caller. + +See isOperatorFor and defaultOperators. + +Emits a RevokedOperator event. + +Requirements + +*` `operator` cannot be calling address. + + + +#### `defaultOperators() → address[] *external*` + +Returns the list of default operators. These accounts are operators +for all token holders, even if authorizeOperator was never called on +them. + +This list is immutable, but individual holders may revoke these via +revokeOperator, in which case isOperatorFor will return false. + + + +#### `operatorSend(address sender, address recipient, uint256 amount, bytes data, bytes operatorData) *external*` + +Moves `amount` tokens from `sender` to `recipient`. The caller must +be an operator of `sender`. + +If send or receive hooks are registered for `sender` and `recipient`, +the corresponding functions will be called with `data` and +`operatorData`. See IERC777Sender and IERC777Recipient. + +Emits a Sent event. + +Requirements + +* `sender` cannot be the zero address. +* `sender` must have at least `amount` tokens. +* the caller must be an operator for `sender`. +* `recipient` cannot be the zero address. +* if `recipient` is a contract, it must implement the IERC777Recipient +interface. + + + +#### `operatorBurn(address account, uint256 amount, bytes data, bytes operatorData) *external*` + +Destroys `amount` tokens from `account`, reducing the total supply. +The caller must be an operator of `account`. + +If a send hook is registered for `account`, the corresponding function +will be called with `data` and `operatorData`. See IERC777Sender. + +Emits a Burned event. + +Requirements + +* `account` cannot be the zero address. +* `account` must have at least `amount` tokens. +* the caller must be an operator for `account`. + + + +#### `Sent(address operator, address from, address to, uint256 amount, bytes data, bytes operatorData) *event*` + + + +#### `Minted(address operator, address to, uint256 amount, bytes data, bytes operatorData) *event*` + + + +#### `Burned(address operator, address from, uint256 amount, bytes data, bytes operatorData) *event*` + + + +#### `AuthorizedOperator(address operator, address tokenHolder) *event*` + + + +#### `RevokedOperator(address operator, address tokenHolder) *event*` + +### ``ERC777`` + +Implementation of the IERC777 interface. + +This implementation is agnostic to the way tokens are created. This means +that a supply mechanism has to be added in a derived contract using _mint. + +Support for ERC20 is included in this contract, as specified by the EIP: both +the ERC777 and ERC20 interfaces can be safely used when interacting with it. +Both IERC777-Sent and IERC20-Transfer events are emitted on token +movements. + +Additionally, the IERC777-granularity value is hard-coded to `1`, meaning that there +are no special restrictions in the amount of tokens that created, moved, or +destroyed. This makes integration with ERC20 applications seamless. + +**Functions** + +* [`constructor(name_, symbol_, defaultOperators_)`](#ERC777-constructor-string-string-address-) +* [`name()`](#ERC777-name) +* [`symbol()`](#ERC777-symbol) +* [`decimals()`](#ERC777-decimals) +* [`granularity()`](#ERC777-granularity) +* [`totalSupply()`](#ERC777-totalSupply) +* [`balanceOf(tokenHolder)`](#ERC777-balanceOf-address-) +* [`send(recipient, amount, data)`](#ERC777-send-address-uint256-bytes-) +* [`transfer(recipient, amount)`](#ERC777-transfer-address-uint256-) +* [`burn(amount, data)`](#ERC777-burn-uint256-bytes-) +* [`isOperatorFor(operator, tokenHolder)`](#ERC777-isOperatorFor-address-address-) +* [`authorizeOperator(operator)`](#ERC777-authorizeOperator-address-) +* [`revokeOperator(operator)`](#ERC777-revokeOperator-address-) +* [`defaultOperators()`](#ERC777-defaultOperators) +* [`operatorSend(sender, recipient, amount, data, operatorData)`](#ERC777-operatorSend-address-address-uint256-bytes-bytes-) +* [`operatorBurn(account, amount, data, operatorData)`](#ERC777-operatorBurn-address-uint256-bytes-bytes-) +* [`allowance(holder, spender)`](#ERC777-allowance-address-address-) +* [`approve(spender, value)`](#ERC777-approve-address-uint256-) +* [`transferFrom(holder, recipient, amount)`](#ERC777-transferFrom-address-address-uint256-) +* [`_mint(account, amount, userData, operatorData)`](#ERC777-_mint-address-uint256-bytes-bytes-) +* [`_send(from, to, amount, userData, operatorData, requireReceptionAck)`](#ERC777-_send-address-address-uint256-bytes-bytes-bool-) +* [`_burn(from, amount, data, operatorData)`](#ERC777-_burn-address-uint256-bytes-bytes-) +* [`_approve(holder, spender, value)`](#ERC777-_approve-address-address-uint256-) +* [`_beforeTokenTransfer(operator, from, to, amount)`](#ERC777-_beforeTokenTransfer-address-address-address-uint256-) + +**Events** + +**IERC20** + +* [`Transfer(from, to, value)`](#IERC20-Transfer-address-address-uint256-) +* [`Approval(owner, spender, value)`](#IERC20-Approval-address-address-uint256-) + +**IERC777** + +* [`Sent(operator, from, to, amount, data, operatorData)`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +* [`Minted(operator, to, amount, data, operatorData)`](#IERC777-Minted-address-address-uint256-bytes-bytes-) +* [`Burned(operator, from, amount, data, operatorData)`](#IERC777-Burned-address-address-uint256-bytes-bytes-) +* [`AuthorizedOperator(operator, tokenHolder)`](#IERC777-AuthorizedOperator-address-address-) +* [`RevokedOperator(operator, tokenHolder)`](#IERC777-RevokedOperator-address-address-) + + + +#### `constructor(string name_, string symbol_, address[] defaultOperators_) *public*` + +`defaultOperators` may be an empty array. + + + +#### `name() → string *public*` + +See IERC777-name. + + + +#### `symbol() → string *public*` + +See IERC777-symbol. + + + +#### `decimals() → uint8 *public*` + +See ERC20-decimals. + +Always returns 18, as per the +[ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). + + + +#### `granularity() → uint256 *public*` + +See IERC777-granularity. + +This implementation always returns `1`. + + + +#### `totalSupply() → uint256 *public*` + +See IERC777-totalSupply. + + + +#### `balanceOf(address tokenHolder) → uint256 *public*` + +Returns the amount of tokens owned by an account (`tokenHolder`). + + + +#### `send(address recipient, uint256 amount, bytes data) *public*` + +See IERC777-send. + +Also emits a IERC20-Transfer event for ERC20 compatibility. + + + +#### `transfer(address recipient, uint256 amount) → bool *public*` + +See IERC20-transfer. + +Unlike `send`, `recipient` is _not_ required to implement the IERC777Recipient +interface if it is a contract. + +Also emits a Sent event. + + + +#### `burn(uint256 amount, bytes data) *public*` + +See IERC777-burn. + +Also emits a IERC20-Transfer event for ERC20 compatibility. + + + +#### `isOperatorFor(address operator, address tokenHolder) → bool *public*` + +See IERC777-isOperatorFor. + + + +#### `authorizeOperator(address operator) *public*` + +See IERC777-authorizeOperator. + + + +#### `revokeOperator(address operator) *public*` + +See IERC777-revokeOperator. + + + +#### `defaultOperators() → address[] *public*` + +See IERC777-defaultOperators. + + + +#### `operatorSend(address sender, address recipient, uint256 amount, bytes data, bytes operatorData) *public*` + +See IERC777-operatorSend. + +Emits Sent and IERC20-Transfer events. + + + +#### `operatorBurn(address account, uint256 amount, bytes data, bytes operatorData) *public*` + +See IERC777-operatorBurn. + +Emits Burned and IERC20-Transfer events. + + + +#### `allowance(address holder, address spender) → uint256 *public*` + +See IERC20-allowance. + +Note that operator and allowance concepts are orthogonal: operators may +not have allowance, and accounts with allowance may not be operators +themselves. + + + +#### `approve(address spender, uint256 value) → bool *public*` + +See IERC20-approve. + +Note that accounts cannot have allowance issued by their operators. + + + +#### `transferFrom(address holder, address recipient, uint256 amount) → bool *public*` + +See IERC20-transferFrom. + +Note that operator and allowance concepts are orthogonal: operators cannot +call `transferFrom` (unless they have allowance), and accounts with +allowance cannot call `operatorSend` (unless they are operators). + +Emits Sent, IERC20-Transfer and IERC20-Approval events. + + + +#### `_mint(address account, uint256 amount, bytes userData, bytes operatorData) *internal*` + +Creates `amount` tokens and assigns them to `account`, increasing +the total supply. + +If a send hook is registered for `account`, the corresponding function +will be called with `operator`, `data` and `operatorData`. + +See IERC777Sender and IERC777Recipient. + +Emits Minted and IERC20-Transfer events. + +Requirements + +* `account` cannot be the zero address. +* if `account` is a contract, it must implement the IERC777Recipient +interface. + + + +#### `_send(address from, address to, uint256 amount, bytes userData, bytes operatorData, bool requireReceptionAck) *internal*` + +Send tokens + + + +#### `_burn(address from, uint256 amount, bytes data, bytes operatorData) *internal*` + +Burn tokens + + + +#### `_approve(address holder, address spender, uint256 value) *internal*` + +See ERC20-_approve. + +Note that accounts cannot have allowance issued by their operators. + + + +#### `_beforeTokenTransfer(address operator, address from, address to, uint256 amount) *internal* + +Hook that is called before any token transfer. This includes +calls to send, transfer, operatorSend, minting and burning. + +Calling conditions: + +*` when `from` and `to` are both non-zero, `amount` of ``from`’s tokens +will be to transferred to `to`. +* when `from` is zero, `amount` tokens will be minted for `to`. +* when `to` is zero, `amount` of ``from`’s tokens will be burned. +* `from` and `to` are never both zero. + +To learn more about hooks, head to [Using Hooks](/contracts/3.x/extending-contracts#using-hooks). + +## Hooks + +### ``IERC777Sender`` + +Interface of the ERC777TokensSender standard as defined in the EIP. + +IERC777 Token holders can be notified of operations performed on their +tokens by having a contract implement this interface (contract holders can be + their own implementer) and registering it on the +[ERC1820 global registry](https://eips.ethereum.org/EIPS/eip-1820). + +See IERC1820Registry and ERC1820Implementer. + +**Functions** + +* [`tokensToSend(operator, from, to, amount, userData, operatorData)`](#IERC777Sender-tokensToSend-address-address-address-uint256-bytes-bytes-) + + + +#### `tokensToSend(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData) *external*` + +Called by an IERC777 token contract whenever a registered holder’s +(`from`) tokens are about to be moved or destroyed. The type of operation +is conveyed by `to` being the zero address or not. + +This call occurs _before_ the token contract’s state is updated, so +IERC777-balanceOf, etc., can be used to query the pre-operation state. + +This function may revert to prevent the operation from being executed. + +### ``IERC777Recipient`` + +Interface of the ERC777TokensRecipient standard as defined in the EIP. + +Accounts can be notified of IERC777 tokens being sent to them by having a +contract implement this interface (contract holders can be their own +implementer) and registering it on the +[ERC1820 global registry](https://eips.ethereum.org/EIPS/eip-1820). + +See IERC1820Registry and ERC1820Implementer. + +**Functions** + +* [`tokensReceived(operator, from, to, amount, userData, operatorData)`](#IERC777Recipient-tokensReceived-address-address-address-uint256-bytes-bytes-) + + + +#### `tokensReceived(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData) *external*` + +Called by an IERC777 token contract whenever tokens are being +moved or created into a registered account (`to`). The type of operation +is conveyed by `from` being the zero address or not. + +This call occurs _after_ the token contract’s state is updated, so +IERC777-balanceOf, etc., can be used to query the post-operation state. + +This function may revert to prevent the operation from being executed. diff --git a/docs/content/contracts/3.x/api/utils.mdx b/docs/content/contracts/3.x/api/utils.mdx new file mode 100644 index 00000000..f9a5befa --- /dev/null +++ b/docs/content/contracts/3.x/api/utils.mdx @@ -0,0 +1,901 @@ +--- +title: Utilities +--- + + +This document is better viewed at https://docs.openzeppelin.com/contracts/api/utils + + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + +Security tools include: + +* Pausable: provides a simple way to halt activity in your contracts (often in response to an external threat). +* ReentrancyGuard: protects you from [reentrant calls](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + +The Address, Arrays and Strings libraries provide more operations related to these native data types, while SafeCast adds ways to safely convert between the different signed and unsigned numeric types. + +For new data types: + +* Counters: a simple way to get a counter that can only be incremented or decremented. Very useful for ID generation, counting contract activity, among others. +* EnumerableMap: like Solidity’s [`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types) type, but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). +* EnumerableSet: like EnumerableMap, but for [sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)). Can be used to store privileged accounts, issued IDs, etc. + + + +Because Solidity does not support generic types, EnumerableMap and EnumerableSet are specialized to a limited number of key-value types. + +As of v3.0, EnumerableMap supports `uint256 -> address` (`UintToAddressMap`), and EnumerableSet supports `address` and `uint256` (`AddressSet` and `UintSet`). + + +Finally, Create2 contains all necessary utilities to safely use the [`CREATE2` EVM opcode](https://blog.openzeppelin.com/getting-the-most-out-of-create2/), without having to deal with low-level assembly. + +## Contracts + +### ``Pausable`` + +Contract module which allows children to implement an emergency stop +mechanism that can be triggered by an authorized account. + +This module is used through inheritance. It will make available the +modifiers `whenNotPaused` and `whenPaused`, which can be applied to +the functions of your contract. Note that they will not be pausable by +simply including this module, only once the modifiers are put in place. + +**Modifiers** + +* [`whenNotPaused()`](#Pausable-whenNotPaused) +* [`whenPaused()`](#Pausable-whenPaused) + +**Functions** + +* [`constructor()`](#Pausable-constructor) +* [`paused()`](#Pausable-paused) +* [`_pause()`](#Pausable-_pause) +* [`_unpause()`](#Pausable-_unpause) + +**Events** + +* [`Paused(account)`](#Pausable-Paused-address-) +* [`Unpaused(account)`](#Pausable-Unpaused-address-) + + + +#### `whenNotPaused() *modifier* + +Modifier to make a function callable only when the contract is not paused. + +Requirements: + +*` The contract must not be paused. + + + +#### `whenPaused() *modifier* + +Modifier to make a function callable only when the contract is paused. + +Requirements: + +*` The contract must be paused. + + + +#### `constructor() *internal*` + +Initializes the contract in unpaused state. + + + +#### `paused() → bool *public*` + +Returns true if the contract is paused, and false otherwise. + + + +#### `_pause() *internal* + +Triggers stopped state. + +Requirements: + +*` The contract must not be paused. + + + +#### `_unpause() *internal* + +Returns to normal state. + +Requirements: + +*` The contract must be paused. + + + +#### `Paused(address account) *event*` + +Emitted when the pause is triggered by `account`. + + + +#### `Unpaused(address account) *event*` + +Emitted when the pause is lifted by `account`. + +### ``ReentrancyGuard`` + +Contract module that helps prevent reentrant calls to a function. + +Inheriting from `ReentrancyGuard` will make the nonReentrant modifier +available, which can be applied to functions to make sure there are no nested +(reentrant) calls to them. + +Note that because there is a single `nonReentrant` guard, functions marked as +`nonReentrant` may not call one another. This can be worked around by making +those functions `private`, and then adding `external` `nonReentrant` entry +points to them. + + +If you would like to learn more about reentrancy and alternative ways +to protect against it, check out our blog post +[Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + + +**Modifiers** + +* [`nonReentrant()`](#ReentrancyGuard-nonReentrant) + +**Functions** + +* [`constructor()`](#ReentrancyGuard-constructor) + + + +#### `nonReentrant() *modifier*` + +Prevents a contract from calling itself, directly or indirectly. +Calling a `nonReentrant` function from another `nonReentrant` +function is not supported. It is possible to prevent this from happening +by making the `nonReentrant` function external, and make it call a +`private` function that does the actual work. + + + +#### `constructor() *internal*` + +## Libraries + +### ``Address`` + +Collection of functions related to the address type + +**Functions** + +* [`isContract(account)`](#Address-isContract-address-) +* [`sendValue(recipient, amount)`](#Address-sendValue-address-payable-uint256-) +* [`functionCall(target, data)`](#Address-functionCall-address-bytes-) +* [`functionCall(target, data, errorMessage)`](#Address-functionCall-address-bytes-string-) +* [`functionCallWithValue(target, data, value)`](#Address-functionCallWithValue-address-bytes-uint256-) +* [`functionCallWithValue(target, data, value, errorMessage)`](#Address-functionCallWithValue-address-bytes-uint256-string-) +* [`functionStaticCall(target, data)`](#Address-functionStaticCall-address-bytes-) +* [`functionStaticCall(target, data, errorMessage)`](#Address-functionStaticCall-address-bytes-string-) +* [`functionDelegateCall(target, data)`](#Address-functionDelegateCall-address-bytes-) +* [`functionDelegateCall(target, data, errorMessage)`](#Address-functionDelegateCall-address-bytes-string-) + + + +#### `isContract(address account) → bool *internal*` + +Returns true if `account` is a contract. + + + + +It is unsafe to assume that an address for which this function returns +false is an externally-owned account (EOA) and not a contract. + +Among others, `isContract` will return false for the following +types of addresses: + +* an externally-owned account +* a contract in construction +* an address where a contract will be created +* an address where a contract lived, but was destroyed + + + + + +#### `sendValue(address payable recipient, uint256 amount) *internal*` + +Replacement for Solidity’s `transfer`: sends `amount` wei to +`recipient`, forwarding all available gas and reverting on errors. + +[EIP1884](https://eips.ethereum.org/EIPS/eip-1884) increases the gas cost +of certain opcodes, possibly making contracts go over the 2300 gas limit +imposed by `transfer`, making them unable to receive funds via +`transfer`. sendValue removes this limitation. + +[Learn more](https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/). + + +because control is transferred to `recipient`, care must be +taken to not create reentrancy vulnerabilities. Consider using +ReentrancyGuard or the +[checks-effects-interactions pattern](https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern). + + + + +#### `functionCall(address target, bytes data) → bytes *internal*` + +Performs a Solidity function call using a low level `call`. A +plain`call` is an unsafe replacement for a function call: use this +function instead. + +If `target` reverts with a revert reason, it is bubbled up by this +function (like regular Solidity function calls). + +Returns the raw returned data. To convert to the expected return value, +use [`abi.decode`](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions). + +Requirements: + +* `target` must be a contract. +* calling `target` with `data` must not revert. + +_Available since v3.1._ + + + +#### `functionCall(address target, bytes data, string errorMessage) → bytes *internal*` + +Same as [`functionCall`](#Address-functionCall-address-bytes-), but with +`errorMessage` as a fallback revert reason when `target` reverts. + +_Available since v3.1._ + + + +#### `functionCallWithValue(address target, bytes data, uint256 value) → bytes *internal*` + +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but also transferring `value` wei to `target`. + +Requirements: + +* the calling contract must have an ETH balance of at least `value`. +* the called Solidity function must be `payable`. + +_Available since v3.1._ + + + +#### `functionCallWithValue(address target, bytes data, uint256 value, string errorMessage) → bytes *internal*` + +Same as [`functionCallWithValue`](#Address-functionCallWithValue-address-bytes-uint256-), but +with `errorMessage` as a fallback revert reason when `target` reverts. + +_Available since v3.1._ + + + +#### `functionStaticCall(address target, bytes data) → bytes *internal*` + +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but performing a static call. + +_Available since v3.3._ + + + +#### `functionStaticCall(address target, bytes data, string errorMessage) → bytes *internal*` + +Same as [`functionCall`](#Address-functionCall-address-bytes-string-), +but performing a static call. + +_Available since v3.3._ + + + +#### `functionDelegateCall(address target, bytes data) → bytes *internal*` + +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but performing a delegate call. + +_Available since v3.4._ + + + +#### `functionDelegateCall(address target, bytes data, string errorMessage) → bytes *internal*` + +Same as [`functionCall`](#Address-functionCall-address-bytes-string-), +but performing a delegate call. + +_Available since v3.4._ + +### ``Arrays`` + +Collection of functions related to array types. + +**Functions** + +* [`findUpperBound(array, element)`](#Arrays-findUpperBound-uint256---uint256-) + + + +#### `findUpperBound(uint256[] array, uint256 element) → uint256 *internal*` + +Searches a sorted `array` and returns the first index that contains +a value greater or equal to `element`. If no such index exists (i.e. all +values in the array are strictly less than `element`), the array length is +returned. Time complexity O(log n). + +`array` is expected to be sorted in ascending order, and to contain no +repeated elements. + +### ``Counters`` + +Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number +of elements in a mapping, issuing ERC721 ids, or counting request ids. + +Include with `using Counters for Counters.Counter;` +Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath +overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never +directly accessed. + +**Functions** + +* [`current(counter)`](#Counters-current-struct-Counters-Counter-) +* [`increment(counter)`](#Counters-increment-struct-Counters-Counter-) +* [`decrement(counter)`](#Counters-decrement-struct-Counters-Counter-) + + + +#### `current(struct Counters.Counter counter) → uint256 *internal*` + + + +#### `increment(struct Counters.Counter counter) *internal*` + + + +#### `decrement(struct Counters.Counter counter) *internal*` + +### ``Create2`` + +Helper to make usage of the `CREATE2` EVM opcode easier and safer. +`CREATE2` can be used to compute in advance the address where a smart +contract will be deployed, which allows for interesting new mechanisms known +as 'counterfactual interactions'. + +See the [EIP](https://eips.ethereum.org/EIPS/eip-1014#motivation) for more +information. + +**Functions** + +* [`deploy(amount, salt, bytecode)`](#Create2-deploy-uint256-bytes32-bytes-) +* [`computeAddress(salt, bytecodeHash)`](#Create2-computeAddress-bytes32-bytes32-) +* [`computeAddress(salt, bytecodeHash, deployer)`](#Create2-computeAddress-bytes32-bytes32-address-) + + + +#### `deploy(uint256 amount, bytes32 salt, bytes bytecode) → address *internal*` + +Deploys a contract using `CREATE2`. The address where the contract +will be deployed can be known in advance via computeAddress. + +The bytecode for a contract can be obtained from Solidity with +`type(contractName).creationCode`. + +Requirements: + +* `bytecode` must not be empty. +* `salt` must have not been used for `bytecode` already. +* the factory must have a balance of at least `amount`. +* if `amount` is non-zero, `bytecode` must have a `payable` constructor. + + + +#### `computeAddress(bytes32 salt, bytes32 bytecodeHash) → address *internal*` + +Returns the address where a contract will be stored if deployed via deploy. Any change in the +`bytecodeHash` or `salt` will result in a new destination address. + + + +#### `computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) → address *internal*` + +Returns the address where a contract will be stored if deployed via deploy from a contract located at +`deployer`. If `deployer` is this contract’s address, returns the same value as computeAddress. + +### ``EnumerableMap`` + +Library for managing an enumerable variant of Solidity’s +[`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types) +type. + +Maps have the following properties: + +* Entries are added, removed, and checked for existence in constant time +(O(1)). +* Entries are enumerated in O(n). No guarantees are made on the ordering. + +``` +contract Example + // Add the library methods + using EnumerableMap for EnumerableMap.UintToAddressMap; + + // Declare a set state variable + EnumerableMap.UintToAddressMap private myMap; + +``` + +As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are +supported. + +**Functions** + +* [`set(map, key, value)`](#EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-) +* [`remove(map, key)`](#EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-) +* [`contains(map, key)`](#EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-) +* [`length(map)`](#EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-) +* [`at(map, index)`](#EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-) +* [`tryGet(map, key)`](#EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-) +* [`get(map, key)`](#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-) +* [`get(map, key, errorMessage)`](#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-string-) + + + +#### `set(struct EnumerableMap.UintToAddressMap map, uint256 key, address value) → bool *internal*` + +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + + + +#### `remove(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool *internal*` + +Removes a value from a set. O(1). + +Returns true if the key was removed from the map, that is if it was present. + + + +#### `contains(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool *internal*` + +Returns true if the key is in the map. O(1). + + + +#### `length(struct EnumerableMap.UintToAddressMap map) → uint256 *internal*` + +Returns the number of elements in the map. O(1). + + + +#### `at(struct EnumerableMap.UintToAddressMap map, uint256 index) → uint256, address *internal*` + +Returns the element stored at position `index` in the set. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +* `index` must be strictly less than length. + + + +#### `tryGet(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool, address *internal*` + +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +_Available since v3.4._ + + + +#### `get(struct EnumerableMap.UintToAddressMap map, uint256 key) → address *internal*` + +Returns the value associated with `key`. O(1). + +Requirements: + +* `key` must be in the map. + + + +#### `get(struct EnumerableMap.UintToAddressMap map, uint256 key, string errorMessage) → address *internal*` + +Same as get, with a custom error message when `key` is not in the map. + + +This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use tryGet. + + +### ``EnumerableSet`` + +Library for managing +[sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) of primitive +types. + +Sets have the following properties: + +* Elements are added, removed, and checked for existence in constant time +(O(1)). +* Elements are enumerated in O(n). No guarantees are made on the ordering. + +``` +contract Example + // Add the library methods + using EnumerableSet for EnumerableSet.AddressSet; + + // Declare a set state variable + EnumerableSet.AddressSet private mySet; + +``` + +As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) +and `uint256` (`UintSet`) are supported. + +**Functions** + +* [`add(set, value)`](#EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-) +* [`remove(set, value)`](#EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-) +* [`contains(set, value)`](#EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-) +* [`length(set)`](#EnumerableSet-length-struct-EnumerableSet-Bytes32Set-) +* [`at(set, index)`](#EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-) +* [`add(set, value)`](#EnumerableSet-add-struct-EnumerableSet-AddressSet-address-) +* [`remove(set, value)`](#EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-) +* [`contains(set, value)`](#EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-) +* [`length(set)`](#EnumerableSet-length-struct-EnumerableSet-AddressSet-) +* [`at(set, index)`](#EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-) +* [`add(set, value)`](#EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-) +* [`remove(set, value)`](#EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-) +* [`contains(set, value)`](#EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-) +* [`length(set)`](#EnumerableSet-length-struct-EnumerableSet-UintSet-) +* [`at(set, index)`](#EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-) + + + +#### `add(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool *internal*` + +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + + + +#### `remove(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool *internal*` + +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + + + +#### `contains(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool *internal*` + +Returns true if the value is in the set. O(1). + + + +#### `length(struct EnumerableSet.Bytes32Set set) → uint256 *internal*` + +Returns the number of values in the set. O(1). + + + +#### `at(struct EnumerableSet.Bytes32Set set, uint256 index) → bytes32 *internal*` + +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +* `index` must be strictly less than length. + + + +#### `add(struct EnumerableSet.AddressSet set, address value) → bool *internal*` + +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + + + +#### `remove(struct EnumerableSet.AddressSet set, address value) → bool *internal*` + +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + + + +#### `contains(struct EnumerableSet.AddressSet set, address value) → bool *internal*` + +Returns true if the value is in the set. O(1). + + + +#### `length(struct EnumerableSet.AddressSet set) → uint256 *internal*` + +Returns the number of values in the set. O(1). + + + +#### `at(struct EnumerableSet.AddressSet set, uint256 index) → address *internal*` + +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +* `index` must be strictly less than length. + + + +#### `add(struct EnumerableSet.UintSet set, uint256 value) → bool *internal*` + +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + + + +#### `remove(struct EnumerableSet.UintSet set, uint256 value) → bool *internal*` + +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + + + +#### `contains(struct EnumerableSet.UintSet set, uint256 value) → bool *internal*` + +Returns true if the value is in the set. O(1). + + + +#### `length(struct EnumerableSet.UintSet set) → uint256 *internal*` + +Returns the number of values on the set. O(1). + + + +#### `at(struct EnumerableSet.UintSet set, uint256 index) → uint256 *internal*` + +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +* `index` must be strictly less than length. + +### ``SafeCast`` + +Wrappers over Solidity’s uintXX/intXX casting operators with added overflow +checks. + +Downcasting from uint256/int256 in Solidity does not revert on overflow. This can +easily result in undesired exploitation or bugs, since developers usually +assume that overflows raise errors. `SafeCast` restores this intuition by +reverting the transaction when such an operation overflows. + +Using this library instead of the unchecked operations eliminates an entire +class of bugs, so it’s recommended to use it always. + +Can be combined with SafeMath and SignedSafeMath to extend it to smaller types, by performing +all math on `uint256` and `int256` and then downcasting. + +**Functions** + +* [`toUint128(value)`](#SafeCast-toUint128-uint256-) +* [`toUint64(value)`](#SafeCast-toUint64-uint256-) +* [`toUint32(value)`](#SafeCast-toUint32-uint256-) +* [`toUint16(value)`](#SafeCast-toUint16-uint256-) +* [`toUint8(value)`](#SafeCast-toUint8-uint256-) +* [`toUint256(value)`](#SafeCast-toUint256-int256-) +* [`toInt128(value)`](#SafeCast-toInt128-int256-) +* [`toInt64(value)`](#SafeCast-toInt64-int256-) +* [`toInt32(value)`](#SafeCast-toInt32-int256-) +* [`toInt16(value)`](#SafeCast-toInt16-int256-) +* [`toInt8(value)`](#SafeCast-toInt8-int256-) +* [`toInt256(value)`](#SafeCast-toInt256-uint256-) + + + +#### `toUint128(uint256 value) → uint128 *internal*` + +Returns the downcasted uint128 from uint256, reverting on +overflow (when the input is greater than largest uint128). + +Counterpart to Solidity’s `uint128` operator. + +Requirements: + +* input must fit into 128 bits + + + +#### `toUint64(uint256 value) → uint64 *internal*` + +Returns the downcasted uint64 from uint256, reverting on +overflow (when the input is greater than largest uint64). + +Counterpart to Solidity’s `uint64` operator. + +Requirements: + +* input must fit into 64 bits + + + +#### `toUint32(uint256 value) → uint32 *internal*` + +Returns the downcasted uint32 from uint256, reverting on +overflow (when the input is greater than largest uint32). + +Counterpart to Solidity’s `uint32` operator. + +Requirements: + +* input must fit into 32 bits + + + +#### `toUint16(uint256 value) → uint16 *internal*` + +Returns the downcasted uint16 from uint256, reverting on +overflow (when the input is greater than largest uint16). + +Counterpart to Solidity’s `uint16` operator. + +Requirements: + +* input must fit into 16 bits + + + +#### `toUint8(uint256 value) → uint8 *internal*` + +Returns the downcasted uint8 from uint256, reverting on +overflow (when the input is greater than largest uint8). + +Counterpart to Solidity’s `uint8` operator. + +Requirements: + +* input must fit into 8 bits. + + + +#### `toUint256(int256 value) → uint256 *internal* + +Converts a signed int256 into an unsigned uint256. + +Requirements: + +*` input must be greater than or equal to 0. + + + +#### `toInt128(int256 value) → int128 *internal*` + +Returns the downcasted int128 from int256, reverting on +overflow (when the input is less than smallest int128 or +greater than largest int128). + +Counterpart to Solidity’s `int128` operator. + +Requirements: + +* input must fit into 128 bits + +_Available since v3.1._ + + + +#### `toInt64(int256 value) → int64 *internal*` + +Returns the downcasted int64 from int256, reverting on +overflow (when the input is less than smallest int64 or +greater than largest int64). + +Counterpart to Solidity’s `int64` operator. + +Requirements: + +* input must fit into 64 bits + +_Available since v3.1._ + + + +#### `toInt32(int256 value) → int32 *internal*` + +Returns the downcasted int32 from int256, reverting on +overflow (when the input is less than smallest int32 or +greater than largest int32). + +Counterpart to Solidity’s `int32` operator. + +Requirements: + +* input must fit into 32 bits + +_Available since v3.1._ + + + +#### `toInt16(int256 value) → int16 *internal*` + +Returns the downcasted int16 from int256, reverting on +overflow (when the input is less than smallest int16 or +greater than largest int16). + +Counterpart to Solidity’s `int16` operator. + +Requirements: + +* input must fit into 16 bits + +_Available since v3.1._ + + + +#### `toInt8(int256 value) → int8 *internal*` + +Returns the downcasted int8 from int256, reverting on +overflow (when the input is less than smallest int8 or +greater than largest int8). + +Counterpart to Solidity’s `int8` operator. + +Requirements: + +* input must fit into 8 bits. + +_Available since v3.1._ + + + +#### `toInt256(uint256 value) → int256 *internal* + +Converts an unsigned uint256 into a signed int256. + +Requirements: + +*` input must be less than or equal to maxInt256. + +### ``Strings`` + +String operations. + +**Functions** + +* [`toString(value)`](#Strings-toString-uint256-) + + + +#### `toString(uint256 value) → string *internal*` + +Converts a `uint256` to its ASCII `string` representation. diff --git a/docs/content/contracts/3.x/crowdsales.mdx b/docs/content/contracts/3.x/crowdsales.mdx new file mode 100644 index 00000000..9240a0b2 --- /dev/null +++ b/docs/content/contracts/3.x/crowdsales.mdx @@ -0,0 +1,13 @@ +--- +title: Crowdsales +--- + +All crowdsale-related contracts were removed from the OpenZeppelin Contracts library on the [v3.0.0 release](https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256) due to both a decline in their usage and the complexity associated with migrating them to Solidity v0.6. + +They are however still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the [v2.x documentation](https://docs.openzeppelin.com/contracts/2.x/crowdsales) when working with them. diff --git a/docs/content/contracts/3.x/drafts.mdx b/docs/content/contracts/3.x/drafts.mdx new file mode 100644 index 00000000..8925b33f --- /dev/null +++ b/docs/content/contracts/3.x/drafts.mdx @@ -0,0 +1,21 @@ +--- +title: Drafts +--- + +All draft contracts were either moved into a different directory or removed from the OpenZeppelin Contracts library on the [v3.0.0 release](https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256). + +* `ERC20Migrator`: removed. +* [`ERC20Snapshot`](/contracts/3.x/api/token/ERC20#ERC20Snapshot): moved to `token/ERC20`. +* `ERC20Detailed` and `ERC1046`: removed. +* `TokenVesting`: removed. Pending a replacement that is being discussed in [`#1214`](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1214). +* [`Counters`](/contracts/3.x/api/utils#Counters): moved to [`utils`](/contracts/3.x/api/utils). +* [`Strings`](/contracts/3.x/api/utils#Strings): moved to [`utils`](/contracts/3.x/api/utils). +* [`SignedSafeMath`](/contracts/3.x/api/utils#SignedSafeMath): moved to [`utils`](/contracts/3.x/api/utils). + +Removed contracts are still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the [v2.x documentation] when working with them. diff --git a/docs/content/contracts/3.x/erc1155.mdx b/docs/content/contracts/3.x/erc1155.mdx new file mode 100644 index 00000000..c91cd90b --- /dev/null +++ b/docs/content/contracts/3.x/erc1155.mdx @@ -0,0 +1,145 @@ +--- +title: ERC1155 +--- + +ERC1155 is a novel token standard that aims to take the best from previous standards to create a [**fungibility-agnostic**](/contracts/3.x/tokens#different-kinds-of-tokens) and **gas-efficient** [token contract](/contracts/3.x/tokens#but-first-coffee-a-primer-on-token-contracts). + + +ERC1155 draws ideas from all of [ERC20](/contracts/3.x/erc20), [ERC721](/contracts/3.x/erc721), and [ERC777](/contracts/3.x/erc777). If you’re unfamiliar with those standards, head to their guides before moving on. + + +## Multi Token Standard + +The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This is why its [`balanceOf`](/contracts/3.x/api/token/ERC1155#IERC1155-balanceOf-address-uint256-) function differs from ERC20’s and ERC777’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 ERC721 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 ERC721 [`balanceOf`](/contracts/3.x/api/token/ERC721#IERC721-balanceOf-address-) function refers to _how many different tokens_ an account has, not how many of each. On the other hand, in ERC1155 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 ERC1155 token contract can hold the entire system state, reducing deployment costs and complexity. + +## 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, [`balanceOfBatch`](/contracts/3.x/api/token/ERC1155#IERC1155-balanceOfBatch-address---uint256---) and [`safeBatchTransferFrom`](/contracts/3.x/api/token/ERC1155#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-), 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 [`_mintBatch`](/contracts/3.x/api/token/ERC1155#ERC1155-_mintBatch-address-uint256---uint256---bytes-). + +## Constructing an ERC1155 Token Contract + +We’ll use ERC1155 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. + + +For an overview of minting mechanisms check out [Creating ERC20 Supply](/contracts/3.x/erc20-supply). + + +Here’s what a contract for tokenized items might look like: + +```solidity +// contracts/GameItems.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +contract GameItems is ERC1155 + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + constructor() public ERC1155("https://game.example/api/item/{id.json") + _mint(msg.sender, GOLD, 10**18, ""); + _mint(msg.sender, SILVER, 10**27, ""); + _mint(msg.sender, THORS_HAMMER, 1, ""); + _mint(msg.sender, SWORD, 10**9, ""); + _mint(msg.sender, SHIELD, 10**9, ""); + +} +``` + +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 [`ERC1155`](/contracts/3.x/api/token/ERC1155#ERC1155) contract includes the optional extension [`IERC1155MetadataURI`](/contracts/3.x/api/token/ERC1155#IERC1155MetadataURI). That’s where the [`uri`](/contracts/3.x/api/token/ERC1155#IERC1155MetadataURI-uri-uint256-) function comes from: we use it to retrieve the metadata uri. + +Also note that, unlike ERC20, ERC1155 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: +```javascript +> gameItems.balanceOf(deployerAddress,3) +1000000000 +``` + +We can transfer items to player accounts: +```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: +```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: + +```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: + +```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 [ERC-1155 Metadata URI JSON Schema](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema). + + +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! + + +## Sending Tokens to Contracts + +A key difference when using [`safeTransferFrom`](/contracts/3.x/api/token/ERC1155#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) is that token transfers to other contracts may revert with the following message: + +```text +ERC1155: transfer to non ERC1155Receiver implementer +``` + +This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC1155 protocol, so transfers to it are disabled to **prevent tokens from being locked forever**. As an example, [the Golem contract currently holds over 350k `GNT` tokens](https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d), worth multiple tens of thousands of dollars, 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 ERC1155 tokens we can inherit from the convenience contract [`ERC1155Holder`](/contracts/3.x/api/token/ERC1155#ERC1155Holder) which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155Holder.sol"; + +contract MyContract is ERC1155Holder + +``` + +We can also implement more complex scenarios using the [`onERC1155Received`](/contracts/3.x/api/token/ERC1155#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and [`onERC1155BatchReceived`](/contracts/3.x/api/token/ERC1155#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) functions. + +## Preset ERC1155 contract +A preset ERC1155 is available, [`ERC1155PresetMinterPauser`](/contracts/3.x/api/presets#ERC1155PresetMinterPauser). It is preset to allow for token minting (create) - including batch minting, stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses [Access Control](/contracts/3.x/access-control) to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. + +This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. diff --git a/docs/content/contracts/3.x/erc20-supply.mdx b/docs/content/contracts/3.x/erc20-supply.mdx new file mode 100644 index 00000000..8acb31bc --- /dev/null +++ b/docs/content/contracts/3.x/erc20-supply.mdx @@ -0,0 +1,105 @@ +--- +title: Creating ERC20 Supply +--- + +In this guide you will learn how to create an ERC20 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 ERC20, and Contracts includes a widely used implementation of it: the aptly named [`ERC20`](/contracts/3.x/api/token/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 ERC20 document. Every token is free to experiment with their own mechanisms, ranging from the most decentralized to the most centralized, from the most naive to the most researched, and more. + +## 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: + +```solidity +contract ERC20FixedSupply is ERC20 + constructor() public { + 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 [`_mint`](/contracts/3.x/api/token/ERC20#ERC20-_mint-address-uint256-) function that will do exactly this: + +```solidity +contract ERC20FixedSupply is ERC20 + constructor() public 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 Miners + +The internal [`_mint`](/contracts/3.x/api/token/ERC20#ERC20-_mint-address-uint256-) function is the key building block that allows us to write ERC20 extensions that implement a supply mechanism. + +The mechanism we will implement is a token reward for the miners that produce Ethereum blocks. In Solidity we can access the address of the current block’s miner in the global variable `block.coinbase`. 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! + +```solidity +contract ERC20WithMinerReward is ERC20 + constructor() public ERC20("Reward", "RWD") { + + function mintMinerReward() public + _mint(block.coinbase, 1000); + +} +``` + +As we can see, `_mint` makes it super easy to do this correctly. + +## Modularizing the Mechanism + +There is one supply mechanism already included in Contracts: `ERC20PresetMinterPauser`. This is a generic mechanism in which a set of accounts is assigned the `minter` role, granting them the permission to call a `mint` function, an external version of `_mint`. + +This can be used for centralized minting, where an externally owned account (i.e. someone with a pair of cryptographic keys) decides how much supply to create and for whom. There are very legitimate use cases for this mechanism, such as [traditional asset-backed stablecoins](https://medium.com/reserve-currency/why-another-stablecoin-866f774afede#3aea). + +The accounts with the minter role don’t need to be externally owned, though, and can just as well be smart contracts that implement a trustless mechanism. We can in fact implement the same behavior as the previous section. + +```solidity +contract MinerRewardMinter + ERC20PresetMinterPauser _token; + + constructor(ERC20PresetMinterPauser token) public { + _token = token; + + + function mintMinerReward() public + _token.mint(block.coinbase, 1000); + +} +``` + +This contract, when initialized with an `ERC20PresetMinterPauser` instance, and granted the `minter` role for that contract, will result in exactly the same behavior implemented in the previous section. What is interesting about using `ERC20PresetMinterPauser` is that we can easily combine multiple supply mechanisms by assigning the role to multiple contracts, and moreover that we can do this dynamically. + + +To learn more about roles and permissioned systems, head to our [Access Control guide](/contracts/3.x/access-control). + + +## Automating the Reward + +So far our supply mechanisms were triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the [`_beforeTokenTransfer`](/contracts/3.x/api/token/ERC20#ERC20-_beforeTokenTransfer-address-address-uint256-) hook (see [Using Hooks](/contracts/3.x/extending-contracts#using-hooks)). + +Adding to the supply mechanism from previous sections, we can use this hook to mint a miner reward for every token transfer that is included in the blockchain. + +```solidity +contract ERC20WithAutoMinerReward is ERC20 + constructor() public ERC20("Reward", "RWD") { + + function _mintMinerReward() internal + _mint(block.coinbase, 1000); + + + function _beforeTokenTransfer(address from, address to, uint256 value) internal virtual override + _mintMinerReward(); + super._beforeTokenTransfer(from, to, value); + +} +``` + +## Wrapping Up + +We’ve seen two ways to implement ERC20 supply mechanisms: internally through `_mint`, and externally through `ERC20PresetMinterPauser`. Hopefully this has helped you understand how to use OpenZeppelin and some of the design principles behind it, and you can apply them to your own smart contracts. diff --git a/docs/content/contracts/3.x/erc20.mdx b/docs/content/contracts/3.x/erc20.mdx new file mode 100644 index 00000000..e5e916a3 --- /dev/null +++ b/docs/content/contracts/3.x/erc20.mdx @@ -0,0 +1,77 @@ +--- +title: ERC20 +--- + +An ERC20 token contract keeps track of [_fungible_ tokens](/contracts/3.x/tokens#different-kinds-of-tokens): any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC20 tokens useful for things like a **medium of exchange currency**, **voting rights**, **staking**, and more. + +OpenZeppelin Contracts provides many ERC20-related contracts. On the [`API reference`](/contracts/3.x/api/token/ERC20) you’ll find detailed information on their properties and usage. + +## Constructing an ERC20 Token Contract + +Using Contracts, we can easily create our own ERC20 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. + +```solidity +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract GLDToken is ERC20 + constructor(uint256 initialSupply) public ERC20("Gold", "GLD") { + _mint(msg.sender, initialSupply); + +} +``` + +Our contracts are often used via [inheritance](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance), and here we’re reusing [`ERC20`](/contracts/3.x/api/token/ERC20#erc20) for both the basic standard implementation and the [`name`](/contracts/3.x/api/token/ERC20#ERC20-name--), [`symbol`](/contracts/3.x/api/token/ERC20#ERC20-symbol--), and [`decimals`](/contracts/3.x/api/token/ERC20#ERC20-decimals--) optional extensions. Additionally, we’re creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. + + +For a more complete discussion of ERC20 supply mechanisms, see [Creating ERC20 Supply](/contracts/3.x/erc20-supply). + + +That’s it! Once deployed, we will be able to query the deployer’s balance: + +```javascript +> GLDToken.balanceOf(deployerAddress) +1000000000000000000000 +``` + +We can also [transfer](/contracts/3.x/api/token/ERC20#IERC20-transfer-address-uint256-) these tokens to other accounts: + +```javascript +> GLDToken.transfer(otherAddress, 300000000000000000000) +> GLDToken.balanceOf(otherAddress) +300000000000000000000 +> GLDToken.balanceOf(deployerAddress) +700000000000000000000 +``` + +## 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, [`ERC20`](/contracts/3.x/api/token/ERC20#ERC20) provides a [`decimals`](/contracts/3.x/api/token/ERC20#ERC20-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 ERC20 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`. + + +By default, `ERC20` uses a value of `18` for `decimals`. To use a different value, you will need to call [_setupDecimals](/contracts/3.x/api/token/ERC20#ERC20-_setupDecimals-uint8-) in your constructor. + + +So if you want to send `5` tokens using a token contract with 18 decimals, the the method to call will actually be: + +```solidity +transfer(recipient, 5 * 10^18); +``` + +## Preset ERC20 contract +A preset ERC20 is available, [`ERC20PresetMinterPauser`](/contracts/3.x/api/presets#ERC20PresetMinterPauser). It is preset to allow for token minting (create), stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses [Access Control](/contracts/3.x/access-control) to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. + +This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. diff --git a/docs/content/contracts/3.x/erc721.mdx b/docs/content/contracts/3.x/erc721.mdx new file mode 100644 index 00000000..26debd91 --- /dev/null +++ b/docs/content/contracts/3.x/erc721.mdx @@ -0,0 +1,86 @@ +--- +title: ERC721 +--- + +We’ve discussed how you can make a _fungible_ token using [ERC20](/contracts/3.x/erc20), but what if not all tokens are alike? This comes up in situations like **real estate** or **collectibles**, where some items are valued more than others, due to their usefulness, rarity, etc. ERC721 is a standard for representing ownership of [_non-fungible_ tokens](/contracts/3.x/tokens#different-kinds-of-tokens), that is, where each token is unique. + +ERC721 is a more complex standard than ERC20, 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 [API Reference](/contracts/3.x/api/token/ERC721) to learn more about these. + +## Constructing an ERC721 Token Contract + +We’ll use ERC721 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 any account can call `awardItem` to mint items. To restrict what accounts can mint items we can add [Access Control](/contracts/3.x/access-control). + +Here’s what a contract for tokenized items might look like: + +```solidity +// contracts/GameItem.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; + +contract GameItem is ERC721 + using Counters for Counters.Counter; + Counters.Counter private _tokenIds; + + constructor() public ERC721("GameItem", "ITM") { + + function awardItem(address player, string memory tokenURI) + public + returns (uint256) + + _tokenIds.increment(); + + uint256 newItemId = _tokenIds.current(); + _mint(player, newItemId); + _setTokenURI(newItemId, tokenURI); + + return newItemId; + +} +``` + +The [`ERC721`](/contracts/3.x/api/token/ERC721#ERC721) contract includes all standard extensions ([`IERC721Metadata`](/contracts/3.x/api/token/ERC721#IERC721Metadata) and [`IERC721Enumerable`](/contracts/3.x/api/token/ERC721#IERC721Enumerable)). That’s where the [`_setTokenURI`](/contracts/3.x/api/token/ERC721#ERC721-_setTokenURI-uint256-string-) method comes from: we use it to store an item’s metadata. + +Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +New items can be created: + +```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: + +```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: + +```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 [ERC721 specification](https://eips.ethereum.org/EIPS/eip-721). + + +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! If you’d like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly). You could also leverage IPFS to store the tokenURI information, but these techniques are out of the scope of this overview guide. + + +## Preset ERC721 contract +A preset ERC721 is available, [`ERC721PresetMinterPauserAutoId`](/contracts/3.x/api/presets#ERC721PresetMinterPauserAutoId). It is preset to allow for token minting (create) with token ID and URI auto generation, stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses [Access Control](/contracts/3.x/access-control) to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. + +This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. diff --git a/docs/content/contracts/3.x/erc777.mdx b/docs/content/contracts/3.x/erc777.mdx new file mode 100644 index 00000000..54cd448f --- /dev/null +++ b/docs/content/contracts/3.x/erc777.mdx @@ -0,0 +1,72 @@ +--- +title: ERC777 +--- + +Like [ERC20](/contracts/3.x/erc20), ERC777 is a standard for [_fungible_ tokens](/contracts/3.x/tokens#different-kinds-of-tokens), and is focused around allowing more complex interactions when trading tokens. More generally, it brings tokens and Ether closer together by providing the equivalent of a `msg.value` field, but for tokens. + +The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around `decimals`, minting and burning with proper events, among others, but its killer feature is **receive hooks**. A hook is simply a function in a contract that is called when tokens are sent to it, meaning **accounts and contracts can react to receiving tokens**. + +This enables a lot of interesting use cases, including atomic purchases using tokens (no need to do `approve` and `transferFrom` in two separate transactions), rejecting reception of tokens (by reverting on the hook call), redirecting the received tokens to other addresses (similarly to how [`PaymentSplitter`](/contracts/3.x/api/payment#PaymentSplitter) does it), among many others. + +Furthermore, since contracts are required to implement these hooks in order to receive tokens, _no tokens can get stuck in a contract that is unaware of the ERC777 protocol_, as has happened countless times when using ERC20s. + +## What If I Already Use ERC20? + +The standard has you covered! The ERC777 standard is **backwards compatible with ERC20**, meaning you can interact with these tokens as if they were ERC20, using the standard functions, while still getting all of the niceties, including send hooks. See the [EIP’s Backwards Compatibility section](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility) to learn more. + +## Constructing an ERC777 Token Contract + +We will replicate the `GLD` example of the [ERC20 guide](/contracts/3.x/erc20#constructing-an-erc20-token-contract), this time using ERC777. As always, check out the [`API reference`](/contracts/3.x/api/token/ERC777) to learn more about the details of each function. + +```solidity +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC777/ERC777.sol"; + +contract GLDToken is ERC777 + constructor(uint256 initialSupply, address[] memory defaultOperators) + public + ERC777("Gold", "GLD", defaultOperators) + { + _mint(msg.sender, initialSupply, "", ""); + +} +``` + +In this case, we’ll be extending from the [`ERC777`](/contracts/3.x/api/token/ERC777#ERC777) contract, which provides an implementation with compatibility support for ERC20. The API is quite similar to that of [`ERC777`](/contracts/3.x/api/token/ERC777#ERC777), and we’ll once again make use of [`_mint`](/contracts/3.x/api/token/ERC777#ERC777-_mint-address-address-uint256-bytes-bytes-) to assign the `initialSupply` to the deployer account. Unlike [ERC20’s `_mint`](/contracts/3.x/api/token/ERC20#ERC20-_mint-address-uint256-), this one includes some extra parameters, but you can safely ignore those for now. + +You’ll notice both [`name`](/contracts/3.x/api/token/ERC777#IERC777-name--) and [`symbol`](/contracts/3.x/api/token/ERC777#IERC777-symbol--) are assigned, but not [`decimals`](/contracts/3.x/api/token/ERC777#ERC777-decimals--). The ERC777 specification makes it mandatory to include support for these functions (unlike ERC20, where it is optional and we had to include [`ERC20Detailed`](/contracts/3.x/api/token/ERC20#ERC20Detailed)), but also mandates that `decimals` always returns a fixed value of `18`, so there’s no need to set it ourselves. For a review of ``decimals`’s role and importance, refer back to our [ERC20 guide](/contracts/3.x/erc20#a-note-on-decimals). + +Finally, we’ll need to set the [`defaultOperators`](/contracts/3.x/api/token/ERC777#IERC777-defaultOperators--): special accounts (usually other smart contracts) that will be able to transfer tokens on behalf of their holders. If you’re not planning on using operators in your token, you can simply pass an empty array. _Stay tuned for an upcoming in-depth guide on ERC777 operators!_ + +That’s it for a basic token contract! We can now deploy it, and use the same [`balanceOf`](/contracts/3.x/api/token/ERC777#IERC777-balanceOf-address-) method to query the deployer’s balance: + +```javascript +> GLDToken.balanceOf(deployerAddress) +1000 +``` + +To move tokens from one account to another, we can use both [``ERC20`’s `transfer`](/contracts/3.x/api/token/ERC777#ERC777-transfer-address-uint256-) method, or the new [``ERC777`’s `send`](/contracts/3.x/api/token/ERC777#ERC777-send-address-uint256-bytes-), which fulfills a very similar role, but adds an optional `data` field: + +```javascript +> GLDToken.transfer(otherAddress, 300) +> GLDToken.send(otherAddress, 300, "") +> GLDToken.balanceOf(otherAddress) +600 +> GLDToken.balanceOf(deployerAddress) +400 +``` + +## Sending Tokens to Contracts + +A key difference when using [`send`](/contracts/3.x/api/token/ERC777#ERC777-send-address-uint256-bytes-) is that token transfers to other contracts may revert with the following message: + +```text +ERC777: token recipient contract has no implementer for ERC777TokensRecipient +``` + +This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC777 protocol, so transfers to it are disabled to **prevent tokens from being locked forever**. As an example, [the Golem contract currently holds over 350k `GNT` tokens](https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d), worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. + +_An upcoming guide will cover how a contract can register itself as a recipient, send and receive hooks, and other advanced features of ERC777!_ diff --git a/docs/content/contracts/3.x/extending-contracts.mdx b/docs/content/contracts/3.x/extending-contracts.mdx new file mode 100644 index 00000000..011c98de --- /dev/null +++ b/docs/content/contracts/3.x/extending-contracts.mdx @@ -0,0 +1,129 @@ +--- +title: Extending Contracts +--- + +Most of the OpenZeppelin Contracts are expected to be used via [inheritance](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance): you will _inherit_ from them when writing your own contracts. + +This is the commonly found `is` syntax, like in `contract MyToken is ERC20`. + + + +Unlike ``contract``s, Solidity ``library``s are not inherited from and instead rely on the [`using for`](https://solidity.readthedocs.io/en/latest/contracts.html#using-for) syntax. + +OpenZeppelin Contracts has some ``library``s: most are in the [Utils](/contracts/3.x/api/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 [`AccessControl`](/contracts/3.x/api/access#AccessControl) so that [`revokeRole`](/contracts/3.x/api/access#AccessControl-revokeRole-bytes32-address-) can no longer be called. This can be achieved using overrides: + +```solidity +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract ModifiedAccessControl is AccessControl + // Override the revokeRole function + function revokeRole(bytes32, address) public override { + revert("ModifiedAccessControl: cannot revoke roles"); + +} +``` + +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. + + +For more information on how overrides work, head over to the [official Solidity documentation](https://solidity.readthedocs.io/en/latest/contracts.html#index-17). + + +Here is a modified version of [`AccessControl`](/contracts/3.x/api/access#AccessControl) where [`revokeRole`](/contracts/3.x/api/access#AccessControl-revokeRole-bytes32-address-) cannot be used to revoke the `DEFAULT_ADMIN_ROLE`: + +```solidity +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract ModifiedAccessControl is AccessControl + function revokeRole(bytes32 role, address account) public override { + require( + role != DEFAULT_ADMIN_ROLE, + "ModifiedAccessControl: cannot revoke default admin role" + ); + + 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. + + +As of v3.0.0, `view` functions are not `virtual` in OpenZeppelin, and therefore cannot be overridden. We’re considering [lifting this restriction](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2154) in an upcoming release. Let us know if this is something you care about! + + +## Using Hooks + +Sometimes, in order to extend a parent contract you will need to override multiple related functions, which leads to code duplication and increased likelihood of bugs. + +For example, consider implementing safe [`ERC20`](/contracts/3.x/api/token/ERC20#ERC20) transfers in the style of [`IERC721Receiver`](/contracts/3.x/api/token/ERC721#IERC721Receiver). You may think overriding [`transfer`](/contracts/3.x/api/token/ERC20#ERC20-transfer-address-uint256-) and [`transferFrom`](/contracts/3.x/api/token/ERC20#ERC20-transferFrom-address-address-uint256-) would be enough, but what about [`_transfer`](/contracts/3.x/api/token/ERC20#ERC20-_transfer-address-address-uint256-) and [`_mint`](/contracts/3.x/api/token/ERC20#ERC20-_mint-address-uint256-)? To prevent you from having to deal with these details, we introduced ***hooks***. + +Hooks are simply functions that are called before or after some action takes place. They provide a centralized point to _hook into_ and extend the original behavior. + +Here’s how you would implement the `IERC721Receiver` pattern in `ERC20`, using the [`_beforeTokenTransfer`](/contracts/3.x/api/token/ERC20#ERC20-_beforeTokenTransfer-address-address-uint256-) hook: + +```solidity +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20WithSafeTransfer is ERC20 + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override + { + super._beforeTokenTransfer(from, to, amount); + + require(_validRecipient(to), "ERC20WithSafeTransfer: invalid recipient"); + + function _validRecipient(address to) private view returns (bool) { + ... + + + ... +} +``` + +Using hooks this way leads to cleaner and safer code, without having to rely on a deep understanding of the parent’s internals. + + + +Hooks are a new feature of OpenZeppelin Contracts v3.0.0, and we’re eager to learn how you plan to use them! + +So far, the only available hook is `_beforeTransferHook`, in all of [`ERC20`](/contracts/3.x/api/token/ERC20#ERC20-_beforeTokenTransfer-address-address-uint256-), [`ERC721`](/contracts/3.x/api/token/ERC721#ERC721-_beforeTokenTransfer-address-address-uint256-), [`ERC777`](/contracts/3.x/api/token/ERC777#ERC777-_beforeTokenTransfer-address-address-address-uint256-) and [`ERC1155`](/contracts/3.x/api/token/ERC1155#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-). If you have ideas for new hooks, let us know! + + +### Rules of Hooks + +There’s a few guidelines you should follow when writing code that uses hooks in order to prevent issues. They are very simple, but do make sure you follow them: + +1. Whenever you override a parent’s hook, re-apply the `virtual` attribute to the hook. That will allow child contracts to add more functionality to the hook. +2. ***Always*** call the parent’s hook in your override using `super`. This will make sure all hooks in the inheritance tree are called: contracts like [`ERC20Pausable`](/contracts/3.x/api/token/ERC20#ERC20Pausable) rely on this behavior. + +```solidity +contract MyToken is ERC20 + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override // Add virtual here! + { + super._beforeTokenTransfer(from, to, amount); // Call parent hook + ... + +} +``` +That’s it! Enjoy simpler code using hooks! diff --git a/docs/content/contracts/3.x/gsn-strategies.mdx b/docs/content/contracts/3.x/gsn-strategies.mdx new file mode 100644 index 00000000..e73118b6 --- /dev/null +++ b/docs/content/contracts/3.x/gsn-strategies.mdx @@ -0,0 +1,166 @@ +--- +title: GSN Strategies +--- + +This guide shows you different strategies to accept relayed calls via the Gas Station Network (GSN) using OpenZeppelin Contracts. + +First, we will go over what a 'GSN strategy' is, and then showcase how to use the two most common strategies. +Finally, we will cover how to create your own custom strategies. + +If you’re still learning about the basics of the Gas Station Network, you should first head over to the [GSN Guide](/contracts/3.x/gsn). + +## GSN Strategies Explained + +A **GSN strategy** decides which relayed call gets approved and which relayed call gets rejected. Strategies are a key concept within the GSN. Dapps need a strategy to prevent malicious users from spending the dapp’s funds for relayed call fees. + +As we have seen in the [GSN Guide](/contracts/3.x/gsn), in order to be GSN enabled, your contracts need to extend from [`GSNRecipient`](/contracts/3.x/api/GSN#GSNRecipient). + +A GSN recipient contract needs the following to work: + +1. It needs to have funds deposited on its RelayHub. +2. It needs to handle `msg.sender` and `msg.data` differently. +3. It needs to decide how to approve and reject relayed calls. + +Depositing funds for the GSN recipient contract can be done via the [GSN Dapp tool] or programmatically with [**OpenZeppelin GSN Helpers**]. + +The actual user’s `msg.sender` and `msg.data` can be obtained safely via [`_msgSender()`](/contracts/3.x/api/GSN#GSNRecipient-_msgSender--) and [`_msgData()`](/contracts/3.x/api/GSN#GSNRecipient-_msgData--) of [`GSNRecipient`](/contracts/3.x/api/GSN#GSNRecipient). + +Deciding how to approve and reject relayed calls is a bit more complex. Chances are you probably want to choose which users can use your contracts via the GSN and potentially charge them for it, like a bouncer at a nightclub. We call these _GSN strategies_. + +The base [`GSNRecipient`](/contracts/3.x/api/GSN#GSNRecipient) contract doesn’t include a strategy, so you must either use one of the pre-built ones or write your own. We will first go over using the included strategies: [`GSNRecipientSignature`](/contracts/3.x/api/GSN#GSNRecipientSignature) and [`GSNRecipientERC20Fee`](/contracts/3.x/api/GSN#GSNRecipientERC20Fee). + +## `GSNRecipientSignature` + +[`GSNRecipientSignature`](/contracts/3.x/api/GSN#GSNRecipientSignature) lets users relay calls via the GSN to your recipient contract (charging you for it) if they can prove that an account you trust approved them to do so. The way they do this is via a _signature_. + +The relayed call must include a signature of the relayed call parameters by the same account that was added to the contract as a trusted signer. If it is not the same, `GSNRecipientSignature` will not accept the relayed call. + +This means that you need to set up a system where your trusted account signs the relayed call parameters to then include in the relayed call, as long as they are valid users (according to your business logic). + +The definition of a valid user depends on your system, but an example is users that have completed their sign up via some kind of [OAuth](https://en.wikipedia.org/wiki/OAuth) and validation, e.g. gone through a captcha or validated their email address. +You could restrict it further and let new users send a specific number of relayed calls (e.g. limit to 5 relayed calls via the GSN, at which point the user needs to create a wallet). +Alternatively, you could charge the user off-chain (e.g. via credit card) for credit on your system and let them create relayed calls until their credit runs out. + +The great thing about this setup is that **your contract doesn’t need to change** if you want to change the business rules. All you are doing is changing the backend logic conditions under which users use your contract for free. +On the other hand, you need to have a backend server, microservice, or lambda function to accomplish this. + +### How Does `GSNRecipientSignature` Work? + +`GSNRecipientSignature` decides whether or not to accept the relayed call based on the included signature. + +The `acceptRelayedCall` implementation recovers the address from the signature of the relayed call parameters in `approvalData` and compares it to the trusted signer. +If the included signature matches the trusted signer, the relayed call is approved. +On the other hand, when the included signature doesn’t match the trusted signer, the relayed call gets rejected with an error code of `INVALID_SIGNER`. + +### How to Use `GSNRecipientSignature` + +You will need to create an off-chain service (e.g. backend server, microservice, or lambda function) that your dapp calls to sign (or not sign, based on your business logic) the relayed call parameters with your trusted signer account. The signature is then included as the `approvalData` in the relayed call. + +Instead of using `GSNRecipient` directly, your GSN recipient contract will instead inherit from `GSNRecipientSignature`, as well as setting the trusted signer in the constructor of `GSNRecipientSignature` as per the following sample code below: + +```solidity +import "@openzeppelin/contracts/GSN/GSNRecipientSignature.sol"; + +contract MyContract is GSNRecipientSignature + constructor(address trustedSigner) public GSNRecipientSignature(trustedSigner) { + +} +``` + + +We wrote an in-depth guide on how to setup a signing server that works with `GSNRecipientSignature`, [check it out!](https://forum.openzeppelin.com/t/advanced-gsn-gsnrecipientsignature-sol/1414) + + +## `GSNRecipientERC20Fee` + +[`GSNRecipientERC20Fee`](/contracts/3.x/api/GSN#GSNRecipientERC20Fee) is a bit more complex (but don’t worry, it has already been written for you!). Unlike `GSNRecipientSignature`, `GSNRecipientERC20Fee` doesn’t require any off-chain services. +Instead of off-chain approving each relayed call, you will give special-purpose ERC20 tokens to your users. These tokens are then used as payment for relayed calls to your recipient contract. +Any user that has enough tokens to pay has their relayed calls automatically approved and the recipient contract will cover their transaction costs! + +Each recipient contract has their own special-purpose token. The exchange rate from token to ether is 1:1, as the tokens are used to pay your contract to cover the gas fees when using the GSN. + +`GSNRecipientERC20Fee` has an internal [`_mint`](/contracts/3.x/api/GSN#GSNRecipientERC20Fee-_mint-address-uint256-) function. Firstly, you need to setup a way to call it (e.g. add a public function with some form of [access control](/contracts/3.x/access-control) such as [`onlyMinter`](/contracts/3.x/api/access#MinterRole-onlyMinter--)). +Then, issue tokens to users based on your business logic. For example, you could mint a limited amount of tokens to new users, mint tokens when users buy them off-chain, give tokens based on a users subscription, etc. + + +**Users do not need to call approve** on their tokens for your recipient contract to use them. They are a modified ERC20 variant that lets the recipient contract retrieve them. + + +### How Does `GSNRecipientERC20Fee` Work? + +`GSNRecipientERC20Fee` decides to approve or reject relayed calls based on the balance of the users tokens. + +The `acceptRelayedCall` function implementation checks the users token balance. +If the user doesn’t have enough tokens the relayed call gets rejected with an error of `INSUFFICIENT_BALANCE`. +If there are enough tokens, the relayed call is approved with the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data being returned so it can be used in `_preRelayedCall` and `_postRelayedCall`. + +In `_preRelayedCall` function the `maxPossibleCharge` amount of tokens is transferred to the recipient contract. +The maximum amount of tokens required is transferred assuming that the relayed call will use all the gas available. +Then, in the `_postRelayedCall` function, the actual amount is calculated, including the recipient contract implementation and ERC20 token transfers, and the difference is refunded. + +The maximum amount of tokens required is transferred in `_preRelayedCall` to protect the contract from exploits (this is really similar to how ether is locked in Ethereum transactions). + + +The gas cost estimation is not 100% accurate, we may tweak it further down the road. + + + +Always use `_preRelayedCall` and `_postRelayedCall` functions. Internal `_preRelayedCall` and `_postRelayedCall` functions are used instead of public `preRelayedCall` and `postRelayedCall` functions, as the public functions are prevented from being called by non-RelayHub contracts. + + +### How to Use `GSNRecipientERC20Fee` + +Your GSN recipient contract needs to inherit from `GSNRecipientERC20Fee` along with appropriate [access control](/contracts/3.x/access-control) (for token minting), set the token details in the constructor of `GSNRecipientERC20Fee` and create a public `mint` function suitably protected by your chosen access control as per the following sample code (which uses [`AccessControl`](/contracts/3.x/api/access#AccessControl)): + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/GSN/GSNRecipientERC20Fee.sol"; +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract MyContract is GSNRecipientERC20Fee, AccessControl + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor() public GSNRecipientERC20Fee("FeeToken", "FEE") { + _setupRole(MINTER_ROLE, _msgSender()); + + + function _msgSender() internal view override(Context, GSNRecipient) returns (address payable) + return GSNRecipient._msgSender(); + + + function _msgData() internal view override(Context, GSNRecipient) returns (bytes memory) + return GSNRecipient._msgData(); + + + function mint(address account, uint256 amount) public + require(hasRole(MINTER_ROLE, _msgSender()), "Caller is not a minter"); + _mint(account, amount); + +} +``` + +## Custom Strategies + +If the included strategies don’t quite fit your business needs, you can also write your own! For example, your custom strategy could use a specified token to pay for relayed calls with a custom exchange rate to ether. Alternatively you could issue users who subscribe to your dapp ERC721 tokens, and accounts holding the subscription token could use your contract for free as part of the subscription. There are lots of potential options! + +To write a custom strategy, simply inherit from `GSNRecipient` and implement the `acceptRelayedCall`, `_preRelayedCall` and `_postRelayedCall` functions. + +Your `acceptRelayedCall` implementation decides whether or not to accept the relayed call: return `_approveRelayedCall` to accept, and return `_rejectRelayedCall` with an error code to reject. + +Not all GSN strategies use `_preRelayedCall` and `_postRelayedCall` (though they must still be implemented, e.g. `GSNRecipientSignature` leaves them empty), but are useful when your strategy involves charging end users. + +`_preRelayedCall` should take the maximum possible charge, with `_postRelayedCall` refunding any difference from the actual charge once the relayed call has been made. +When returning `_approveRelayedCall` to approve the relayed call, the end users address, `maxPossibleCharge`, `transactionFee` and `gasPrice` data can also be returned so that the data can be used in `_preRelayedCall` and `_postRelayedCall`. +See [the code for `GSNRecipientERC20Fee`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/GSN/GSNRecipientERC20Fee.sol) as an example implementation. + +Once your strategy is ready, all your GSN recipient needs to do is inherit from it: + +```solidity +contract MyContract is MyCustomGSNStrategy + constructor() public MyCustomGSNStrategy() { + +} +``` diff --git a/docs/content/contracts/3.x/gsn.mdx b/docs/content/contracts/3.x/gsn.mdx new file mode 100644 index 00000000..c0fc0388 --- /dev/null +++ b/docs/content/contracts/3.x/gsn.mdx @@ -0,0 +1,90 @@ +--- +title: Writing GSN-capable contracts +--- + +The [Gas Station Network](https://gsn.openzeppelin.com) allows you to build apps where you pay for your users transactions, so they do not need to hold Ether to pay for gas, easing their onboarding process. In this guide, we will learn how to write smart contracts that can receive transactions from the GSN, by using OpenZeppelin Contracts. + +If you’re new to the GSN, you probably want to first take a look at the [overview of the system](/contracts/5.x/learn/sending-gasless-transactions) to get a clearer picture of how gasless transactions are achieved. Otherwise, strap in! + +## Receiving a Relayed Call + +The first step to writing a recipient is to inherit from our GSNRecipient contract. If you’re also inheriting from other contracts, such as ERC20, this will work just fine: adding GSNRecipient will make all of your token functions GSN-callable. + +```solidity +import "@openzeppelin/contracts/GSN/GSNRecipient.sol"; + +contract MyContract is GSNRecipient, ... + + +``` + +### `msg.sender` and `msg.data` + +There’s only one extra detail you need to take care of when working with GSN recipient contracts: _you must never use `msg.sender` or `msg.data` directly_. On relayed calls, `msg.sender` will be `RelayHub` instead of your user! This doesn’t mean however you won’t be able to retrieve your users' addresses: `GSNRecipient` provides two functions, `_msgSender()` and `_msgData()`, which are drop-in replacements for `msg.sender` and `msg.data` while taking care of the low-level details. As long as you use these two functions instead of the original getters, you’re good to go! + + +Third-party contracts you inherit from may not use these replacement functions, making them unsafe to use when mixed with `GSNRecipient`. If in doubt, head on over to our [support forum](https://forum.openzeppelin.com/c/support). + + +### Accepting and Charging + +Unlike regular contract function calls, each relayed call has an additional number of steps it must go through, which are functions of the `IRelayRecipient` interface `RelayHub` will call on your contract. `GSNRecipient` includes this interface but no implementation: most of writing a recipient involves handling these function calls. They are designed to provide flexibility, but basic recipients can safely ignore most of them while still being secure and sound. + +The OpenZeppelin Contracts provide a number of tried-and-tested approaches for you to use out of the box, but you should still have a basic idea of what’s going on under the hood. + +#### `acceptRelayedCall` + +First, RelayHub will ask your recipient contract if it wants to receive a relayed call. Recall that you will be charged for incurred gas costs by the relayer, so you should only accept calls that you’re willing to pay for! + +```solidity + function acceptRelayedCall( + address relay, + address from, + bytes calldata encodedFunction, + uint256 transactionFee, + uint256 gasPrice, + uint256 gasLimit, + uint256 nonce, + bytes calldata approvalData, + uint256 maxPossibleCharge + ) external view returns (uint256, bytes memory); +``` + +There are multiple ways to make this work, including: + +1. having a whitelist of trusted users +2. only accepting calls to an onboarding function +3. charging users in tokens (possibly issued by you) +4. delegating the acceptance logic off-chain + +All relayed call requests can be rejected at no cost to the recipient. + +In this function, you return a number indicating whether you accept the call (0) or not (any other number). You can also return some arbitrary data that will get passed along to the following two functions (pre and post) as an execution context. + +### pre and postRelayedCall + +After a relayed call is accepted, RelayHub will give your contract two opportunities to charge your user for their call, perform some bookkeeping, etc.: _before_ and _after_ the actual relayed call is made. These functions are aptly named `preRelayedCall` and `postRelayedCall`. + +```solidity + +function preRelayedCall(bytes calldata context) external returns (bytes32); +``` + +`preRelayedCall` will inform you of the maximum cost the call may have, and can be used to charge the user in advance. This is useful if the user may spend their allowance as part of the call, so you can lock some funds here. + +```solidity + function postRelayedCall( + bytes calldata context, + bool success, + uint256 actualCharge, + bytes32 preRetVal + ) external; +``` + +`postRelayedCall` will give you an accurate estimate of the transaction cost, making it a natural place to charge users. It will also let you know if the relayed call reverted or not. This allows you, for instance, to not charge users for reverted calls - but remember that you will be charged by the relayer nonetheless. + +These functions allow you to implement, for instance, a flow where you charge your users for the relayed transactions in a custom token. You can lock some of their tokens in `pre`, and execute the actual charge in `post`. This is similar to how gas fees work in Ethereum: the network first locks enough ETH to pay for the transaction’s gas limit at its gas price, and then pays for what it actually spent. + +## Further reading + +Read our [guide on the payment strategies] pre-built and shipped in OpenZeppelin Contracts, or check out [the API reference of the GSN base contracts]. diff --git a/docs/content/contracts/3.x/index.mdx b/docs/content/contracts/3.x/index.mdx new file mode 100644 index 00000000..551e4dfd --- /dev/null +++ b/docs/content/contracts/3.x/index.mdx @@ -0,0 +1,61 @@ +--- +title: Contracts +--- + +**A library for secure smart contract development.** Build on a solid foundation of community-vetted code. + +* Implementations of standards like [ERC20](/contracts/3.x/erc20) and [ERC721](/contracts/3.x/erc721). +* Flexible [role-based permissioning](/contracts/3.x/access-control) scheme. +* Reusable [Solidity components](/contracts/3.x/utilities) to build custom contracts and complex decentralized systems. +* First-class integration with the [Gas Station Network](/contracts/3.x/gsn) for systems with no gas fees! +* [Audited](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/audit) by leading security firms (_last full audit on v2.0.0_). + +## Overview + +### Installation + +```console +$ npm install @openzeppelin/contracts +``` + +OpenZeppelin Contracts features a [stable API](/contracts/3.x/releases-stability#api-stability), which means your contracts won’t break unexpectedly when upgrading to a newer minor version. + +### Usage + +Once installed, you can use the contracts in the library by importing them: + +```solidity +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MyNFT is ERC721 + constructor() ERC721("MyNFT", "MNFT") public { + +} +``` + + +If you’re new to smart contract development, head to [Developing Smart Contracts](/contracts/5.x/learn/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 sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides: + +* [Access Control](/contracts/3.x/access-control): decide who can perform each of the actions on your system. +* [Tokens](/contracts/3.x/tokens): create tradable assets or collectibles, like the well known [ERC20](/contracts/3.x/erc20) and [ERC721](/contracts/3.x/erc721) standards. +* [Gas Station Network](/contracts/3.x/gsn): let your users interact with your contracts without having to pay for gas themselves. +* [Utilities](/contracts/3.x/utilities): generic useful tools, including non-overflowing math, signature verification, and trustless paying systems. + +The [full API](/contracts/3.x/api/token/ERC20) 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](https://forum.openzeppelin.com). + +Finally, you may want to take a look at the [guides on our blog](https://blog.openzeppelin.com/guides/), which cover several common use cases and good practices.. 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. + +* [The Hitchhiker’s Guide to Smart Contracts in Ethereum](https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05) will help you get an overview of the various tools available for smart contract development, and help you set up your environment. +* [A Gentle Introduction to Ethereum Programming, Part 1](https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094) provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. +* For a more in-depth dive, you may read the guide [Designing the architecture for your Ethereum application](https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317), which discusses how to better structure your application and its relationship to the real world. diff --git a/docs/content/contracts/3.x/releases-stability.mdx b/docs/content/contracts/3.x/releases-stability.mdx new file mode 100644 index 00000000..9a6b63b7 --- /dev/null +++ b/docs/content/contracts/3.x/releases-stability.mdx @@ -0,0 +1,71 @@ +--- +title: New Releases and API Stability +--- + +Developing smart contracts is hard, and a conservative approach towards dependencies is sometimes favored. However, it is also very important to stay on top of new releases: these may include bug fixes, or deprecate old patterns in favor of newer and better practices. + +Here we describe when you should expect new releases to come out, and how this affects you as a user of OpenZeppelin Contracts. + +## Release Schedule + +OpenZeppelin Contracts follows a [semantic versioning scheme](#versioning-scheme). + +### Minor Releases + +OpenZeppelin Contracts aims for a new minor release every 1 or 2 months. + +At the beginning of the release cycle we decide which issues we want to prioritize, and assign them to [a milestone on GitHub](https://github.com/OpenZeppelin/openzeppelin-contracts/milestones). During the next five weeks, they are worked on and fixed. + +Once the milestone is complete, we publish a feature-frozen release candidate. The purpose of the release candidate is to have a period where the community can review the new code before the actual release. If important problems are discovered, several more release candidates may be required. After a week of no more changes to the release candidate, the new version is published. + +### Major Releases + +After several months or a year a new major release may come out. These are not scheduled, but will be based on the need to release breaking changes such as a redesign of a core feature of the library (e.g. [access control](https://github.com/OpenZeppelin/openzeppelin-contracts/pulls/2112) in 3.0). Since we value stability, we aim for these to happen infrequently (expect no less than six months between majors). However, we may be forced to release one when there are big changes to the Solidity language. + +## API Stability + +On the [OpenZeppelin 2.0 release](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0), we committed ourselves to keeping a stable API. We aim to more precisely define what we understand by _stable_ and _API_ here, so users of the library can understand these guarantees and be confident their project won’t break unexpectedly. + +In a nutshell, the API being stable means _if your project is working today, it will continue to do so_. New contracts and features will be added in minor releases, but only in a backwards compatible way. + +### Versioning Scheme + +We follow [SemVer](https://semver.org/), which means API breakage may occur between major releases (which [don’t happen very often](#release-schedule)). + +### Solidity Functions + +While the internal implementation of functions may change, their semantics and signature will remain the same. The domain of their arguments will not be less restrictive (e.g. if transferring a value of 0 is disallowed, it will remain disallowed), nor will general state restrictions be lifted (e.g. `whenPaused` modifiers). + +If new functions are added to a contract, it will be in a backwards-compatible way: their usage won’t be mandatory, and they won’t extend functionality in ways that may foreseeable break an application (e.g. [an `internal` method may be added to make it easier to retrieve information that was already available](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1512)). + +#### `internal` + +This extends not only to `external` and `public` functions, but also `internal` ones: many contracts are meant to be used by inheriting them (e.g. `Pausable`, `PullPayment`, the different `Roles` contracts), and are therefore used by calling these functions. Similarly, since all OpenZeppelin Contracts state variables are `private`, they can only be accessed this way (e.g. to create new `ERC20` tokens, instead of manually modifying `totalSupply` and `balances`, `_mint` should be called). + +`private` functions have no guarantees on their behavior, usage, or existence. + +Finally, sometimes language limitations will force us to e.g. make `internal` a function that should be `private` in order to implement features the way we want to. These cases will be well documented, and the normal stability guarantees won’t apply. + +### Libraries + +Some of our Solidity libraries use ``struct``s to handle internal data that should not be accessed directly (e.g. `Roles`). There’s an [open issue](https://github.com/ethereum/solidity/issues/4637) in the Solidity repository requesting a language feature to prevent said access, but it looks like it won’t be implemented any time soon. Because of this, we will use leading underscores and mark said `struct` s to make it clear to the user that its contents and layout are _not_ part of the API. + +### Events + +No events will be removed, and their arguments won’t be changed in any way. New events may be added in later versions, and existing events may be emitted under new, reasonable circumstances (e.g. [from 2.1 on, `ERC20` also emits `Approval` on `transferFrom` calls](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/707)). + +### Gas Costs + +While attempts will generally be made to lower the gas costs of working with OpenZeppelin Contracts, there are no guarantees regarding this. In particular, users should not assume gas costs will not increase when upgrading library versions. + +### Bug Fixes + +The API stability guarantees may need to be broken in order to fix a bug, and we will do so. This decision won’t be made lightly however, and all options will be explored to make the change as non-disruptive as possible. When sufficient, contracts or functions which may result in unsafe behavior will be deprecated instead of removed (e.g. [#1543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1543) and [#1550](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1550)). + +### Solidity Compiler Version + +Starting on version 0.5.0, the Solidity team switched to a faster release cycle, with minor releases every few weeks (v0.5.0 was released on November 2018, and v0.5.5 on March 2019), and major, breaking-change releases every couple of months (with v0.6.0 released on December 2019 and v0.7.0 on July 2020). Including the compiler version in OpenZeppelin Contract’s stability guarantees would therefore force the library to either stick to old compilers, or release frequent major updates simply to keep up with newer Solidity releases. + +Because of this, **the minimum required Solidity compiler version is not part of the stability guarantees**, and users may be required to upgrade their compiler when using newer versions of Contracts. Bug fixes will still be backported to older library releases so that all versions currently in use receive these updates. + +You can read more about the rationale behind this, the other options we considered and why we went down this path [here](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1498#issuecomment-449191611). diff --git a/docs/content/contracts/3.x/tokens.mdx b/docs/content/contracts/3.x/tokens.mdx new file mode 100644 index 00000000..53d532a5 --- /dev/null +++ b/docs/content/contracts/3.x/tokens.mdx @@ -0,0 +1,32 @@ +--- +title: 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 + +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 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 + +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 [fungibility](https://en.wikipedia.org/wiki/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 ERC20 or ERC721 token standards, and that’s why you’re here. Head to our specialized guides to learn more about these: + +* [ERC20](/contracts/3.x/erc20): the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. +* [ERC721](/contracts/3.x/erc721): the de-facto solution for non-fungible tokens, often used for collectibles and games. +* [ERC777](/contracts/3.x/erc777): a richer standard for fungible tokens, enabling new use cases and building on past learnings. Backwards compatible with ERC20. +* [ERC1155](/contracts/3.x/erc1155): 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. diff --git a/docs/content/contracts/3.x/upgradeable.mdx b/docs/content/contracts/3.x/upgradeable.mdx new file mode 100644 index 00000000..702b6eea --- /dev/null +++ b/docs/content/contracts/3.x/upgradeable.mdx @@ -0,0 +1,73 @@ +--- +title: Using with Upgrades +--- + +If your contract is going to be deployed with upgradeability, such as using the [OpenZeppelin Upgrades Plugins](/upgrades-plugins), you will need to use the Upgrade Safe variant of OpenZeppelin Contracts. + +This variant is available as a separate package called `@openzeppelin/contracts-upgradeable`, which is hosted in the repository [OpenZeppelin/openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable). + +It follows all of the rules for [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable): 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 +``` + +### Usage + +The package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`. + +```diff +-import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; ++import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; + +-contract MyCollectible is ERC721 ++contract MyCollectible is ERC721Upgradeable { +``` + +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") public ++ function initialize() initializer public { ++ __ERC721_init("MyCollectible", "MCO"); + +``` + + +Use with multiple inheritance requires special attention. See the section below titled [Multiple Inheritance](#multiple-inheritance). + + +Once this contract is set up and compiled, you can deploy it using the [Upgrades Plugins](/upgrades-plugins). The following snippet shows an example deployment script using Hardhat. + +```js +const ethers, upgrades = require("hardhat"); + +async function main() + const MyCollectible = await ethers.getContractFactory("MyCollectible"); + + const mc = await upgrades.deployProxy(MyCollectible); + + await mc.deployed(); + console.log("MyCollectible deployed to:", mc.address); + + +main(); +``` + +## Further Notes + +### 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. + +### Storage Gaps + +You may notice that every contract includes a state variable named `__gap`. This is empty reserved space in storage that is put in place in Upgrade Safe contracts. It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments. + +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 [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable#modifying-your-contracts). The size of the `__gap` array is calculated so that the amount of storage used by a contract always adds up to the same number (in this case 50 storage slots). diff --git a/docs/content/contracts/3.x/utilities.mdx b/docs/content/contracts/3.x/utilities.mdx new file mode 100644 index 00000000..e9f73baf --- /dev/null +++ b/docs/content/contracts/3.x/utilities.mdx @@ -0,0 +1,96 @@ +--- +title: Utilities +--- + +The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. Here are some of the more popular ones. + +## Cryptography + +### Checking Signatures On-Chain + +[`ECDSA`](/contracts/3.x/api/cryptography#ECDSA) provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via [`web3.eth.sign`](https://web3js.readthedocs.io/en/v1.2.4/web3-eth.html#sign), and are 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 [`ECDSA.recover`](/contracts/3.x/api/cryptography#ECDSA-recover-bytes32-bytes-), 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 [`toEthSignedMessageHash`](/contracts/3.x/api/cryptography#ECDSA-toEthSignedMessageHash-bytes32-). + +```solidity +using ECDSA for bytes32; + +function _verify(bytes32 data, address account) pure returns (bool) + return keccack256(data) + .toEthSignedMessageHash() + .recover(signature) == account; + +``` + + +Getting signature verification right is not trivial: make sure you fully read and understand [`ECDSA`](/contracts/3.x/api/cryptography#ECDSA)'s documentation. + + +### Verifying Merkle Proofs + +[`MerkleProof`](/contracts/3.x/api/cryptography#MerkleProof) provides [`verify`](/contracts/3.x/api/cryptography#MerkleProof-verify-bytes32---bytes32-bytes32-), which can prove that some value is part of a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). + +## Introspection + +In Solidity, it’s frequently helpful to know whether or not a contract supports an interface you’d like to use. ERC165 is a standard that helps do runtime interface detection. Contracts provides helpers both for implementing ERC165 in your contracts and querying other contracts: + +* [`IERC165`](/contracts/3.x/api/introspection#IERC165) — this is the ERC165 interface that defines [`supportsInterface`](/contracts/3.x/api/introspection#IERC165-supportsInterface-bytes4-). When implementing ERC165, you’ll conform to this interface. +* [`ERC165`](/contracts/3.x/api/introspection#ERC165) — inherit this contract if you’d like to support interface detection using a lookup table in contract storage. You can register interfaces using [`_registerInterface(bytes4)`](/contracts/3.x/api/introspection#ERC165-_registerInterface-bytes4-): check out example usage as part of the ERC721 implementation. +* [`ERC165Checker`](/contracts/3.x/api/introspection#ERC165Checker) — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about. +* include with `using ERC165Checker for address;` +* [`myAddress._supportsInterface(bytes4)`](/contracts/3.x/api/introspection#ERC165Checker-_supportsInterface-address-bytes4-) +* [`myAddress._supportsAllInterfaces(bytes4[](/contracts/3.x/api/introspection#ERC165Checker-_supportsAllInterfaces-address-bytes4---))`] + +```solidity +contract MyContract + using ERC165Checker for address; + + bytes4 private InterfaceId_ERC721 = 0x80ac58cd; + + /** + * @dev transfer an ERC721 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 + +The most popular math related library OpenZeppelin Contracts provides is [`SafeMath`](/contracts/3.x/api/math#SafeMath), which provides mathematical functions that protect your contract from overflows and underflows. + +Include the contract with `using SafeMath for uint256;` and then call the functions: + +* `myNumber.add(otherNumber)` +* `myNumber.sub(otherNumber)` +* `myNumber.div(otherNumber)` +* `myNumber.mul(otherNumber)` +* `myNumber.mod(otherNumber)` + +Easy! + +## Payment + +Want to split some payments between multiple people? Maybe you have an app that sends 30% of art purchases to the original creator and 70% of the profits to the current owner; you can build that with [`PaymentSplitter`](/contracts/3.x/api/payment#PaymentSplitter)! + +In Solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the [Ethereum Smart Contract Best Practices](https://consensys.github.io/smart-contract-best-practices/) website. One of the ways to fix reentrancy and stalling problems is, instead of immediately sending Ether to accounts that need it, you can use [`PullPayment`](/contracts/3.x/api/payment#PullPayment), which offers an [`_asyncTransfer`](/contracts/3.x/api/payment#PullPayment-_asyncTransfer-address-uint256-) function for sending money to something and requesting that they [`withdrawPayments()`](/contracts/3.x/api/payment#PullPayment-withdrawPayments-address-payable-) it later. + +If you want to Escrow some funds, check out [`Escrow`](/contracts/3.x/api/payment#Escrow) and [`ConditionalEscrow`](/contracts/3.x/api/payment#ConditionalEscrow) for governing the release of some escrowed Ether. + +## Collections + +If you need support for more powerful collections than Solidity’s native arrays and mappings, take a look at [`EnumerableSet`](/contracts/3.x/api/utils#EnumerableSet) and [`EnumerableMap`](/contracts/3.x/api/utils#EnumerableMap). They 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. + +## Misc + +Want to check if an address is a contract? Use [`Address`](/contracts/3.x/api/utils#Address) and [`Address.isContract()`](/contracts/3.x/api/utils#Address-isContract-address-). + +Want to keep track of some numbers that increment by 1 every time you want another one? Check out [`Counters`](/contracts/3.x/api/utils#Counters). This is useful for lots of things, like creating incremental identifiers, as shown on the [ERC721 guide](/contracts/3.x/erc721). diff --git a/docs/content/contracts/4.x/access-control.mdx b/docs/content/contracts/4.x/access-control.mdx new file mode 100644 index 00000000..7e19fd33 --- /dev/null +++ b/docs/content/contracts/4.x/access-control.mdx @@ -0,0 +1,220 @@ +--- +title: 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 [steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7). + +## 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 [`Ownable`](/contracts/4.x/api/access#Ownable) for implementing ownership in your contracts. + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyContract is Ownable + function normalThing() public { + // anyone can call this normalThing() + + + function specialThing() public onlyOwner + // only the owner can call specialThing()! + +} +``` + +By default, the [`owner`](/contracts/4.x/api/access#Ownable-owner--) of an `Ownable` contract is the account that deployed it, which is usually exactly what you want. + +Ownable also lets you: + +* [`transferOwnership`](/contracts/4.x/api/access#Ownable-transferOwnership-address-) from the owner account to a new one, and +* [`renounceOwnership`](/contracts/4.x/api/access#Ownable-renounceOwnership--) for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + + +Removing the owner altogether will mean that administrative tasks that are protected by `onlyOwner` will no longer be callable! + + +Note that **a contract can also be the owner of another one**! This opens the door to using, for example, a [Gnosis Safe](https://gnosis-safe.io), an [Aragon DAO](https://aragon.org), 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 [MakerDAO](https://makerdao.com), use systems similar to this one. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. [_Role-Based Access Control (RBAC)_](https://en.wikipedia.org/wiki/Role-based_access_control) 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 `AccessControl` + +OpenZeppelin Contracts provides [`AccessControl`](/contracts/4.x/api/access#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 [`ERC20` token](/contracts/4.x/erc20) to define a 'minter' role, which allows accounts that have it create new tokens: + +```solidity +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20, AccessControl + // Create a new role identifier for the minter role + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + constructor(address minter) ERC20("MyToken", "TKN") { + // Grant the minter role to a specified account + _grantRole(MINTER_ROLE, minter); + + + function mint(address to, uint256 amount) public + // Check that the calling account has the minter role + require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); + _mint(to, amount); + +} +``` + + +Make sure you fully understand how [`AccessControl`](/contracts/4.x/api/access#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 ERC20 token example by also defining a 'burner' role, which lets accounts destroy tokens, and by using the `onlyRole` modifier: + +```solidity +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20, AccessControl + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor(address minter, address burner) ERC20("MyToken", "TKN") { + _grantRole(MINTER_ROLE, minter); + _grantRole(BURNER_ROLE, burner); + + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) + _mint(to, amount); + + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) + _burn(from, amount); + +} +``` + +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 [principle of least privilege](https://en.wikipedia.org/wiki/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 Roles + +The ERC20 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 [`AccessControlDefaultAdminRules`](/contracts/4.x/api/access#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 in between steps. + +Let’s take a look at the ERC20 token example, this time taking advantage of the default admin role: + +```solidity +// contracts/MyToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyToken is ERC20, AccessControl + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor() ERC20("MyToken", "TKN") { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) + _mint(to, amount); + + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) + _burn(from, amount); + +} +``` + +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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), 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 + +Because accounts might [grant and revoke roles](#granting-and-revoking-roles) dynamically, it is not always possible to determine which accounts hold a particular role. This is important as it allows to prove 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. + +Under the hood, `AccessControl` uses `EnumerableSet`, a more powerful variant of Solidity’s `mapping` type, which allows for key enumeration. `getRoleMemberCount` can be used to retrieve the number of accounts that have a particular role, and `getRoleMember` can then be called to get the address of each of these accounts. + +```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 [`Ownable`](/contracts/4.x/api/access#Ownable) and [`AccessControl`](/contracts/4.x/api/access#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 [`TimelockController`](/contracts/4.x/api/governance#TimelockController) is addressing. + +The [`TimelockController`](/contracts/4.x/api/governance#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 [`TimelockController`](/contracts/4.x/api/governance#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 [`TimelockController`](/contracts/4.x/api/governance#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 [`AccessControl`](/contracts/4.x/api/access#AccessControl) interface and the `bytes32` values for each role are accessible through the `ADMIN_ROLE`, `PROPOSER_ROLE` and `EXECUTOR_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 enforce a delay on timelock maintenance operations. + + +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. + + +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 [`TimelockController`](/contracts/4.x/api/governance#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 [`getMinDelay`](/contracts/4.x/api/governance#TimelockController-getMinDelay--) method) can be updated by calling the [`updateDelay`](/contracts/4.x/api/governance#TimelockController-updateDelay-uint256-) 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. diff --git a/docs/content/contracts/4.x/api/access.mdx b/docs/content/contracts/4.x/api/access.mdx new file mode 100644 index 00000000..3a7967c7 --- /dev/null +++ b/docs/content/contracts/4.x/api/access.mdx @@ -0,0 +1,2383 @@ +--- +title: "Access" +description: "Smart contract access utilities and implementations" +--- + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +* [`AccessControl`](#AccessControl) provides a general role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts. +* [`Ownable`](#Ownable) is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. + +## Authorization + +[`Ownable`](#Ownable) + +[`Ownable2Step`](#Ownable2Step) + +[`IAccessControl`](#IAccessControl) + +[`AccessControl`](#AccessControl) + +[`AccessControlCrossChain`](#AccessControlCrossChain) + +[`IAccessControlEnumerable`](#IAccessControlEnumerable) + +[`AccessControlEnumerable`](#AccessControlEnumerable) + +[`AccessControlDefaultAdminRules`](#AccessControlDefaultAdminRules) + + + +
+ +## `AccessControl` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/AccessControl.sol"; +``` + +Contract module that allows children to implement role-based access +control mechanisms. This is a lightweight version that doesn't allow enumerating role +members except through off-chain means by accessing the contract event logs. Some +applications may benefit from on-chain enumerability, for those cases see +[`AccessControlEnumerable`](#AccessControlEnumerable). + +Roles are referred to by their `bytes32` identifier. These should be exposed +in the external API and be unique. The best way to achieve this is by +using `public constant` hash digests: + +```solidity +bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); +``` + +Roles can be used to represent a set of permissions. To restrict access to a +function call, use [`AccessControl.hasRole`](#AccessControl-hasRole-bytes32-address-): + +```solidity +function foo() public { + require(hasRole(MY_ROLE, msg.sender)); + ... +} +``` + +Roles can be granted and revoked dynamically via the [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and +[`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-) functions. Each role has an associated admin role, and only +accounts that have a role's admin role can call [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means +that only accounts with this role will be able to grant or revoke other +roles. More complex role relationships can be created by using +[`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). + + +The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to +grant and revoke this role. Extra precautions should be taken to secure +accounts that have been granted it. We recommend using [`AccessControlDefaultAdminRules`](#AccessControlDefaultAdminRules) +to enforce additional security measures for this role. + + +
+

Modifiers

+
+- [onlyRole(role)](#AccessControl-onlyRole-bytes32-) +
+
+ +
+

Functions

+
+- [supportsInterface(interfaceId)](#AccessControl-supportsInterface-bytes4-) +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [_grantRole(role, account)](#AccessControl-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControl-_revokeRole-bytes32-address-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControl +
+
+ +
+

Events

+
+#### ERC165 +#### IERC165 +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

onlyRole(bytes32 role)

+
+

internal

+# +
+
+ +
+ +Modifier that checks that an account has a specific role. Reverts +with a standardized message including the required role. + +The format of the revert reason is given by the following regular expression: + + /^AccessControl: account (0x[0-9a-f][`SafeCast.toUint240`](/contracts/4.x/api/utils#SafeCast-toUint240-uint256-)) is missing role (0x[0-9a-f][`Base64`](/contracts/4.x/api/utils#Base64))$/ + +_Available since v4.1._ + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

hasRole(bytes32 role, address account) → bool

+
+

public

+# +
+
+
+ +Returns `true` if `account` has been granted `role`. + +
+
+ + + +
+
+

_checkRole(bytes32 role)

+
+

internal

+# +
+
+
+ +Revert with a standard message if `_msgSender()` is missing `role`. +Overriding this function changes the behavior of the [`AccessControl.onlyRole`](#AccessControl-onlyRole-bytes32-) modifier. + +Format of the revert message is described in [`AccessControl._checkRole`](#AccessControl-_checkRole-bytes32-address-). + +_Available since v4.6._ + +
+
+ + + +
+
+

_checkRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Revert with a standard message if `account` is missing `role`. + +The format of the revert reason is given by the following regular expression: + + /^AccessControl: account (0x[0-9a-f][`SafeCast.toUint240`](/contracts/4.x/api/utils#SafeCast-toUint240-uint256-)) is missing role (0x[0-9a-f][`Base64`](/contracts/4.x/api/utils#Base64))$/ + +
+
+ + + +
+
+

getRoleAdmin(bytes32 role) → bytes32

+
+

public

+# +
+
+
+ +Returns the admin role that controls `role`. See [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and +[`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). + +To change a role's admin, use [`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). + +
+
+ + + +
+
+

grantRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

revokeRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

renounceRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +Revokes `role` from the calling account. + +Roles are often managed via [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-): this function's +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been revoked `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) +event. + +Requirements: + +- the caller must be `account`. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

_setupRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) +event. Note that unlike [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-), this function doesn't perform any +checks on the calling account. + +May emit a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +[WARNING] +==== +This function should only be called from the constructor when setting +up the initial roles for the system. + +Using this function in any other way is effectively circumventing the admin +system imposed by [`AccessControl`](#AccessControl). +==== + +NOTE: This function is deprecated in favor of [`AccessControl._grantRole`](#AccessControl-_grantRole-bytes32-address-). + +
+
+ + + +
+
+

_setRoleAdmin(bytes32 role, bytes32 adminRole)

+
+

internal

+# +
+
+
+ +Sets `adminRole` as ``role``'s admin role. + +Emits a [`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) event. + +
+
+ + + +
+
+

_grantRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Grants `role` to `account`. + +Internal function without access restriction. + +May emit a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

_revokeRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Revokes `role` from `account`. + +Internal function without access restriction. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

DEFAULT_ADMIN_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `AccessControlCrossChain` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/AccessControlCrossChain.sol"; +``` + +An extension to [`AccessControl`](#AccessControl) with support for cross-chain access management. +For each role, is extension implements an equivalent "aliased" role that is used for +restricting calls originating from other chains. + +For example, if a function `myFunction` is protected by `onlyRole(SOME_ROLE)`, and +if an address `x` has role `SOME_ROLE`, it would be able to call `myFunction` directly. +A wallet or contract at the same address on another chain would however not be able +to call this function. In order to do so, it would require to have the role +`_crossChainRoleAlias(SOME_ROLE)`. + +This aliasing is required to protect against multiple contracts living at the same +address on different chains but controlled by conflicting entities. + +_Available since v4.6._ + +
+

Functions

+
+- [_checkRole(role)](#AccessControlCrossChain-_checkRole-bytes32-) +- [_crossChainRoleAlias(role)](#AccessControlCrossChain-_crossChainRoleAlias-bytes32-) +- [CROSSCHAIN_ALIAS()](#AccessControlCrossChain-CROSSCHAIN_ALIAS-bytes32) +#### CrossChainEnabled +- [_isCrossChain()](#CrossChainEnabled-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabled-_crossChainSender--) +#### AccessControl +- [supportsInterface(interfaceId)](#AccessControl-supportsInterface-bytes4-) +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [_grantRole(role, account)](#AccessControl-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControl-_revokeRole-bytes32-address-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControl +
+
+ +
+

Events

+
+#### CrossChainEnabled +#### AccessControl +#### ERC165 +#### IERC165 +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

_checkRole(bytes32 role)

+
+

internal

+# +
+
+
+ +See [`AccessControl._checkRole`](#AccessControl-_checkRole-bytes32-address-). + +
+
+ + + +
+
+

_crossChainRoleAlias(bytes32 role) → bytes32

+
+

internal

+# +
+
+
+ +Returns the aliased role corresponding to `role`. + +
+
+ + + +
+
+

CROSSCHAIN_ALIAS() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `AccessControlDefaultAdminRules` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/AccessControlDefaultAdminRules.sol"; +``` + +Extension of [`AccessControl`](#AccessControl) that allows specifying special rules to manage +the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions +over other roles that may potentially have privileged rights in the system. + +If a specific role doesn't have an admin role assigned, the holder of the +`DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. + +This contract implements the following risk mitigations on top of [`AccessControl`](#AccessControl): + +* Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced. +* Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. +* Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. +* The delay can be changed by scheduling, see [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). +* It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. + +Example usage: + +```solidity +contract MyToken is AccessControlDefaultAdminRules { + constructor() AccessControlDefaultAdminRules( + 3 days, + msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder + ) {} +} +``` + +_Available since v4.9._ + +
+

Functions

+
+- [constructor(initialDelay, initialDefaultAdmin)](#AccessControlDefaultAdminRules-constructor-uint48-address-) +- [supportsInterface(interfaceId)](#AccessControlDefaultAdminRules-supportsInterface-bytes4-) +- [owner()](#AccessControlDefaultAdminRules-owner--) +- [grantRole(role, account)](#AccessControlDefaultAdminRules-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControlDefaultAdminRules-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControlDefaultAdminRules-renounceRole-bytes32-address-) +- [_grantRole(role, account)](#AccessControlDefaultAdminRules-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlDefaultAdminRules-_revokeRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32-) +- [defaultAdmin()](#AccessControlDefaultAdminRules-defaultAdmin--) +- [pendingDefaultAdmin()](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) +- [defaultAdminDelay()](#AccessControlDefaultAdminRules-defaultAdminDelay--) +- [pendingDefaultAdminDelay()](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) +- [defaultAdminDelayIncreaseWait()](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--) +- [beginDefaultAdminTransfer(newAdmin)](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) +- [_beginDefaultAdminTransfer(newAdmin)](#AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address-) +- [cancelDefaultAdminTransfer()](#AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--) +- [_cancelDefaultAdminTransfer()](#AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer--) +- [acceptDefaultAdminTransfer()](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--) +- [_acceptDefaultAdminTransfer()](#AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer--) +- [changeDefaultAdminDelay(newDelay)](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-) +- [_changeDefaultAdminDelay(newDelay)](#AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48-) +- [rollbackDefaultAdminDelay()](#AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--) +- [_rollbackDefaultAdminDelay()](#AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay--) +- [_delayChangeWait(newDelay)](#AccessControlDefaultAdminRules-_delayChangeWait-uint48-) +#### AccessControl +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IERC5313 +#### IAccessControlDefaultAdminRules +#### IAccessControl +
+
+ +
+

Events

+
+#### AccessControl +#### ERC165 +#### IERC165 +#### IERC5313 +#### IAccessControlDefaultAdminRules +- [DefaultAdminTransferScheduled(newAdmin, acceptSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-) +- [DefaultAdminTransferCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--) +- [DefaultAdminDelayChangeScheduled(newDelay, effectSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-) +- [DefaultAdminDelayChangeCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--) +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

constructor(uint48 initialDelay, address initialDefaultAdmin)

+
+

internal

+# +
+
+
+ +Sets the initial values for [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) and [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) address. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

owner() → address

+
+

public

+# +
+
+
+ +See [`IERC5313.owner`](/contracts/4.x/api/interfaces#IERC5313-owner--). + +
+
+ + + +
+
+

grantRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +See [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-). Reverts for `DEFAULT_ADMIN_ROLE`. + +
+
+ + + +
+
+

revokeRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +See [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). Reverts for `DEFAULT_ADMIN_ROLE`. + +
+
+ + + +
+
+

renounceRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +See [`AccessControl.renounceRole`](#AccessControl-renounceRole-bytes32-address-). + +For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling +[`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) to the `address(0)`, so it's required that the [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) schedule +has also passed when calling this function. + +After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions. + +NOTE: Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--), +thereby disabling any functionality that is only available for it, and the possibility of reassigning a +non-administrated role. + +
+
+ + + +
+
+

_grantRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +See [`AccessControl._grantRole`](#AccessControl-_grantRole-bytes32-address-). + +For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) or if the +role has been previously renounced. + +NOTE: Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE` +assignable again. Make sure to guarantee this is the expected behavior in your implementation. + +
+
+ + + +
+
+

_revokeRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +See [`AccessControl._revokeRole`](#AccessControl-_revokeRole-bytes32-address-). + +
+
+ + + +
+
+

_setRoleAdmin(bytes32 role, bytes32 adminRole)

+
+

internal

+# +
+
+
+ +See [`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). Reverts for `DEFAULT_ADMIN_ROLE`. + +
+
+ + + +
+
+

defaultAdmin() → address

+
+

public

+# +
+
+
+ +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + +
+
+ + + +
+
+

pendingDefaultAdmin() → address newAdmin, uint48 schedule

+
+

public

+# +
+
+
+ +Returns a tuple of a `newAdmin` and an accept schedule. + +After the `schedule` passes, the `newAdmin` will be able to accept the [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) role +by calling [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--), completing the role transfer. + +A zero value only in `acceptSchedule` indicates no pending admin transfer. + +NOTE: A zero address `newAdmin` means that [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) is being renounced. + +
+
+ + + +
+
+

defaultAdminDelay() → uint48

+
+

public

+# +
+
+
+ +Returns the delay required to schedule the acceptance of a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started. + +This delay will be added to the current timestamp when calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) to set +the acceptance schedule. + +NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this +function returns the new delay. See [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). + +
+
+ + + +
+
+

pendingDefaultAdminDelay() → uint48 newDelay, uint48 schedule

+
+

public

+# +
+
+
+ +Returns a tuple of `newDelay` and an effect schedule. + +After the `schedule` passes, the `newDelay` will get into effect immediately for every +new [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A zero value only in `effectSchedule` indicates no pending delay change. + +NOTE: A zero value only for `newDelay` means that the next [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +will be zero after the effect schedule. + +
+
+ + + +
+
+

defaultAdminDelayIncreaseWait() → uint48

+
+

public

+# +
+
+
+ +Maximum time in seconds for an increase to [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) (that is scheduled using [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-)) +to take effect. Default to 5 days. + +When the [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) is scheduled to be increased, it goes into effect after the new delay has passed with +the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) +that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can +be overrode for a custom [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) increase scheduling. + + +Make sure to add a reasonable amount of time while overriding this value, otherwise, +there's a risk of setting a high new delay that goes into effect almost immediately without the +possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + + +
+
+ + + +
+
+

beginDefaultAdminTransfer(address newAdmin)

+
+

public

+# +
+
+
+ +Starts a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) scheduled for acceptance +after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminRoleChangeStarted event. + +
+
+ + + +
+
+

_beginDefaultAdminTransfer(address newAdmin)

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +Internal function without access restriction. + +
+
+ + + +
+
+

cancelDefaultAdminTransfer()

+
+

public

+# +
+
+
+ +Cancels a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminTransferCanceled event. + +
+
+ + + +
+
+

_cancelDefaultAdminTransfer()

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.cancelDefaultAdminTransfer`](#AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--). + +Internal function without access restriction. + +
+
+ + + +
+
+

acceptDefaultAdminTransfer()

+
+

public

+# +
+
+
+ +Completes a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` should be granted to the caller. +- `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. +- [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) should be reset to zero values. + +Requirements: + +- Only can be called by the [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `newAdmin`. +- The [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `acceptSchedule` should've passed. + +
+
+ + + +
+
+

_acceptDefaultAdminTransfer()

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--). + +Internal function without access restriction. + +
+
+ + + +
+
+

changeDefaultAdminDelay(uint48 newDelay)

+
+

public

+# +
+
+
+ +Initiates a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) update by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) scheduled for getting +into effect after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +This function guarantees that any call to [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) done between the timestamp this +method is called and the [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) effect schedule will use the current [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +set before calling. + +The [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--)'s effect schedule is defined in a way that waiting until the schedule and then +calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) with the new delay will take at least the same as another [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) +complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by +[`AccessControlDefaultAdminRules.defaultAdminDelayIncreaseWait`](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--). +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

_changeDefaultAdminDelay(uint48 newDelay)

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). + +Internal function without access restriction. + +
+
+ + + +
+
+

rollbackDefaultAdminDelay()

+
+

public

+# +
+
+
+ +Cancels a scheduled [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

_rollbackDefaultAdminDelay()

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.rollbackDefaultAdminDelay`](#AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--). + +Internal function without access restriction. + +
+
+ + + +
+
+

_delayChangeWait(uint48 newDelay) → uint48

+
+

internal

+# +
+
+
+ +Returns the amount of seconds to wait after the `newDelay` will +become the new [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +The value returned guarantees that if the delay is reduced, it will go into effect +after a wait that honors the previously set delay. + +See [`AccessControlDefaultAdminRules.defaultAdminDelayIncreaseWait`](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--). + +
+
+ + + +
+ +## `AccessControlEnumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +``` + +Extension of [`AccessControl`](#AccessControl) that allows enumerating the members of each role. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#AccessControlEnumerable-supportsInterface-bytes4-) +- [getRoleMember(role, index)](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#AccessControlEnumerable-getRoleMemberCount-bytes32-) +- [_grantRole(role, account)](#AccessControlEnumerable-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlEnumerable-_revokeRole-bytes32-address-) +#### AccessControl +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +
+
+ +
+

Events

+
+#### AccessControl +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

getRoleMember(bytes32 role, uint256 index) → address

+
+

public

+# +
+
+
+ +Returns one of the accounts that have `role`. `index` must be a +value between 0 and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + + +When using [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), make sure +you perform all queries on the same block. See the following +[forum post](https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296) +for more information. + + +
+
+ + + +
+
+

getRoleMemberCount(bytes32 role) → uint256

+
+

public

+# +
+
+
+ +Returns the number of accounts that have `role`. Can be used +together with [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) to enumerate all bearers of a role. + +
+
+ + + +
+
+

_grantRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Overload [`AccessControl._grantRole`](#AccessControl-_grantRole-bytes32-address-) to track enumerable memberships + +
+
+ + + +
+
+

_revokeRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Overload [`AccessControl._revokeRole`](#AccessControl-_revokeRole-bytes32-address-) to track enumerable memberships + +
+
+ + + +
+ +## `IAccessControl` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/IAccessControl.sol"; +``` + +External interface of AccessControl declared to support ERC165 detection. + +
+

Functions

+
+- [hasRole(role, account)](#IAccessControl-hasRole-bytes32-address-) +- [getRoleAdmin(role)](#IAccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#IAccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#IAccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#IAccessControl-renounceRole-bytes32-address-) +
+
+ +
+

Events

+
+- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

hasRole(bytes32 role, address account) → bool

+
+

external

+# +
+
+
+ +Returns `true` if `account` has been granted `role`. + +
+
+ + + +
+
+

getRoleAdmin(bytes32 role) → bytes32

+
+

external

+# +
+
+
+ +Returns the admin role that controls `role`. See [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and +[`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). + +To change a role's admin, use [`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). + +
+
+ + + +
+
+

grantRole(bytes32 role, address account)

+
+

external

+# +
+
+
+ +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +
+
+ + + +
+
+

revokeRole(bytes32 role, address account)

+
+

external

+# +
+
+
+ +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +Requirements: + +- the caller must have ``role``'s admin role. + +
+
+ + + +
+
+

renounceRole(bytes32 role, address account)

+
+

external

+# +
+
+
+ +Revokes `role` from the calling account. + +Roles are often managed via [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-): this function's +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been granted `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) +event. + +Requirements: + +- the caller must be `account`. + +
+
+ + + +
+
+

RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)

+
+

event

+# +
+
+ +
+ +Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite +[`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) not being emitted signaling this. + +_Available since v3.1._ + +
+
+ + +
+
+

RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)

+
+

event

+# +
+
+ +
+ +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call, an admin role +bearer except when using [`AccessControl._setupRole`](#AccessControl-_setupRole-bytes32-address-). + +
+
+ + +
+
+

RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)

+
+

event

+# +
+
+ +
+ +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + - if using `revokeRole`, it is the admin role bearer + - if using `renounceRole`, it is the role bearer (i.e. `account`) + +
+
+ + + +
+ +## `IAccessControlDefaultAdminRules` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/IAccessControlDefaultAdminRules.sol"; +``` + +External interface of AccessControlDefaultAdminRules declared to support ERC165 detection. + +_Available since v4.9._ + +
+

Functions

+
+- [defaultAdmin()](#IAccessControlDefaultAdminRules-defaultAdmin--) +- [pendingDefaultAdmin()](#IAccessControlDefaultAdminRules-pendingDefaultAdmin--) +- [defaultAdminDelay()](#IAccessControlDefaultAdminRules-defaultAdminDelay--) +- [pendingDefaultAdminDelay()](#IAccessControlDefaultAdminRules-pendingDefaultAdminDelay--) +- [beginDefaultAdminTransfer(newAdmin)](#IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) +- [cancelDefaultAdminTransfer()](#IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer--) +- [acceptDefaultAdminTransfer()](#IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer--) +- [changeDefaultAdminDelay(newDelay)](#IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-) +- [rollbackDefaultAdminDelay()](#IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay--) +- [defaultAdminDelayIncreaseWait()](#IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--) +#### IAccessControl +- [hasRole(role, account)](#IAccessControl-hasRole-bytes32-address-) +- [getRoleAdmin(role)](#IAccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#IAccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#IAccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#IAccessControl-renounceRole-bytes32-address-) +
+
+ +
+

Events

+
+- [DefaultAdminTransferScheduled(newAdmin, acceptSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-) +- [DefaultAdminTransferCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--) +- [DefaultAdminDelayChangeScheduled(newDelay, effectSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-) +- [DefaultAdminDelayChangeCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--) +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

defaultAdmin() → address

+
+

external

+# +
+
+
+ +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + +
+
+ + + +
+
+

pendingDefaultAdmin() → address newAdmin, uint48 acceptSchedule

+
+

external

+# +
+
+
+ +Returns a tuple of a `newAdmin` and an accept schedule. + +After the `schedule` passes, the `newAdmin` will be able to accept the [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) role +by calling [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--), completing the role transfer. + +A zero value only in `acceptSchedule` indicates no pending admin transfer. + +NOTE: A zero address `newAdmin` means that [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) is being renounced. + +
+
+ + + +
+
+

defaultAdminDelay() → uint48

+
+

external

+# +
+
+
+ +Returns the delay required to schedule the acceptance of a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started. + +This delay will be added to the current timestamp when calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) to set +the acceptance schedule. + +NOTE: If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this +function returns the new delay. See [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). + +
+
+ + + +
+
+

pendingDefaultAdminDelay() → uint48 newDelay, uint48 effectSchedule

+
+

external

+# +
+
+
+ +Returns a tuple of `newDelay` and an effect schedule. + +After the `schedule` passes, the `newDelay` will get into effect immediately for every +new [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A zero value only in `effectSchedule` indicates no pending delay change. + +NOTE: A zero value only for `newDelay` means that the next [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +will be zero after the effect schedule. + +
+
+ + + +
+
+

beginDefaultAdminTransfer(address newAdmin)

+
+

external

+# +
+
+
+ +Starts a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) scheduled for acceptance +after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminRoleChangeStarted event. + +
+
+ + + +
+
+

cancelDefaultAdminTransfer()

+
+

external

+# +
+
+
+ +Cancels a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminTransferCanceled event. + +
+
+ + + +
+
+

acceptDefaultAdminTransfer()

+
+

external

+# +
+
+
+ +Completes a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` should be granted to the caller. +- `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. +- [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) should be reset to zero values. + +Requirements: + +- Only can be called by the [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `newAdmin`. +- The [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `acceptSchedule` should've passed. + +
+
+ + + +
+
+

changeDefaultAdminDelay(uint48 newDelay)

+
+

external

+# +
+
+
+ +Initiates a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) update by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) scheduled for getting +into effect after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +This function guarantees that any call to [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) done between the timestamp this +method is called and the [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) effect schedule will use the current [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +set before calling. + +The [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--)'s effect schedule is defined in a way that waiting until the schedule and then +calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) with the new delay will take at least the same as another [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) +complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by +[`AccessControlDefaultAdminRules.defaultAdminDelayIncreaseWait`](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--). +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

rollbackDefaultAdminDelay()

+
+

external

+# +
+
+
+ +Cancels a scheduled [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

defaultAdminDelayIncreaseWait() → uint48

+
+

external

+# +
+
+
+ +Maximum time in seconds for an increase to [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) (that is scheduled using [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-)) +to take effect. Default to 5 days. + +When the [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) is scheduled to be increased, it goes into effect after the new delay has passed with +the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) +that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can +be overrode for a custom [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) increase scheduling. + + +Make sure to add a reasonable amount of time while overriding this value, otherwise, +there's a risk of setting a high new delay that goes into effect almost immediately without the +possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + + +
+
+ + + +
+
+

DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule)

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer is started, setting `newAdmin` as the next +address to become the [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) by calling [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--) only after `acceptSchedule` +passes. + +
+
+ + +
+
+

DefaultAdminTransferCanceled()

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) is reset if it was never accepted, regardless of its schedule. + +
+
+ + +
+
+

DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule)

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) change is started, setting `newDelay` as the next +delay to be applied between default admin transfer after `effectSchedule` has passed. + +
+
+ + +
+
+

DefaultAdminDelayChangeCanceled()

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) is reset if its schedule didn't pass. + +
+
+ + + +
+ +## `IAccessControlEnumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/IAccessControlEnumerable.sol"; +``` + +External interface of AccessControlEnumerable declared to support ERC165 detection. + +
+

Functions

+
+- [getRoleMember(role, index)](#IAccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#IAccessControlEnumerable-getRoleMemberCount-bytes32-) +#### IAccessControl +- [hasRole(role, account)](#IAccessControl-hasRole-bytes32-address-) +- [getRoleAdmin(role)](#IAccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#IAccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#IAccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#IAccessControl-renounceRole-bytes32-address-) +
+
+ +
+

Events

+
+#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

getRoleMember(bytes32 role, uint256 index) → address

+
+

external

+# +
+
+
+ +Returns one of the accounts that have `role`. `index` must be a +value between 0 and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + + +When using [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), make sure +you perform all queries on the same block. See the following +[forum post](https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296) +for more information. + + +
+
+ + + +
+
+

getRoleMemberCount(bytes32 role) → uint256

+
+

external

+# +
+
+
+ +Returns the number of accounts that have `role`. Can be used +together with [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) to enumerate all bearers of a role. + +
+
+ + + +
+ +## `Ownable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/Ownable.sol"; +``` + +Contract module which provides a basic access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +By default, the owner account will be the one that deploys the contract. This +can later be changed with [`Ownable.transferOwnership`](#Ownable-transferOwnership-address-). + +This module is used through inheritance. It will make available the modifier +`onlyOwner`, which can be applied to your functions to restrict their use to +the owner. + +
+

Modifiers

+
+- [onlyOwner()](#Ownable-onlyOwner--) +
+
+ +
+

Functions

+
+- [constructor()](#Ownable-constructor--) +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ + + +
+
+

onlyOwner()

+
+

internal

+# +
+
+ +
+ +Throws if called by any account other than the owner. + +
+
+ + + +
+
+

constructor()

+
+

internal

+# +
+
+
+ +Initializes the contract setting the deployer as the initial owner. + +
+
+ + + +
+
+

owner() → address

+
+

public

+# +
+
+
+ +Returns the address of the current owner. + +
+
+ + + +
+
+

_checkOwner()

+
+

internal

+# +
+
+
+ +Throws if the sender is not the owner. + +
+
+ + + +
+
+

renounceOwnership()

+
+

public

+# +
+
+
+ +Leaves the contract without owner. It will not be possible to call +`onlyOwner` functions. Can only be called by the current owner. + +NOTE: Renouncing ownership will leave the contract without an owner, +thereby disabling any functionality that is only available to the owner. + +
+
+ + + +
+
+

transferOwnership(address newOwner)

+
+

public

+# +
+
+
+ +Transfers ownership of the contract to a new account (`newOwner`). +Can only be called by the current owner. + +
+
+ + + +
+
+

_transferOwnership(address newOwner)

+
+

internal

+# +
+
+
+ +Transfers ownership of the contract to a new account (`newOwner`). +Internal function without access restriction. + +
+
+ + + +
+
+

OwnershipTransferred(address indexed previousOwner, address indexed newOwner)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `Ownable2Step` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +``` + +Contract module which provides access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +By default, the owner account will be the one that deploys the contract. This +can later be changed with [`Ownable.transferOwnership`](#Ownable-transferOwnership-address-) and [`Ownable2Step.acceptOwnership`](#Ownable2Step-acceptOwnership--). + +This module is used through inheritance. It will make available all functions +from parent (Ownable). + +
+

Functions

+
+- [pendingOwner()](#Ownable2Step-pendingOwner--) +- [transferOwnership(newOwner)](#Ownable2Step-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable2Step-_transferOwnership-address-) +- [acceptOwnership()](#Ownable2Step-acceptOwnership--) +#### Ownable +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +
+
+ +
+

Events

+
+- [OwnershipTransferStarted(previousOwner, newOwner)](#Ownable2Step-OwnershipTransferStarted-address-address-) +#### Ownable +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ + + +
+
+

pendingOwner() → address

+
+

public

+# +
+
+
+ +Returns the address of the pending owner. + +
+
+ + + +
+
+

transferOwnership(address newOwner)

+
+

public

+# +
+
+
+ +Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. +Can only be called by the current owner. + +
+
+ + + +
+
+

_transferOwnership(address newOwner)

+
+

internal

+# +
+
+
+ +Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. +Internal function without access restriction. + +
+
+ + + +
+
+

acceptOwnership()

+
+

public

+# +
+
+
+ +The new owner accepts the ownership transfer. + +
+
+ + + +
+
+

OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)

+
+

event

+# +
+
+ +
+ +
+
diff --git a/docs/content/contracts/4.x/api/crosschain.mdx b/docs/content/contracts/4.x/api/crosschain.mdx new file mode 100644 index 00000000..a21c65c0 --- /dev/null +++ b/docs/content/contracts/4.x/api/crosschain.mdx @@ -0,0 +1,989 @@ +--- +title: "Crosschain" +description: "Smart contract crosschain utilities and implementations" +--- + +This directory provides building blocks to improve cross-chain awareness of smart contracts. + +* [`CrossChainEnabled`](#CrossChainEnabled) is an abstraction that contains accessors and modifiers to control the execution flow when receiving cross-chain messages. + +## CrossChainEnabled specializations + +The following specializations of [`CrossChainEnabled`](#CrossChainEnabled) provide implementations of the [`CrossChainEnabled`](#CrossChainEnabled) abstraction for specific bridges. This can be used to complex cross-chain aware components such as [`AccessControlCrossChain`](/contracts/4.x/api/access#AccessControlCrossChain). + +[`CrossChainEnabledAMB`](#CrossChainEnabledAMB) + +[`CrossChainEnabledArbitrumL1`](#CrossChainEnabledArbitrumL1) + +[`CrossChainEnabledArbitrumL2`](#CrossChainEnabledArbitrumL2) + +[`CrossChainEnabledOptimism`](#CrossChainEnabledOptimism) + +[`CrossChainEnabledPolygonChild`](#CrossChainEnabledPolygonChild) + +## Libraries for cross-chain + +In addition to the [`CrossChainEnabled`](#CrossChainEnabled) abstraction, cross-chain awareness is also available through libraries. These libraries can be used to build complex designs such as contracts with the ability to interact with multiple bridges. + +[`LibAMB`](#LibAMB) + +[`LibArbitrumL1`](#LibArbitrumL1) + +[`LibArbitrumL2`](#LibArbitrumL2) + +[`LibOptimism`](#LibOptimism) + + + +
+ +## `CrossChainEnabled` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/CrossChainEnabled.sol"; +``` + +Provides information for building cross-chain aware contracts. This +abstract contract provides accessors and modifiers to control the execution +flow when receiving cross-chain messages. + +Actual implementations of cross-chain aware contracts, which are based on +this abstraction, will have to inherit from a bridge-specific +specialization. Such specializations are provided under +`crosschain//CrossChainEnabled.sol`. + +_Available since v4.6._ + +
+

Modifiers

+
+- [onlyCrossChain()](#CrossChainEnabled-onlyCrossChain--) +- [onlyCrossChainSender(expected)](#CrossChainEnabled-onlyCrossChainSender-address-) +
+
+ +
+

Functions

+
+- [_isCrossChain()](#CrossChainEnabled-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabled-_crossChainSender--) +
+
+ + + +
+
+

onlyCrossChain()

+
+

internal

+# +
+
+ +
+ +Throws if the current function call is not the result of a +cross-chain execution. + +
+
+ + + +
+
+

onlyCrossChainSender(address expected)

+
+

internal

+# +
+
+ +
+ +Throws if the current function call is not the result of a +cross-chain execution initiated by `account`. + +
+
+ + + +
+
+

_isCrossChain() → bool

+
+

internal

+# +
+
+
+ +Returns whether the current function call is the result of a +cross-chain message. + +
+
+ + + +
+
+

_crossChainSender() → address

+
+

internal

+# +
+
+
+ +Returns the address of the sender of the cross-chain message that +triggered the current function call. + + +Should revert with `NotCrossChainCall` if the current function +call is not the result of a cross-chain message. + + +
+
+ + + +
+ +## `CrossChainEnabledAMB` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/amb/CrossChainEnabledAMB.sol"; +``` + +[AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge) +specialization or the [`CrossChainEnabled`](#CrossChainEnabled) abstraction. + +As of february 2020, AMB bridges are available between the following chains: + +- [ETH ⇌ xDai](https://docs.tokenbridge.net/eth-xdai-amb-bridge/about-the-eth-xdai-amb) +- [ETH ⇌ qDai](https://docs.tokenbridge.net/eth-qdai-bridge/about-the-eth-qdai-amb) +- [ETH ⇌ ETC](https://docs.tokenbridge.net/eth-etc-amb-bridge/about-the-eth-etc-amb) +- [ETH ⇌ BSC](https://docs.tokenbridge.net/eth-bsc-amb/about-the-eth-bsc-amb) +- [ETH ⇌ POA](https://docs.tokenbridge.net/eth-poa-amb-bridge/about-the-eth-poa-amb) +- [BSC ⇌ xDai](https://docs.tokenbridge.net/bsc-xdai-amb/about-the-bsc-xdai-amb) +- [POA ⇌ xDai](https://docs.tokenbridge.net/poa-xdai-amb/about-the-poa-xdai-amb) +- [Rinkeby ⇌ xDai](https://docs.tokenbridge.net/rinkeby-xdai-amb-bridge/about-the-rinkeby-xdai-amb) +- [Kovan ⇌ Sokol](https://docs.tokenbridge.net/kovan-sokol-amb-bridge/about-the-kovan-sokol-amb) + +_Available since v4.6._ + +
+

Functions

+
+- [constructor(bridge)](#CrossChainEnabledAMB-constructor-address-) +- [_isCrossChain()](#CrossChainEnabledAMB-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabledAMB-_crossChainSender--) +#### CrossChainEnabled +
+
+ + + +
+
+

constructor(address bridge)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_isCrossChain() → bool

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._isCrossChain`](#CrossChainEnabled-_isCrossChain--) + +
+
+ + + +
+
+

_crossChainSender() → address

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._crossChainSender`](#CrossChainEnabled-_crossChainSender--) + +
+
+ + + +
+ +## `LibAMB` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/amb/LibAMB.sol"; +``` + +Primitives for cross-chain aware contracts using the +[AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge) +family of bridges. + +
+

Functions

+
+- [isCrossChain(bridge)](#LibAMB-isCrossChain-address-) +- [crossChainSender(bridge)](#LibAMB-crossChainSender-address-) +
+
+ + + +
+
+

isCrossChain(address bridge) → bool

+
+

internal

+# +
+
+
+ +Returns whether the current function call is the result of a +cross-chain message relayed by `bridge`. + +
+
+ + + +
+
+

crossChainSender(address bridge) → address

+
+

internal

+# +
+
+
+ +Returns the address of the sender that triggered the current +cross-chain message through `bridge`. + +NOTE: [`LibAMB.isCrossChain`](#LibAMB-isCrossChain-address-) should be checked before trying to recover the +sender, as it will revert with `NotCrossChainCall` if the current +function call is not the result of a cross-chain message. + +
+
+ + + +
+ +## `CrossChainEnabledArbitrumL1` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL1.sol"; +``` + +[Arbitrum](https://arbitrum.io/) specialization or the +[`CrossChainEnabled`](#CrossChainEnabled) abstraction the L1 side (mainnet). + +This version should only be deployed on L1 to process cross-chain messages +originating from L2. For the other side, use [`CrossChainEnabledArbitrumL2`](#CrossChainEnabledArbitrumL2). + +The bridge contract is provided and maintained by the arbitrum team. You can +find the address of this contract on the rinkeby testnet in +[Arbitrum's developer documentation](https://developer.offchainlabs.com/docs/useful_addresses). + +_Available since v4.6._ + +
+

Functions

+
+- [constructor(bridge)](#CrossChainEnabledArbitrumL1-constructor-address-) +- [_isCrossChain()](#CrossChainEnabledArbitrumL1-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabledArbitrumL1-_crossChainSender--) +#### CrossChainEnabled +
+
+ + + +
+
+

constructor(address bridge)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_isCrossChain() → bool

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._isCrossChain`](#CrossChainEnabled-_isCrossChain--) + +
+
+ + + +
+
+

_crossChainSender() → address

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._crossChainSender`](#CrossChainEnabled-_crossChainSender--) + +
+
+ + + +
+ +## `CrossChainEnabledArbitrumL2` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/arbitrum/CrossChainEnabledArbitrumL2.sol"; +``` + +[Arbitrum](https://arbitrum.io/) specialization or the +[`CrossChainEnabled`](#CrossChainEnabled) abstraction the L2 side (arbitrum). + +This version should only be deployed on L2 to process cross-chain messages +originating from L1. For the other side, use [`CrossChainEnabledArbitrumL1`](#CrossChainEnabledArbitrumL1). + +Arbitrum L2 includes the `ArbSys` contract at a fixed address. Therefore, +this specialization of [`CrossChainEnabled`](#CrossChainEnabled) does not include a constructor. + +_Available since v4.6._ + + +There is currently a bug in Arbitrum that causes this contract to +fail to detect cross-chain calls when deployed behind a proxy. This will be +fixed when the network is upgraded to Arbitrum Nitro, currently scheduled for +August 31st 2022. + + +
+

Functions

+
+- [_isCrossChain()](#CrossChainEnabledArbitrumL2-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabledArbitrumL2-_crossChainSender--) +#### CrossChainEnabled +
+
+ + + +
+
+

_isCrossChain() → bool

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._isCrossChain`](#CrossChainEnabled-_isCrossChain--) + +
+
+ + + +
+
+

_crossChainSender() → address

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._crossChainSender`](#CrossChainEnabled-_crossChainSender--) + +
+
+ + + +
+ +## `LibArbitrumL1` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/arbitrum/LibArbitrumL1.sol"; +``` + +Primitives for cross-chain aware contracts for +[Arbitrum](https://arbitrum.io/). + +This version should only be used on L1 to process cross-chain messages +originating from L2. For the other side, use [`LibArbitrumL2`](#LibArbitrumL2). + +
+

Functions

+
+- [isCrossChain(bridge)](#LibArbitrumL1-isCrossChain-address-) +- [crossChainSender(bridge)](#LibArbitrumL1-crossChainSender-address-) +
+
+ + + +
+
+

isCrossChain(address bridge) → bool

+
+

internal

+# +
+
+
+ +Returns whether the current function call is the result of a +cross-chain message relayed by the `bridge`. + +
+
+ + + +
+
+

crossChainSender(address bridge) → address

+
+

internal

+# +
+
+
+ +Returns the address of the sender that triggered the current +cross-chain message through the `bridge`. + +NOTE: [`LibAMB.isCrossChain`](#LibAMB-isCrossChain-address-) should be checked before trying to recover the +sender, as it will revert with `NotCrossChainCall` if the current +function call is not the result of a cross-chain message. + +
+
+ + + +
+ +## `LibArbitrumL2` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/arbitrum/LibArbitrumL2.sol"; +``` + +Primitives for cross-chain aware contracts for +[Arbitrum](https://arbitrum.io/). + +This version should only be used on L2 to process cross-chain messages +originating from L1. For the other side, use [`LibArbitrumL1`](#LibArbitrumL1). + + +There is currently a bug in Arbitrum that causes this contract to +fail to detect cross-chain calls when deployed behind a proxy. This will be +fixed when the network is upgraded to Arbitrum Nitro, currently scheduled for +August 31st 2022. + + +
+

Functions

+
+- [isCrossChain(arbsys)](#LibArbitrumL2-isCrossChain-address-) +- [crossChainSender(arbsys)](#LibArbitrumL2-crossChainSender-address-) +- [ARBSYS()](#LibArbitrumL2-ARBSYS-address) +
+
+ + + +
+
+

isCrossChain(address arbsys) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

crossChainSender(address arbsys) → address

+
+

internal

+# +
+
+
+ +Returns the address of the sender that triggered the current +cross-chain message through `arbsys`. + +NOTE: [`LibAMB.isCrossChain`](#LibAMB-isCrossChain-address-) should be checked before trying to recover the +sender, as it will revert with `NotCrossChainCall` if the current +function call is not the result of a cross-chain message. + +
+
+ + + +
+
+

ARBSYS() → address

+
+

public

+# +
+
+
+ +Returns whether the current function call is the result of a +cross-chain message relayed by `arbsys`. + +
+
+ + + +
+ +## `NotCrossChainCall` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/errors.sol"; +``` + + + +
+
+

NotCrossChainCall()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `InvalidCrossChainSender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/errors.sol"; +``` + + + +
+
+

InvalidCrossChainSender(address actual, address expected)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `CrossChainEnabledOptimism` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/optimism/CrossChainEnabledOptimism.sol"; +``` + +[Optimism](https://www.optimism.io/) specialization or the +[`CrossChainEnabled`](#CrossChainEnabled) abstraction. + +The messenger (`CrossDomainMessenger`) contract is provided and maintained by +the optimism team. You can find the address of this contract on mainnet and +kovan in the [deployments section of Optimism monorepo](https://github.com/ethereum-optimism/optimism/tree/develop/packages/contracts/deployments). + +_Available since v4.6._ + +
+

Functions

+
+- [constructor(messenger)](#CrossChainEnabledOptimism-constructor-address-) +- [_isCrossChain()](#CrossChainEnabledOptimism-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabledOptimism-_crossChainSender--) +#### CrossChainEnabled +
+
+ + + +
+
+

constructor(address messenger)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_isCrossChain() → bool

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._isCrossChain`](#CrossChainEnabled-_isCrossChain--) + +
+
+ + + +
+
+

_crossChainSender() → address

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._crossChainSender`](#CrossChainEnabled-_crossChainSender--) + +
+
+ + + +
+ +## `LibOptimism` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/optimism/LibOptimism.sol"; +``` + +Primitives for cross-chain aware contracts for [Optimism](https://www.optimism.io/). +See the [documentation](https://community.optimism.io/docs/developers/bridge/messaging/#accessing-msg-sender) +for the functionality used here. + +
+

Functions

+
+- [isCrossChain(messenger)](#LibOptimism-isCrossChain-address-) +- [crossChainSender(messenger)](#LibOptimism-crossChainSender-address-) +
+
+ + + +
+
+

isCrossChain(address messenger) → bool

+
+

internal

+# +
+
+
+ +Returns whether the current function call is the result of a +cross-chain message relayed by `messenger`. + +
+
+ + + +
+
+

crossChainSender(address messenger) → address

+
+

internal

+# +
+
+
+ +Returns the address of the sender that triggered the current +cross-chain message through `messenger`. + +NOTE: [`LibAMB.isCrossChain`](#LibAMB-isCrossChain-address-) should be checked before trying to recover the +sender, as it will revert with `NotCrossChainCall` if the current +function call is not the result of a cross-chain message. + +
+
+ + + +
+ +## `DEFAULT_SENDER` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol"; +``` + + + +
+ +## `CrossChainEnabledPolygonChild` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/polygon/CrossChainEnabledPolygonChild.sol"; +``` + +[Polygon](https://polygon.technology/) specialization or the +[`CrossChainEnabled`](#CrossChainEnabled) abstraction the child side (polygon/mumbai). + +This version should only be deployed on child chain to process cross-chain +messages originating from the parent chain. + +The fxChild contract is provided and maintained by the polygon team. You can +find the address of this contract polygon and mumbai in +[Polygon's Fx-Portal documentation](https://docs.polygon.technology/docs/develop/l1-l2-communication/fx-portal/#contract-addresses). + +_Available since v4.6._ + +
+

Functions

+
+- [constructor(fxChild)](#CrossChainEnabledPolygonChild-constructor-address-) +- [_isCrossChain()](#CrossChainEnabledPolygonChild-_isCrossChain--) +- [_crossChainSender()](#CrossChainEnabledPolygonChild-_crossChainSender--) +- [processMessageFromRoot(, rootMessageSender, data)](#CrossChainEnabledPolygonChild-processMessageFromRoot-uint256-address-bytes-) +#### ReentrancyGuard +- [_reentrancyGuardEntered()](#ReentrancyGuard-_reentrancyGuardEntered--) +#### CrossChainEnabled +#### IFxMessageProcessor +
+
+ + + +
+
+

constructor(address fxChild)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_isCrossChain() → bool

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._isCrossChain`](#CrossChainEnabled-_isCrossChain--) + +
+
+ + + +
+
+

_crossChainSender() → address

+
+

internal

+# +
+
+
+ +see [`CrossChainEnabled._crossChainSender`](#CrossChainEnabled-_crossChainSender--) + +
+
+ + + +
+
+

processMessageFromRoot(uint256, address rootMessageSender, bytes data)

+
+

external

+# +
+
+
+ +External entry point to receive and relay messages originating +from the fxChild. + +Non-reentrancy is crucial to avoid a cross-chain call being able +to impersonate anyone by just looping through this with user-defined +arguments. + +Note: if _fxChild calls any other function that does a delegate-call, +then security could be compromised. + +
+
diff --git a/docs/content/contracts/4.x/api/finance.mdx b/docs/content/contracts/4.x/api/finance.mdx new file mode 100644 index 00000000..ea030371 --- /dev/null +++ b/docs/content/contracts/4.x/api/finance.mdx @@ -0,0 +1,706 @@ +--- +title: "Finance" +description: "Smart contract finance utilities and implementations" +--- + +This directory includes primitives for financial systems: + +* [`PaymentSplitter`](#PaymentSplitter) allows to split Ether and ERC20 payments among a group of accounts. The sender does not need to be + aware that the assets will be split in this way, since it is handled transparently by the contract. The split can be + in equal parts or in any other arbitrary proportion. +* [`VestingWallet`](#VestingWallet) handles the vesting of Ether and ERC20 tokens for a given beneficiary. Custody of multiple tokens can + be given to this contract, which will release the token to the beneficiary following a given, customizable, vesting + schedule. + +## Contracts + +[`PaymentSplitter`](#PaymentSplitter) + +[`VestingWallet`](#VestingWallet) + + + +
+ +## `PaymentSplitter` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/finance/PaymentSplitter.sol"; +``` + +This contract allows to split Ether payments among a group of accounts. The sender does not need to be aware +that the Ether will be split in this way, since it is handled transparently by the contract. + +The split can be in equal parts or in any other arbitrary proportion. The way this is specified is by assigning each +account to a number of shares. Of all the Ether that this contract receives, each account will then be able to claim +an amount proportional to the percentage of total shares they were assigned. The distribution of shares is set at the +time of contract deployment and can't be updated thereafter. + +`PaymentSplitter` follows a _pull payment_ model. This means that payments are not automatically forwarded to the +accounts but kept in this contract, and the actual transfer is triggered as a separate step by calling the [`PaymentSplitter.release`](#PaymentSplitter-release-contract-IERC20-address-) +function. + +NOTE: This contract assumes that ERC20 tokens will behave similarly to native tokens (Ether). Rebasing tokens, and +tokens that apply fees during transfers, are likely to not be supported as expected. If in doubt, we encourage you +to run tests before sending real value to this contract. + +
+

Functions

+
+- [constructor(payees, shares_)](#PaymentSplitter-constructor-address---uint256---) +- [receive()](#PaymentSplitter-receive--) +- [totalShares()](#PaymentSplitter-totalShares--) +- [totalReleased()](#PaymentSplitter-totalReleased--) +- [totalReleased(token)](#PaymentSplitter-totalReleased-contract-IERC20-) +- [shares(account)](#PaymentSplitter-shares-address-) +- [released(account)](#PaymentSplitter-released-address-) +- [released(token, account)](#PaymentSplitter-released-contract-IERC20-address-) +- [payee(index)](#PaymentSplitter-payee-uint256-) +- [releasable(account)](#PaymentSplitter-releasable-address-) +- [releasable(token, account)](#PaymentSplitter-releasable-contract-IERC20-address-) +- [release(account)](#PaymentSplitter-release-address-payable-) +- [release(token, account)](#PaymentSplitter-release-contract-IERC20-address-) +
+
+ +
+

Events

+
+- [PayeeAdded(account, shares)](#PaymentSplitter-PayeeAdded-address-uint256-) +- [PaymentReleased(to, amount)](#PaymentSplitter-PaymentReleased-address-uint256-) +- [ERC20PaymentReleased(token, to, amount)](#PaymentSplitter-ERC20PaymentReleased-contract-IERC20-address-uint256-) +- [PaymentReceived(from, amount)](#PaymentSplitter-PaymentReceived-address-uint256-) +
+
+ + + +
+
+

constructor(address[] payees, uint256[] shares_)

+
+

public

+# +
+
+
+ +Creates an instance of `PaymentSplitter` where each account in `payees` is assigned the number of shares at +the matching position in the `shares` array. + +All addresses in `payees` must be non-zero. Both arrays must have the same non-zero length, and there must be no +duplicates in `payees`. + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +The Ether received will be logged with [`PaymentSplitter.PaymentReceived`](#PaymentSplitter-PaymentReceived-address-uint256-) events. Note that these events are not fully +reliable: it's possible for a contract to receive Ether without triggering this function. This only affects the +reliability of the events, and not the actual splitting of Ether. + +To learn more about this see the Solidity documentation for +[fallback +functions](https://solidity.readthedocs.io/en/latest/contracts.html#fallback-function). + +
+
+ + + +
+
+

totalShares() → uint256

+
+

public

+# +
+
+
+ +Getter for the total shares held by payees. + +
+
+ + + +
+
+

totalReleased() → uint256

+
+

public

+# +
+
+
+ +Getter for the total amount of Ether already released. + +
+
+ + + +
+
+

totalReleased(contract IERC20 token) → uint256

+
+

public

+# +
+
+
+ +Getter for the total amount of `token` already released. `token` should be the address of an IERC20 +contract. + +
+
+ + + +
+
+

shares(address account) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of shares held by an account. + +
+
+ + + +
+
+

released(address account) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of Ether already released to a payee. + +
+
+ + + +
+
+

released(contract IERC20 token, address account) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of `token` tokens already released to a payee. `token` should be the address of an +IERC20 contract. + +
+
+ + + +
+
+

payee(uint256 index) → address

+
+

public

+# +
+
+
+ +Getter for the address of the payee number `index`. + +
+
+ + + +
+
+

releasable(address account) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of payee's releasable Ether. + +
+
+ + + +
+
+

releasable(contract IERC20 token, address account) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of payee's releasable `token` tokens. `token` should be the address of an +IERC20 contract. + +
+
+ + + +
+
+

release(address payable account)

+
+

public

+# +
+
+
+ +Triggers a transfer to `account` of the amount of Ether they are owed, according to their percentage of the +total shares and their previous withdrawals. + +
+
+ + + +
+
+

release(contract IERC20 token, address account)

+
+

public

+# +
+
+
+ +Triggers a transfer to `account` of the amount of `token` tokens they are owed, according to their +percentage of the total shares and their previous withdrawals. `token` must be the address of an IERC20 +contract. + +
+
+ + + +
+
+

PayeeAdded(address account, uint256 shares)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

PaymentReleased(address to, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ERC20PaymentReleased(contract IERC20 indexed token, address to, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

PaymentReceived(address from, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `VestingWallet` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/finance/VestingWallet.sol"; +``` + +This contract handles the vesting of Eth and ERC20 tokens for a given beneficiary. Custody of multiple tokens +can be given to this contract, which will release the token to the beneficiary following a given vesting schedule. +The vesting schedule is customizable through the [`VestingWallet.vestedAmount`](#VestingWallet-vestedAmount-address-uint64-) function. + +Any token transferred to this contract will follow the vesting schedule as if they were locked from the beginning. +Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) +be immediately releasable. + +
+

Functions

+
+- [constructor(beneficiaryAddress, startTimestamp, durationSeconds)](#VestingWallet-constructor-address-uint64-uint64-) +- [receive()](#VestingWallet-receive--) +- [beneficiary()](#VestingWallet-beneficiary--) +- [start()](#VestingWallet-start--) +- [duration()](#VestingWallet-duration--) +- [released()](#VestingWallet-released--) +- [released(token)](#VestingWallet-released-address-) +- [releasable()](#VestingWallet-releasable--) +- [releasable(token)](#VestingWallet-releasable-address-) +- [release()](#VestingWallet-release--) +- [release(token)](#VestingWallet-release-address-) +- [vestedAmount(timestamp)](#VestingWallet-vestedAmount-uint64-) +- [vestedAmount(token, timestamp)](#VestingWallet-vestedAmount-address-uint64-) +- [_vestingSchedule(totalAllocation, timestamp)](#VestingWallet-_vestingSchedule-uint256-uint64-) +
+
+ +
+

Events

+
+- [EtherReleased(amount)](#VestingWallet-EtherReleased-uint256-) +- [ERC20Released(token, amount)](#VestingWallet-ERC20Released-address-uint256-) +
+
+ + + +
+
+

constructor(address beneficiaryAddress, uint64 startTimestamp, uint64 durationSeconds)

+
+

public

+# +
+
+
+ +Set the beneficiary, start timestamp and vesting duration of the vesting wallet. + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +The contract should be able to receive Eth. + +
+
+ + + +
+
+

beneficiary() → address

+
+

public

+# +
+
+
+ +Getter for the beneficiary address. + +
+
+ + + +
+
+

start() → uint256

+
+

public

+# +
+
+
+ +Getter for the start timestamp. + +
+
+ + + +
+
+

duration() → uint256

+
+

public

+# +
+
+
+ +Getter for the vesting duration. + +
+
+ + + +
+
+

released() → uint256

+
+

public

+# +
+
+
+ +Amount of eth already released + +
+
+ + + +
+
+

released(address token) → uint256

+
+

public

+# +
+
+
+ +Amount of token already released + +
+
+ + + +
+
+

releasable() → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of releasable eth. + +
+
+ + + +
+
+

releasable(address token) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of releasable `token` tokens. `token` should be the address of an +IERC20 contract. + +
+
+ + + +
+
+

release()

+
+

public

+# +
+
+
+ +Release the native token (ether) that have already vested. + +Emits a [`VestingWallet.EtherReleased`](#VestingWallet-EtherReleased-uint256-) event. + +
+
+ + + +
+
+

release(address token)

+
+

public

+# +
+
+
+ +Release the tokens that have already vested. + +Emits a [`VestingWallet.ERC20Released`](#VestingWallet-ERC20Released-address-uint256-) event. + +
+
+ + + +
+
+

vestedAmount(uint64 timestamp) → uint256

+
+

public

+# +
+
+
+ +Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. + +
+
+ + + +
+
+

vestedAmount(address token, uint64 timestamp) → uint256

+
+

public

+# +
+
+
+ +Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. + +
+
+ + + +
+
+

_vestingSchedule(uint256 totalAllocation, uint64 timestamp) → uint256

+
+

internal

+# +
+
+
+ +Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for +an asset given its total historical allocation. + +
+
+ + + +
+
+

EtherReleased(uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ERC20Released(address indexed token, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ diff --git a/docs/content/contracts/4.x/api/governance.mdx b/docs/content/contracts/4.x/api/governance.mdx new file mode 100644 index 00000000..b1e4055d --- /dev/null +++ b/docs/content/contracts/4.x/api/governance.mdx @@ -0,0 +1,5854 @@ +--- +title: "Governance" +description: "Smart contract governance utilities and implementations" +--- + +This directory includes primitives for on-chain governance. + +## Governor + +This modular system of Governor contracts allows the deployment on-chain voting protocols similar to [Compound’s Governor Alpha & Bravo](https://compound.finance/docs/governance) and beyond, through the ability to easily customize multiple aspects of the protocol. + + +For a guided experience, set up your Governor contract using [Contracts Wizard](https://wizard.openzeppelin.com/#governor). + +For a written walkthrough, check out our guide on [How to set up on-chain governance](/contracts/4.x/governance). + + +* [`Governor`](#Governor): The core contract that contains all the logic and primitives. It is abstract and requires choosing one of each of the modules below, or custom ones. + +Votes modules determine the source of voting power, and sometimes quorum number. + +* [`GovernorVotes`](#GovernorVotes): Extracts voting weight from an [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes), or since v4.5 an [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes) token. +* [`GovernorVotesComp`](#GovernorVotesComp): Extracts voting weight from a COMP-like or [`ERC20VotesComp`](/contracts/4.x/api/token/ERC20#ERC20VotesComp) token. +* [`GovernorVotesQuorumFraction`](#GovernorVotesQuorumFraction): Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. + +Counting modules determine valid voting options. + +* [`GovernorCountingSimple`](#GovernorCountingSimple): Simple voting mechanism with 3 voting options: Against, For and Abstain. + +Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. + +* [`GovernorTimelockControl`](#GovernorTimelockControl): Connects with an instance of [`TimelockController`](#TimelockController). Allows multiple proposers and executors, in addition to the Governor itself. +* [`GovernorTimelockCompound`](#GovernorTimelockCompound): Connects with an instance of Compound’s [`Timelock`](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol) contract. + +Other extensions can customize the behavior or interface in multiple ways. + +* [`GovernorCompatibilityBravo`](#GovernorCompatibilityBravo): Extends the interface to be fully `GovernorBravo`-compatible. Note that events are compatible regardless of whether this extension is included or not. +* [`GovernorSettings`](#GovernorSettings): Manages some of the settings (voting delay, voting period duration, and proposal threshold) in a way that can be updated through a governance proposal, without requiring an upgrade. +* [`GovernorPreventLateQuorum`](#GovernorPreventLateQuorum): Ensures there is a minimum voting period after quorum is reached as a security protection against large voters. + +In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications: + +* [`votingDelay()`](#Governor-votingDelay-): Delay (in EIP-6372 clock) since the proposal is submitted until voting power is fixed and voting starts. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes. +* [`votingPeriod()`](#Governor-votingPeriod-): Delay (in EIP-6372 clock) since the proposal starts until voting ends. +* [`quorum(uint256 timepoint)`](#Governor-quorum-uint256-): Quorum required for a proposal to be successful. This function includes a `timepoint` argument (see EIP-6372) so the quorum can adapt through time, for example, to follow a token’s `totalSupply`. + + +Functions of the `Governor` contract do not include access control. If you want to restrict access, you should add these checks by overloading the particular functions. Among these, [`Governor._cancel`](#Governor-_cancel-address---uint256---bytes---bytes32-) is internal by default, and you will have to expose it (with the right access control mechanism) yourself if this function is needed. + + +### Core + +[`IGovernor`](#IGovernor) + +[`Governor`](#Governor) + +### Modules + +[`GovernorCountingSimple`](#GovernorCountingSimple) + +[`GovernorVotes`](#GovernorVotes) + +[`GovernorVotesQuorumFraction`](#GovernorVotesQuorumFraction) + +[`GovernorVotesComp`](#GovernorVotesComp) + +### Extensions + +[`GovernorTimelockControl`](#GovernorTimelockControl) + +[`GovernorTimelockCompound`](#GovernorTimelockCompound) + +[`GovernorSettings`](#GovernorSettings) + +[`GovernorPreventLateQuorum`](#GovernorPreventLateQuorum) + +[`GovernorCompatibilityBravo`](#GovernorCompatibilityBravo) + +### Deprecated + +[`GovernorProposalThreshold`](#GovernorProposalThreshold) + +## Utils + +[`Votes`](#Votes) + +## Timelock + +In a governance system, the [`TimelockController`](#TimelockController) contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a [`Governor`](#Governor). + +[`TimelockController`](#TimelockController) + +#### Terminology + +* **Operation:** A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution (see [operation lifecycle](/contracts/4.x/access-control#operation_lifecycle)). If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. +* **Operation status:** + * **Unset:** An operation that is not part of the timelock mechanism. + * **Pending:** An operation that has been scheduled, before the timer expires. + * **Ready:** An operation that has been scheduled, after the timer expires. + * **Done:** An operation that has been executed. +* **Predecessor**: An (optional) dependency between operations. An operation can depend on another operation (its predecessor), forcing the execution order of these two operations. +* **Role**: + * **Admin:** An address (smart contract or EOA) that is in charge of granting the roles of Proposer and Executor. + * **Proposer:** An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. + * **Executor:** An address (smart contract or EOA) that is in charge of executing operations once the timelock has expired. This role can be given to the zero address to allow anyone to execute operations. + +#### Operation structure + +Operation executed by the [`TimelockController`](/contracts/4.x/api/governance#TimelockController) can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. + +Both operations contain: + +* **Target**, the address of the smart contract that the timelock should operate on. +* **Value**, in wei, that should be sent with the transaction. Most of the time this will be 0. Ether can be deposited before-end or passed along when executing the transaction. +* **Data**, containing the encoded function selector and parameters of the call. This can be produced using a number of tools. For example, a maintenance operation granting role `ROLE` to `ACCOUNT` can be encoded using web3js as follows: + +```javascript +const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() +``` + +* **Predecessor**, that specifies a dependency between operations. This dependency is optional. Use `bytes32(0)` if the operation does not have any dependency. +* **Salt**, used to disambiguate two otherwise identical operations. This can be any random value. + +In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. + +#### Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: + +`Unset` -> `Pending` -> `Pending` + `Ready` -> `Done` + +* By calling [`schedule`](/contracts/4.x/api/governance#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-) (or [`scheduleBatch`](/contracts/4.x/api/governance#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-)), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the [`getTimestamp`](/contracts/4.x/api/governance#TimelockController-getTimestamp-bytes32-) method. +* Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. +* By calling [`execute`](/contracts/4.x/api/governance#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-) (or [`executeBatch`](/contracts/4.x/api/governance#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-)), an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. +* [`cancel`](/contracts/4.x/api/governance#TimelockController-TimelockController-cancel-bytes32-) allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is re-scheduled. + +Operations status can be queried using the functions: + +* [`isOperationPending(bytes32)`](/contracts/4.x/api/governance#TimelockController-isOperationPending-bytes32-) +* [`isOperationReady(bytes32)`](/contracts/4.x/api/governance#TimelockController-isOperationReady-bytes32-) +* [`isOperationDone(bytes32)`](/contracts/4.x/api/governance#TimelockController-isOperationDone-bytes32-) + +#### Roles + +##### Admin + +The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, the admin role can be granted to any address (in addition to the timelock itself). After further configuration and testing, this optional admin should renounce its role such that all further maintenance operations have to go through the timelock process. + +This role is identified by the **TIMELOCK_ADMIN_ROLE** value: `0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5` + +##### Proposer + +The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. + + +**Proposer fight:** Having multiple proposers, while providing redundancy in case one becomes unavailable, can be dangerous. As proposer have their say on all operations, they could cancel operations they disagree with, including operations to remove them for the proposers. + + +This role is identified by the **PROPOSER_ROLE** value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` + +##### Executor + +The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executors can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. Alternatively, it is possible to allow _any_ address to execute a proposal once the timelock has expired by granting the executor role to the zero address. + +This role is identified by the **EXECUTOR_ROLE** value: `0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63` + + +A live contract without at least one proposer and one executor is locked. Make sure these roles are filled by reliable entities before the deployer renounces its administrative rights in favour of the timelock contract itself. See the [`AccessControl`](/contracts/4.x/api/access#AccessControl) documentation to learn more about role management. + + + + +
+ +## `Governor` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/Governor.sol"; +``` + +Core of the governance system, designed to be extended though various modules. + +This contract is abstract and requires several functions to be implemented in various modules: + +- A counting module must implement [`IGovernor.quorum`](#IGovernor-quorum-uint256-), [`Governor._quorumReached`](#Governor-_quorumReached-uint256-), [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-) and [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- A voting module must implement [`Governor._getVotes`](#Governor-_getVotes-address-uint256-bytes-) +- Additionally, [`IGovernor.votingPeriod`](#IGovernor-votingPeriod--) must also be implemented + +_Available since v4.3._ + +
+

Modifiers

+
+- [onlyGovernance()](#Governor-onlyGovernance--) +
+
+ +
+

Functions

+
+- [constructor(name_)](#Governor-constructor-string-) +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

onlyGovernance()

+
+

internal

+# +
+
+ +
+ +Restricts a function so it can only be executed through governance proposals. For example, governance +parameter setters in [`GovernorSettings`](#GovernorSettings) are protected using this modifier. + +The governance executing address may be different from the Governor's own address, for example it could be a +timelock. This can be customized by modules by overriding [`Governor._executor`](#Governor-_executor--). The executor is only able to invoke these +functions during the execution of the governor's [`Governor.execute`](#Governor-execute-address---uint256---bytes---bytes32-) function, and not under any other circumstances. Thus, +for example, additional timelock proposers are not able to change governance parameters without going through the +governance protocol (since v4.6). + +
+
+ + + +
+
+

constructor(string name_)

+
+

internal

+# +
+
+
+ +Sets the value for [`Governor.name`](#Governor-name--) and [`Governor.version`](#Governor-version--) + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +See [`IGovernor.name`](#IGovernor-name--). + +
+
+ + + +
+
+

version() → string

+
+

public

+# +
+
+
+ +See [`IGovernor.version`](#IGovernor-version--). + +
+
+ + + +
+
+

hashProposal(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.hashProposal`](#IGovernor-hashProposal-address---uint256---bytes---bytes32-). + +The proposal id is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array +and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id +can be produced from the proposal data which is part of the [`IGovernor.ProposalCreated`](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) event. It can even be computed in +advance, before the proposal is submitted. + +Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the +same proposal (with same operation and same description) will have the same id if submitted on multiple governors +across multiple networks. This also means that in order to execute the same operation twice (on the same +governor) the proposer will have to change the description in order to avoid proposal id conflicts. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +See [`IGovernor.state`](#IGovernor-state-uint256-). + +
+
+ + + +
+
+

proposalThreshold() → uint256

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"The number of votes required in order for a voter to become a proposer"_. + +
+
+ + + +
+
+

proposalSnapshot(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.proposalSnapshot`](#IGovernor-proposalSnapshot-uint256-). + +
+
+ + + +
+
+

proposalDeadline(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.proposalDeadline`](#IGovernor-proposalDeadline-uint256-). + +
+
+ + + +
+
+

proposalProposer(uint256 proposalId) → address

+
+

public

+# +
+
+
+ +Returns the account that created a given proposal. + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Amount of votes already cast passes the threshold limit. + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Is the proposal successful or not. + +
+
+ + + +
+
+

_getVotes(address account, uint256 timepoint, bytes params) → uint256

+
+

internal

+# +
+
+
+ +Get the voting weight of `account` at a specific `timepoint`, for a vote as described by `params`. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 weight, bytes params)

+
+

internal

+# +
+
+
+ +Register a vote for `proposalId` by `account` with a given `support`, voting `weight` and voting `params`. + +Note: Support is generic and can represent various things depending on the voting system used. + +
+
+ + + +
+
+

_defaultParams() → bytes

+
+

internal

+# +
+
+
+ +Default additional encoded parameters used by castVote methods that don't include them + +Note: Should be overridden by specific implementations to use an appropriate value, the +meaning of the additional params, in the context of that implementation + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.propose`](#IGovernor-propose-address---uint256---bytes---string-). This function has opt-in frontrunning protection, described in [`Governor._isValidDescriptionForProposer`](#Governor-_isValidDescriptionForProposer-address-string-). + +
+
+ + + +
+
+

execute(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.execute`](#IGovernor-execute-address---uint256---bytes---bytes32-). + +
+
+ + + +
+
+

cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.cancel`](#IGovernor-cancel-address---uint256---bytes---bytes32-). + +
+
+ + + +
+
+

_execute(uint256, address[] targets, uint256[] values, bytes[] calldatas, bytes32)

+
+

internal

+# +
+
+
+ +Internal execution mechanism. Can be overridden to implement different execution mechanism + +
+
+ + + +
+
+

_beforeExecute(uint256, address[] targets, uint256[], bytes[] calldatas, bytes32)

+
+

internal

+# +
+
+
+ +Hook before execution is triggered. + +
+
+ + + +
+
+

_afterExecute(uint256, address[], uint256[], bytes[], bytes32)

+
+

internal

+# +
+
+
+ +Hook after execution is triggered. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Internal cancel mechanism: locks up the proposal timer, preventing it from being re-submitted. Marks it as +canceled to allow distinguishing it from executed proposals. + +Emits a [`IGovernor.ProposalCanceled`](#IGovernor-ProposalCanceled-uint256-) event. + +
+
+ + + +
+
+

getVotes(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.getVotes`](#IGovernor-getVotes-address-uint256-). + +
+
+ + + +
+
+

getVotesWithParams(address account, uint256 timepoint, bytes params) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.getVotesWithParams`](#IGovernor-getVotesWithParams-address-uint256-bytes-). + +
+
+ + + +
+
+

castVote(uint256 proposalId, uint8 support) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.castVote`](#IGovernor-castVote-uint256-uint8-). + +
+
+ + + +
+
+

castVoteWithReason(uint256 proposalId, uint8 support, string reason) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.castVoteWithReason`](#IGovernor-castVoteWithReason-uint256-uint8-string-). + +
+
+ + + +
+
+

castVoteWithReasonAndParams(uint256 proposalId, uint8 support, string reason, bytes params) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.castVoteWithReasonAndParams`](#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-). + +
+
+ + + +
+
+

castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.castVoteBySig`](#IGovernor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

castVoteWithReasonAndParamsBySig(uint256 proposalId, uint8 support, string reason, bytes params, uint8 v, bytes32 r, bytes32 s) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.castVoteWithReasonAndParamsBySig`](#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

_castVote(uint256 proposalId, address account, uint8 support, string reason) → uint256

+
+

internal

+# +
+
+
+ +Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve +voting weight using [`IGovernor.getVotes`](#IGovernor-getVotes-address-uint256-) and call the [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) internal function. Uses the _defaultParams(). + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

_castVote(uint256 proposalId, address account, uint8 support, string reason, bytes params) → uint256

+
+

internal

+# +
+
+
+ +Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve +voting weight using [`IGovernor.getVotes`](#IGovernor-getVotes-address-uint256-) and call the [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) internal function. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

relay(address target, uint256 value, bytes data)

+
+

external

+# +
+
+
+ +Relays a transaction or function call to an arbitrary target. In cases where the governance executor +is some contract other than the governor itself, like when using a timelock, this function can be invoked +in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. +Note that if the executor is simply the governor itself, use of `relay` is redundant. + +
+
+ + + +
+
+

_executor() → address

+
+

internal

+# +
+
+
+ +Address through which the governor executes action. Will be overloaded by module that execute actions +through another contract such as a timelock. + +
+
+ + + +
+
+

onERC721Received(address, address, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC721Receiver.onERC721Received`](/contracts/4.x/api/token/ERC721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-). + +
+
+ + + +
+
+

onERC1155Received(address, address, uint256, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC1155Receiver.onERC1155Received`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-). + +
+
+ + + +
+
+

onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC1155Receiver.onERC1155BatchReceived`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-). + +
+
+ + + +
+
+

_isValidDescriptionForProposer(address proposer, string description) → bool

+
+

internal

+# +
+
+
+ +Check if the proposer is authorized to submit a proposal with the given description. + +If the proposal description ends with `#proposer=0x???`, where `0x???` is an address written as a hex string +(case insensitive), then the submission of this proposal will only be authorized to said address. + +This is used for frontrunning protection. By adding this pattern at the end of their proposal, one can ensure +that no other address can submit the same proposal. An attacker would have to either remove or change that part, +which would result in a different proposal id. + +If the description does not match this pattern, it is unrestricted and anyone can submit it. This includes: +- If the `0x???` part is not a valid hex string. +- If the `0x???` part is a valid hex string, but does not contain exactly 40 hex digits. +- If it ends with the expected suffix followed by newlines or other whitespace. +- If it ends with some other similar suffix, e.g. `#other=abc`. +- If it does not end with any such suffix. + +
+
+ + + +
+
+

BALLOT_TYPEHASH() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

EXTENDED_BALLOT_TYPEHASH() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `IGovernor` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/IGovernor.sol"; +``` + +Interface of the [`Governor`](#Governor) core. + +_Available since v4.3._ + +
+

Functions

+
+- [name()](#IGovernor-name--) +- [version()](#IGovernor-version--) +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#IGovernor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#IGovernor-state-uint256-) +- [proposalSnapshot(proposalId)](#IGovernor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#IGovernor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#IGovernor-proposalProposer-uint256-) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [getVotes(account, timepoint)](#IGovernor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#IGovernor-getVotesWithParams-address-uint256-bytes-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +- [propose(targets, values, calldatas, description)](#IGovernor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#IGovernor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#IGovernor-cancel-address---uint256---bytes---bytes32-) +- [castVote(proposalId, support)](#IGovernor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#IGovernor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#IGovernor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +#### IERC6372 +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### IERC165 +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +Name of the governor instance (used in building the ERC712 domain separator). + +
+
+ + + +
+
+

version() → string

+
+

public

+# +
+
+
+ +Version of the governor instance (used in building the ERC712 domain separator). Default: "1" + +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +See [`IERC6372`](/contracts/4.x/api/interfaces#IERC6372) + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +See EIP-6372. + +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

public

+# +
+
+
+ +A description of the possible `support` values for [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +NOTE: The string can be decoded by the standard +[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +JavaScript class. + +
+
+ + + +
+
+

hashProposal(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Hashing function used to (re)build the proposal id from the proposal details.. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Current state of a proposal, following Compound's convention + +
+
+ + + +
+
+

proposalSnapshot(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Timepoint used to retrieve user's votes and quorum. If using block number (as per Compound's Comp), the +snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the +following block. + +
+
+ + + +
+
+

proposalDeadline(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is +possible to cast a vote during this block. + +
+
+ + + +
+
+

proposalProposer(uint256 proposalId) → address

+
+

public

+# +
+
+
+ +The account that created a proposal. + +
+
+ + + +
+
+

votingDelay() → uint256

+
+

public

+# +
+
+
+ +Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends +on the clock (see EIP-6372) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a +proposal starts. + +
+
+ + + +
+
+

votingPeriod() → uint256

+
+

public

+# +
+
+
+ +Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock +(see EIP-6372) this contract uses. + +NOTE: The [`IGovernor.votingDelay`](#IGovernor-votingDelay--) can delay the start of the vote. This must be considered when setting the voting +duration compared to the voting delay. + +
+
+ + + +
+
+

quorum(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Minimum number of cast voted required for a proposal to be successful. + +NOTE: The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows to scale the +quorum depending on values such as the totalSupply of a token at this timepoint (see [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes)). + +
+
+ + + +
+
+

getVotes(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Voting power of an `account` at a specific `timepoint`. + +Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or +multiple), [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes) tokens. + +
+
+ + + +
+
+

getVotesWithParams(address account, uint256 timepoint, bytes params) → uint256

+
+

public

+# +
+
+
+ +Voting power of an `account` at a specific `timepoint` given additional encoded parameters. + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +Returns whether `account` has cast a vote on `proposalId`. + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256 proposalId

+
+

public

+# +
+
+
+ +Create a new proposal. Vote start after a delay specified by [`IGovernor.votingDelay`](#IGovernor-votingDelay--) and lasts for a +duration specified by [`IGovernor.votingPeriod`](#IGovernor-votingPeriod--). + +Emits a [`IGovernor.ProposalCreated`](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) event. + +
+
+ + + +
+
+

execute(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256 proposalId

+
+

public

+# +
+
+
+ +Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the +deadline to be reached. + +Emits a [`IGovernor.ProposalExecuted`](#IGovernor-ProposalExecuted-uint256-) event. + +Note: some module can modify the requirements for execution, for example by adding an additional timelock. + +
+
+ + + +
+
+

cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256 proposalId

+
+

public

+# +
+
+
+ +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. +before the vote starts. + +Emits a [`IGovernor.ProposalCanceled`](#IGovernor-ProposalCanceled-uint256-) event. + +
+
+ + + +
+
+

castVote(uint256 proposalId, uint8 support) → uint256 balance

+
+

public

+# +
+
+
+ +Cast a vote + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReason(uint256 proposalId, uint8 support, string reason) → uint256 balance

+
+

public

+# +
+
+
+ +Cast a vote with a reason + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReasonAndParams(uint256 proposalId, uint8 support, string reason, bytes params) → uint256 balance

+
+

public

+# +
+
+
+ +Cast a vote with a reason and additional encoded parameters + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) event depending on the length of params. + +
+
+ + + +
+
+

castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) → uint256 balance

+
+

public

+# +
+
+
+ +Cast a vote using the user's cryptographic signature. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReasonAndParamsBySig(uint256 proposalId, uint8 support, string reason, bytes params, uint8 v, bytes32 r, bytes32 s) → uint256 balance

+
+

public

+# +
+
+
+ +Cast a vote with a reason and additional encoded parameters using the user's cryptographic signature. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) event depending on the length of params. + +
+
+ + + +
+
+

ProposalCreated(uint256 proposalId, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 voteStart, uint256 voteEnd, string description)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is created. + +
+
+ + +
+
+

ProposalCanceled(uint256 proposalId)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is canceled. + +
+
+ + +
+
+

ProposalExecuted(uint256 proposalId)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is executed. + +
+
+ + +
+
+

VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason)

+
+

event

+# +
+
+ +
+ +Emitted when a vote is cast without params. + +Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + +
+
+ + +
+
+

VoteCastWithParams(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason, bytes params)

+
+

event

+# +
+
+ +
+ +Emitted when a vote is cast with params. + +Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. +`params` are additional encoded parameters. Their interpepretation also depends on the voting module used. + +
+
+ + + +
+ +## `TimelockController` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/TimelockController.sol"; +``` + +Contract module which acts as a timelocked controller. When set as the +owner of an `Ownable` smart contract, it enforces a timelock on all +`onlyOwner` maintenance operations. This gives time for users of the +controlled contract to exit before a potentially dangerous maintenance +operation is applied. + +By default, this contract is self administered, meaning administration tasks +have to go through the timelock process. The proposer (resp executor) role +is in charge of proposing (resp executing) operations. A common use case is +to position this [`TimelockController`](#TimelockController) as the owner of a smart contract, with +a multisig or a DAO as the sole proposer. + +_Available since v3.3._ + +
+

Modifiers

+
+- [onlyRoleOrOpenRole(role)](#TimelockController-onlyRoleOrOpenRole-bytes32-) +
+
+ +
+

Functions

+
+- [constructor(minDelay, proposers, executors, admin)](#TimelockController-constructor-uint256-address---address---address-) +- [receive()](#TimelockController-receive--) +- [supportsInterface(interfaceId)](#TimelockController-supportsInterface-bytes4-) +- [isOperation(id)](#TimelockController-isOperation-bytes32-) +- [isOperationPending(id)](#TimelockController-isOperationPending-bytes32-) +- [isOperationReady(id)](#TimelockController-isOperationReady-bytes32-) +- [isOperationDone(id)](#TimelockController-isOperationDone-bytes32-) +- [getTimestamp(id)](#TimelockController-getTimestamp-bytes32-) +- [getMinDelay()](#TimelockController-getMinDelay--) +- [hashOperation(target, value, data, predecessor, salt)](#TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-) +- [hashOperationBatch(targets, values, payloads, predecessor, salt)](#TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-) +- [schedule(target, value, data, predecessor, salt, delay)](#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-) +- [scheduleBatch(targets, values, payloads, predecessor, salt, delay)](#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-) +- [cancel(id)](#TimelockController-cancel-bytes32-) +- [execute(target, value, payload, predecessor, salt)](#TimelockController-execute-address-uint256-bytes-bytes32-bytes32-) +- [executeBatch(targets, values, payloads, predecessor, salt)](#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-) +- [_execute(target, value, data)](#TimelockController-_execute-address-uint256-bytes-) +- [updateDelay(newDelay)](#TimelockController-updateDelay-uint256-) +- [onERC721Received(, , , )](#TimelockController-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#TimelockController-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#TimelockController-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [TIMELOCK_ADMIN_ROLE()](#TimelockController-TIMELOCK_ADMIN_ROLE-bytes32) +- [PROPOSER_ROLE()](#TimelockController-PROPOSER_ROLE-bytes32) +- [EXECUTOR_ROLE()](#TimelockController-EXECUTOR_ROLE-bytes32) +- [CANCELLER_ROLE()](#TimelockController-CANCELLER_ROLE-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### AccessControl +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [_grantRole(role, account)](#AccessControl-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControl-_revokeRole-bytes32-address-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControl +
+
+ +
+

Events

+
+- [CallScheduled(id, index, target, value, data, predecessor, delay)](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-) +- [CallExecuted(id, index, target, value, data)](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) +- [CallSalt(id, salt)](#TimelockController-CallSalt-bytes32-bytes32-) +- [Cancelled(id)](#TimelockController-Cancelled-bytes32-) +- [MinDelayChange(oldDuration, newDuration)](#TimelockController-MinDelayChange-uint256-uint256-) +#### IERC1155Receiver +#### IERC721Receiver +#### AccessControl +#### ERC165 +#### IERC165 +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

onlyRoleOrOpenRole(bytes32 role)

+
+

internal

+# +
+
+ +
+ +Modifier to make a function callable only by a certain role. In +addition to checking the sender's role, `address(0)` 's role is also +considered. Granting a role to `address(0)` is equivalent to enabling +this role for everyone. + +
+
+ + + +
+
+

constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)

+
+

public

+# +
+
+
+ +Initializes the contract with the following parameters: + +- `minDelay`: initial minimum delay for operations +- `proposers`: accounts to be granted proposer and canceller roles +- `executors`: accounts to be granted executor role +- `admin`: optional account to be granted admin role; disable with zero address + + +The optional admin can aid with initial configuration of roles after deployment +without being subject to delay, but this role should be subsequently renounced in favor of +administration through timelocked proposals. Previous versions of this contract would assign +this admin to the deployer automatically and should be renounced as well. + + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +Contract might receive/hold ETH as part of the maintenance process. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

isOperation(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an id correspond to a registered operation. This +includes both Pending, Ready and Done operations. + +
+
+ + + +
+
+

isOperationPending(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready". + +
+
+ + + +
+
+

isOperationReady(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending". + +
+
+ + + +
+
+

isOperationDone(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an operation is done or not. + +
+
+ + + +
+
+

getTimestamp(bytes32 id) → uint256

+
+

public

+# +
+
+
+ +Returns the timestamp at which an operation becomes ready (0 for +unset operations, 1 for done operations). + +
+
+ + + +
+
+

getMinDelay() → uint256

+
+

public

+# +
+
+
+ +Returns the minimum delay for an operation to become valid. + +This value can be changed by executing an operation that calls `updateDelay`. + +
+
+ + + +
+
+

hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) → bytes32

+
+

public

+# +
+
+
+ +Returns the identifier of an operation containing a single +transaction. + +
+
+ + + +
+
+

hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) → bytes32

+
+

public

+# +
+
+
+ +Returns the identifier of an operation containing a batch of +transactions. + +
+
+ + + +
+
+

schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)

+
+

public

+# +
+
+
+ +Schedule an operation containing a single transaction. + +Emits [`TimelockController.CallSalt`](#TimelockController-CallSalt-bytes32-bytes32-) if salt is nonzero, and [`TimelockController.CallScheduled`](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-). + +Requirements: + +- the caller must have the 'proposer' role. + +
+
+ + + +
+
+

scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)

+
+

public

+# +
+
+
+ +Schedule an operation containing a batch of transactions. + +Emits [`TimelockController.CallSalt`](#TimelockController-CallSalt-bytes32-bytes32-) if salt is nonzero, and one [`TimelockController.CallScheduled`](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-) event per transaction in the batch. + +Requirements: + +- the caller must have the 'proposer' role. + +
+
+ + + +
+
+

cancel(bytes32 id)

+
+

public

+# +
+
+
+ +Cancel an operation. + +Requirements: + +- the caller must have the 'canceller' role. + +
+
+ + + +
+
+

execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt)

+
+

public

+# +
+
+
+ +Execute an (ready) operation containing a single transaction. + +Emits a [`TimelockController.CallExecuted`](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) event. + +Requirements: + +- the caller must have the 'executor' role. + +
+
+ + + +
+
+

executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt)

+
+

public

+# +
+
+
+ +Execute an (ready) operation containing a batch of transactions. + +Emits one [`TimelockController.CallExecuted`](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) event per transaction in the batch. + +Requirements: + +- the caller must have the 'executor' role. + +
+
+ + + +
+
+

_execute(address target, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Execute an operation's call. + +
+
+ + + +
+
+

updateDelay(uint256 newDelay)

+
+

external

+# +
+
+
+ +Changes the minimum timelock duration for future operations. + +Emits a [`TimelockController.MinDelayChange`](#TimelockController-MinDelayChange-uint256-uint256-) event. + +Requirements: + +- the caller must be the timelock itself. This can only be achieved by scheduling and later executing +an operation where the timelock is the target and the data is the ABI-encoded call to this function. + +
+
+ + + +
+
+

onERC721Received(address, address, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC721Receiver.onERC721Received`](/contracts/4.x/api/token/ERC721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-). + +
+
+ + + +
+
+

onERC1155Received(address, address, uint256, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC1155Receiver.onERC1155Received`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-). + +
+
+ + + +
+
+

onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC1155Receiver.onERC1155BatchReceived`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-). + +
+
+ + + +
+
+

TIMELOCK_ADMIN_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

PROPOSER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

EXECUTOR_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

CANCELLER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)

+
+

event

+# +
+
+ +
+ +Emitted when a call is scheduled as part of operation `id`. + +
+
+ + +
+
+

CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)

+
+

event

+# +
+
+ +
+ +Emitted when a call is performed as part of operation `id`. + +
+
+ + +
+
+

CallSalt(bytes32 indexed id, bytes32 salt)

+
+

event

+# +
+
+ +
+ +Emitted when new proposal is scheduled with non-zero salt. + +
+
+ + +
+
+

Cancelled(bytes32 indexed id)

+
+

event

+# +
+
+ +
+ +Emitted when operation `id` is cancelled. + +
+
+ + +
+
+

MinDelayChange(uint256 oldDuration, uint256 newDuration)

+
+

event

+# +
+
+ +
+ +Emitted when the minimum delay for future operations is modified. + +
+
+ + + +
+ +## `GovernorCompatibilityBravo` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; +``` + +Compatibility layer that implements GovernorBravo compatibility on top of [`Governor`](#Governor). + +This compatibility layer includes a voting system and requires a [`IGovernorTimelock`](#IGovernorTimelock) compatible module to be added +through inheritance. It does not include token bindings, nor does it include any variable upgrade patterns. + +NOTE: When using this module, you may need to enable the Solidity optimizer to avoid hitting the contract size limit. + +_Available since v4.3._ + +
+

Functions

+
+- [COUNTING_MODE()](#GovernorCompatibilityBravo-COUNTING_MODE--) +- [propose(targets, values, calldatas, description)](#GovernorCompatibilityBravo-propose-address---uint256---bytes---string-) +- [propose(targets, values, signatures, calldatas, description)](#GovernorCompatibilityBravo-propose-address---uint256---string---bytes---string-) +- [queue(proposalId)](#GovernorCompatibilityBravo-queue-uint256-) +- [execute(proposalId)](#GovernorCompatibilityBravo-execute-uint256-) +- [cancel(proposalId)](#GovernorCompatibilityBravo-cancel-uint256-) +- [cancel(targets, values, calldatas, descriptionHash)](#GovernorCompatibilityBravo-cancel-address---uint256---bytes---bytes32-) +- [proposals(proposalId)](#GovernorCompatibilityBravo-proposals-uint256-) +- [getActions(proposalId)](#GovernorCompatibilityBravo-getActions-uint256-) +- [getReceipt(proposalId, voter)](#GovernorCompatibilityBravo-getReceipt-uint256-address-) +- [quorumVotes()](#GovernorCompatibilityBravo-quorumVotes--) +- [hasVoted(proposalId, account)](#GovernorCompatibilityBravo-hasVoted-uint256-address-) +- [_quorumReached(proposalId)](#GovernorCompatibilityBravo-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#GovernorCompatibilityBravo-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, weight, )](#GovernorCompatibilityBravo-_countVote-uint256-address-uint8-uint256-bytes-) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernorCompatibilityBravo +#### IGovernorTimelock +- [timelock()](#IGovernorTimelock-timelock--) +- [proposalEta(proposalId)](#IGovernorTimelock-proposalEta-uint256-) +- [queue(targets, values, calldatas, descriptionHash)](#IGovernorTimelock-queue-address---uint256---bytes---bytes32-) +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernorCompatibilityBravo +#### IGovernorTimelock +- [ProposalQueued(proposalId, eta)](#IGovernorTimelock-ProposalQueued-uint256-uint256-) +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

public

+# +
+
+
+ +A description of the possible `support` values for [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + +NOTE: The string can be decoded by the standard +[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +JavaScript class. + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.propose`](#IGovernor-propose-address---uint256---bytes---string-). + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.propose`](#IGovernorCompatibilityBravo-propose-address---uint256---string---bytes---string-). + +
+
+ + + +
+
+

queue(uint256 proposalId)

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.queue`](#IGovernorCompatibilityBravo-queue-uint256-). + +
+
+ + + +
+
+

execute(uint256 proposalId)

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.execute`](#IGovernorCompatibilityBravo-execute-uint256-). + +
+
+ + + +
+
+

cancel(uint256 proposalId)

+
+

public

+# +
+
+
+ +Cancel a proposal with GovernorBravo logic. + +
+
+ + + +
+
+

cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Cancel a proposal with GovernorBravo logic. At any moment a proposal can be cancelled, either by the +proposer, or by third parties if the proposer's voting power has dropped below the proposal threshold. + +
+
+ + + +
+
+

proposals(uint256 proposalId) → uint256 id, address proposer, uint256 eta, uint256 startBlock, uint256 endBlock, uint256 forVotes, uint256 againstVotes, uint256 abstainVotes, bool canceled, bool executed

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.proposals`](#IGovernorCompatibilityBravo-proposals-uint256-). + +
+
+ + + +
+
+

getActions(uint256 proposalId) → address[] targets, uint256[] values, string[] signatures, bytes[] calldatas

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.getActions`](#IGovernorCompatibilityBravo-getActions-uint256-). + +
+
+ + + +
+
+

getReceipt(uint256 proposalId, address voter) → struct IGovernorCompatibilityBravo.Receipt

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.getReceipt`](#IGovernorCompatibilityBravo-getReceipt-uint256-address-). + +
+
+ + + +
+
+

quorumVotes() → uint256

+
+

public

+# +
+
+
+ +See [`IGovernorCompatibilityBravo.quorumVotes`](#IGovernorCompatibilityBravo-quorumVotes--). + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +See [`IGovernor.hasVoted`](#IGovernor-hasVoted-uint256-address-). + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._quorumReached`](#Governor-_quorumReached-uint256-). In this module, only forVotes count toward the quorum. + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-). In this module, the forVotes must be strictly over the againstVotes. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 weight, bytes)

+
+

internal

+# +
+
+
+ +See [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-). In this module, the support follows Governor Bravo. + +
+
+ + + +
+ +## `IGovernorCompatibilityBravo` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/compatibility/IGovernorCompatibilityBravo.sol"; +``` + +Interface extension that adds missing functions to the [`Governor`](#Governor) core to provide `GovernorBravo` compatibility. + +_Available since v4.3._ + +
+

Functions

+
+- [quorumVotes()](#IGovernorCompatibilityBravo-quorumVotes--) +- [proposals()](#IGovernorCompatibilityBravo-proposals-uint256-) +- [propose(targets, values, signatures, calldatas, description)](#IGovernorCompatibilityBravo-propose-address---uint256---string---bytes---string-) +- [queue(proposalId)](#IGovernorCompatibilityBravo-queue-uint256-) +- [execute(proposalId)](#IGovernorCompatibilityBravo-execute-uint256-) +- [cancel(proposalId)](#IGovernorCompatibilityBravo-cancel-uint256-) +- [getActions(proposalId)](#IGovernorCompatibilityBravo-getActions-uint256-) +- [getReceipt(proposalId, voter)](#IGovernorCompatibilityBravo-getReceipt-uint256-address-) +#### IGovernor +- [name()](#IGovernor-name--) +- [version()](#IGovernor-version--) +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#IGovernor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#IGovernor-state-uint256-) +- [proposalSnapshot(proposalId)](#IGovernor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#IGovernor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#IGovernor-proposalProposer-uint256-) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [getVotes(account, timepoint)](#IGovernor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#IGovernor-getVotesWithParams-address-uint256-bytes-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +- [propose(targets, values, calldatas, description)](#IGovernor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#IGovernor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#IGovernor-cancel-address---uint256---bytes---bytes32-) +- [castVote(proposalId, support)](#IGovernor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#IGovernor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#IGovernor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +#### IERC6372 +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### IERC165 +
+
+ + + +
+
+

quorumVotes() → uint256

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface. + +
+
+ + + +
+
+

proposals(uint256) → uint256 id, address proposer, uint256 eta, uint256 startBlock, uint256 endBlock, uint256 forVotes, uint256 againstVotes, uint256 abstainVotes, bool canceled, bool executed

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"The official record of all proposals ever proposed"_. + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"Function used to propose a new proposal"_. + +
+
+ + + +
+
+

queue(uint256 proposalId)

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"Queues a proposal of state succeeded"_. + +
+
+ + + +
+
+

execute(uint256 proposalId)

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"Executes a queued proposal if eta has passed"_. + +
+
+ + + +
+
+

cancel(uint256 proposalId)

+
+

public

+# +
+
+
+ +Cancels a proposal only if the sender is the proposer or the proposer delegates' voting power dropped below the proposal threshold. + +
+
+ + + +
+
+

getActions(uint256 proposalId) → address[] targets, uint256[] values, string[] signatures, bytes[] calldatas

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"Gets actions of a proposal"_. + +
+
+ + + +
+
+

getReceipt(uint256 proposalId, address voter) → struct IGovernorCompatibilityBravo.Receipt

+
+

public

+# +
+
+
+ +Part of the Governor Bravo's interface: _"Gets the receipt for a voter on a given proposal"_. + +
+
+ + + +
+ +## `GovernorCountingSimple` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; +``` + +Extension of [`Governor`](#Governor) for simple, 3 options, vote counting. + +_Available since v4.3._ + +
+

Functions

+
+- [COUNTING_MODE()](#GovernorCountingSimple-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#GovernorCountingSimple-hasVoted-uint256-address-) +- [proposalVotes(proposalId)](#GovernorCountingSimple-proposalVotes-uint256-) +- [_quorumReached(proposalId)](#GovernorCountingSimple-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#GovernorCountingSimple-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, weight, )](#GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes-) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

public

+# +
+
+
+ +See [`IGovernor.COUNTING_MODE`](#IGovernor-COUNTING_MODE--). + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +See [`IGovernor.hasVoted`](#IGovernor-hasVoted-uint256-address-). + +
+
+ + + +
+
+

proposalVotes(uint256 proposalId) → uint256 againstVotes, uint256 forVotes, uint256 abstainVotes

+
+

public

+# +
+
+
+ +Accessor to the internal vote counts. + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._quorumReached`](#Governor-_quorumReached-uint256-). + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-). In this module, the forVotes must be strictly over the againstVotes. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 weight, bytes)

+
+

internal

+# +
+
+
+ +See [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-). In this module, the support follows the `VoteType` enum (from Governor Bravo). + +
+
+ + + +
+ +## `GovernorPreventLateQuorum` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorPreventLateQuorum.sol"; +``` + +A module that ensures there is a minimum voting period after quorum is reached. This prevents a large voter from +swaying a vote and triggering quorum at the last minute, by ensuring there is always time for other voters to react +and try to oppose the decision. + +If a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at +least a specified time has passed (the "vote extension" parameter). This parameter can be set through a governance +proposal. + +_Available since v4.5._ + +
+

Functions

+
+- [constructor(initialVoteExtension)](#GovernorPreventLateQuorum-constructor-uint64-) +- [proposalDeadline(proposalId)](#GovernorPreventLateQuorum-proposalDeadline-uint256-) +- [_castVote(proposalId, account, support, reason, params)](#GovernorPreventLateQuorum-_castVote-uint256-address-uint8-string-bytes-) +- [lateQuorumVoteExtension()](#GovernorPreventLateQuorum-lateQuorumVoteExtension--) +- [setLateQuorumVoteExtension(newVoteExtension)](#GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint64-) +- [_setLateQuorumVoteExtension(newVoteExtension)](#GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint64-) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+- [ProposalExtended(proposalId, extendedDeadline)](#GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-) +- [LateQuorumVoteExtensionSet(oldVoteExtension, newVoteExtension)](#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-) +#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(uint64 initialVoteExtension)

+
+

internal

+# +
+
+
+ +Initializes the vote extension parameter: the time in either number of blocks or seconds (depending on the governor +clock mode) that is required to pass since the moment a proposal reaches quorum until its voting period ends. If +necessary the voting period will be extended beyond the one set during proposal creation. + +
+
+ + + +
+
+

proposalDeadline(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Returns the proposal deadline, which may have been extended beyond that set at proposal creation, if the +proposal reached quorum late in the voting period. See [`Governor.proposalDeadline`](#Governor-proposalDeadline-uint256-). + +
+
+ + + +
+
+

_castVote(uint256 proposalId, address account, uint8 support, string reason, bytes params) → uint256

+
+

internal

+# +
+
+
+ +Casts a vote and detects if it caused quorum to be reached, potentially extending the voting period. See +[`Governor._castVote`](#Governor-_castVote-uint256-address-uint8-string-bytes-). + +May emit a [`GovernorPreventLateQuorum.ProposalExtended`](#GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-) event. + +
+
+ + + +
+
+

lateQuorumVoteExtension() → uint64

+
+

public

+# +
+
+
+ +Returns the current value of the vote extension parameter: the number of blocks that are required to pass +from the time a proposal reaches quorum until its voting period ends. + +
+
+ + + +
+
+

setLateQuorumVoteExtension(uint64 newVoteExtension)

+
+

public

+# +
+
+
+ +Changes the [`GovernorPreventLateQuorum.lateQuorumVoteExtension`](#GovernorPreventLateQuorum-lateQuorumVoteExtension--). This operation can only be performed by the governance executor, +generally through a governance proposal. + +Emits a [`GovernorPreventLateQuorum.LateQuorumVoteExtensionSet`](#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-) event. + +
+
+ + + +
+
+

_setLateQuorumVoteExtension(uint64 newVoteExtension)

+
+

internal

+# +
+
+
+ +Changes the [`GovernorPreventLateQuorum.lateQuorumVoteExtension`](#GovernorPreventLateQuorum-lateQuorumVoteExtension--). This is an internal function that can be exposed in a public function +like [`GovernorPreventLateQuorum.setLateQuorumVoteExtension`](#GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint64-) if another access control mechanism is needed. + +Emits a [`GovernorPreventLateQuorum.LateQuorumVoteExtensionSet`](#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-) event. + +
+
+ + + +
+
+

ProposalExtended(uint256 indexed proposalId, uint64 extendedDeadline)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period. + +
+
+ + +
+
+

LateQuorumVoteExtensionSet(uint64 oldVoteExtension, uint64 newVoteExtension)

+
+

event

+# +
+
+ +
+ +Emitted when the [`GovernorPreventLateQuorum.lateQuorumVoteExtension`](#GovernorPreventLateQuorum-lateQuorumVoteExtension--) parameter is changed. + +
+
+ + + +
+ +## `GovernorProposalThreshold` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorProposalThreshold.sol"; +``` + +Extension of [`Governor`](#Governor) for proposal restriction to token holders with a minimum balance. + +_Available since v4.3._ +_Deprecated since v4.4._ + +
+

Functions

+
+- [propose(targets, values, calldatas, description)](#GovernorProposalThreshold-propose-address---uint256---bytes---string-) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.propose`](#IGovernor-propose-address---uint256---bytes---string-). This function has opt-in frontrunning protection, described in [`Governor._isValidDescriptionForProposer`](#Governor-_isValidDescriptionForProposer-address-string-). + +
+
+ + + +
+ +## `GovernorSettings` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; +``` + +Extension of [`Governor`](#Governor) for settings updatable through governance. + +_Available since v4.4._ + +
+

Functions

+
+- [constructor(initialVotingDelay, initialVotingPeriod, initialProposalThreshold)](#GovernorSettings-constructor-uint256-uint256-uint256-) +- [votingDelay()](#GovernorSettings-votingDelay--) +- [votingPeriod()](#GovernorSettings-votingPeriod--) +- [proposalThreshold()](#GovernorSettings-proposalThreshold--) +- [setVotingDelay(newVotingDelay)](#GovernorSettings-setVotingDelay-uint256-) +- [setVotingPeriod(newVotingPeriod)](#GovernorSettings-setVotingPeriod-uint256-) +- [setProposalThreshold(newProposalThreshold)](#GovernorSettings-setProposalThreshold-uint256-) +- [_setVotingDelay(newVotingDelay)](#GovernorSettings-_setVotingDelay-uint256-) +- [_setVotingPeriod(newVotingPeriod)](#GovernorSettings-_setVotingPeriod-uint256-) +- [_setProposalThreshold(newProposalThreshold)](#GovernorSettings-_setProposalThreshold-uint256-) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+- [VotingDelaySet(oldVotingDelay, newVotingDelay)](#GovernorSettings-VotingDelaySet-uint256-uint256-) +- [VotingPeriodSet(oldVotingPeriod, newVotingPeriod)](#GovernorSettings-VotingPeriodSet-uint256-uint256-) +- [ProposalThresholdSet(oldProposalThreshold, newProposalThreshold)](#GovernorSettings-ProposalThresholdSet-uint256-uint256-) +#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(uint256 initialVotingDelay, uint256 initialVotingPeriod, uint256 initialProposalThreshold)

+
+

internal

+# +
+
+
+ +Initialize the governance parameters. + +
+
+ + + +
+
+

votingDelay() → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.votingDelay`](#IGovernor-votingDelay--). + +
+
+ + + +
+
+

votingPeriod() → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.votingPeriod`](#IGovernor-votingPeriod--). + +
+
+ + + +
+
+

proposalThreshold() → uint256

+
+

public

+# +
+
+
+ +See [`Governor.proposalThreshold`](#Governor-proposalThreshold--). + +
+
+ + + +
+
+

setVotingDelay(uint256 newVotingDelay)

+
+

public

+# +
+
+
+ +Update the voting delay. This operation can only be performed through a governance proposal. + +Emits a [`GovernorSettings.VotingDelaySet`](#GovernorSettings-VotingDelaySet-uint256-uint256-) event. + +
+
+ + + +
+
+

setVotingPeriod(uint256 newVotingPeriod)

+
+

public

+# +
+
+
+ +Update the voting period. This operation can only be performed through a governance proposal. + +Emits a [`GovernorSettings.VotingPeriodSet`](#GovernorSettings-VotingPeriodSet-uint256-uint256-) event. + +
+
+ + + +
+
+

setProposalThreshold(uint256 newProposalThreshold)

+
+

public

+# +
+
+
+ +Update the proposal threshold. This operation can only be performed through a governance proposal. + +Emits a [`GovernorSettings.ProposalThresholdSet`](#GovernorSettings-ProposalThresholdSet-uint256-uint256-) event. + +
+
+ + + +
+
+

_setVotingDelay(uint256 newVotingDelay)

+
+

internal

+# +
+
+
+ +Internal setter for the voting delay. + +Emits a [`GovernorSettings.VotingDelaySet`](#GovernorSettings-VotingDelaySet-uint256-uint256-) event. + +
+
+ + + +
+
+

_setVotingPeriod(uint256 newVotingPeriod)

+
+

internal

+# +
+
+
+ +Internal setter for the voting period. + +Emits a [`GovernorSettings.VotingPeriodSet`](#GovernorSettings-VotingPeriodSet-uint256-uint256-) event. + +
+
+ + + +
+
+

_setProposalThreshold(uint256 newProposalThreshold)

+
+

internal

+# +
+
+
+ +Internal setter for the proposal threshold. + +Emits a [`GovernorSettings.ProposalThresholdSet`](#GovernorSettings-ProposalThresholdSet-uint256-uint256-) event. + +
+
+ + + +
+
+

VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `GovernorTimelockCompound` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol"; +``` + +Extension of [`Governor`](#Governor) that binds the execution process to a Compound Timelock. This adds a delay, enforced by +the external timelock to all successful proposal (in addition to the voting duration). The [`Governor`](#Governor) needs to be +the admin of the timelock for any operation to be performed. A public, unrestricted, +[`GovernorTimelockCompound.__acceptAdmin`](#GovernorTimelockCompound-__acceptAdmin--) is available to accept ownership of the timelock. + +Using this model means the proposal will be operated by the [`TimelockController`](#TimelockController) and not by the [`Governor`](#Governor). Thus, +the assets and permissions must be attached to the [`TimelockController`](#TimelockController). Any asset sent to the [`Governor`](#Governor) will be +inaccessible. + +_Available since v4.3._ + +
+

Functions

+
+- [constructor(timelockAddress)](#GovernorTimelockCompound-constructor-contract-ICompoundTimelock-) +- [supportsInterface(interfaceId)](#GovernorTimelockCompound-supportsInterface-bytes4-) +- [state(proposalId)](#GovernorTimelockCompound-state-uint256-) +- [timelock()](#GovernorTimelockCompound-timelock--) +- [proposalEta(proposalId)](#GovernorTimelockCompound-proposalEta-uint256-) +- [queue(targets, values, calldatas, descriptionHash)](#GovernorTimelockCompound-queue-address---uint256---bytes---bytes32-) +- [_execute(proposalId, targets, values, calldatas, )](#GovernorTimelockCompound-_execute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32-) +- [_executor()](#GovernorTimelockCompound-_executor--) +- [__acceptAdmin()](#GovernorTimelockCompound-__acceptAdmin--) +- [updateTimelock(newTimelock)](#GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock-) +#### Governor +- [receive()](#Governor-receive--) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernorTimelock +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+- [TimelockChange(oldTimelock, newTimelock)](#GovernorTimelockCompound-TimelockChange-address-address-) +#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernorTimelock +- [ProposalQueued(proposalId, eta)](#IGovernorTimelock-ProposalQueued-uint256-uint256-) +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(contract ICompoundTimelock timelockAddress)

+
+

internal

+# +
+
+
+ +Set the timelock. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Overridden version of the [`Governor.state`](#Governor-state-uint256-) function with added support for the `Queued` and `Expired` state. + +
+
+ + + +
+
+

timelock() → address

+
+

public

+# +
+
+
+ +Public accessor to check the address of the timelock + +
+
+ + + +
+
+

proposalEta(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Public accessor to check the eta of a queued proposal + +
+
+ + + +
+
+

queue(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Function to queue a proposal to the timelock. + +
+
+ + + +
+
+

_execute(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32)

+
+

internal

+# +
+
+
+ +Overridden execute function that run the already queued proposal through the timelock. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Overridden version of the [`Governor._cancel`](#Governor-_cancel-address---uint256---bytes---bytes32-) function to cancel the timelocked proposal if it as already +been queued. + +
+
+ + + +
+
+

_executor() → address

+
+

internal

+# +
+
+
+ +Address through which the governor executes action. In this case, the timelock. + +
+
+ + + +
+
+

__acceptAdmin()

+
+

public

+# +
+
+
+ +Accept admin right over the timelock. + +
+
+ + + +
+
+

updateTimelock(contract ICompoundTimelock newTimelock)

+
+

external

+# +
+
+
+ +Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates +must be proposed, scheduled, and executed through governance proposals. + +For security reasons, the timelock must be handed over to another admin before setting up a new one. The two +operations (hand over the timelock) and do the update can be batched in a single proposal. + +Note that if the timelock admin has been handed over in a previous operation, we refuse updates made through the +timelock if admin of the timelock has already been accepted and the operation is executed outside the scope of +governance. + +CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + +
+
+ + + +
+
+

TimelockChange(address oldTimelock, address newTimelock)

+
+

event

+# +
+
+ +
+ +Emitted when the timelock controller used for proposal execution is modified. + +
+
+ + + +
+ +## `GovernorTimelockControl` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +``` + +Extension of [`Governor`](#Governor) that binds the execution process to an instance of [`TimelockController`](#TimelockController). This adds a +delay, enforced by the [`TimelockController`](#TimelockController) to all successful proposal (in addition to the voting duration). The +[`Governor`](#Governor) needs the proposer (and ideally the executor) roles for the [`Governor`](#Governor) to work properly. + +Using this model means the proposal will be operated by the [`TimelockController`](#TimelockController) and not by the [`Governor`](#Governor). Thus, +the assets and permissions must be attached to the [`TimelockController`](#TimelockController). Any asset sent to the [`Governor`](#Governor) will be +inaccessible. + + +Setting up the TimelockController to have additional proposers besides the governor is very risky, as it +grants them powers that they must be trusted or known not to use: 1) [`Governor.onlyGovernance`](#Governor-onlyGovernance--) functions like [`Governor.relay`](#Governor-relay-address-uint256-bytes-) are +available to them through the timelock, and 2) approved governance proposals can be blocked by them, effectively +executing a Denial of Service attack. This risk will be mitigated in a future release. + + +_Available since v4.3._ + +
+

Functions

+
+- [constructor(timelockAddress)](#GovernorTimelockControl-constructor-contract-TimelockController-) +- [supportsInterface(interfaceId)](#GovernorTimelockControl-supportsInterface-bytes4-) +- [state(proposalId)](#GovernorTimelockControl-state-uint256-) +- [timelock()](#GovernorTimelockControl-timelock--) +- [proposalEta(proposalId)](#GovernorTimelockControl-proposalEta-uint256-) +- [queue(targets, values, calldatas, descriptionHash)](#GovernorTimelockControl-queue-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, descriptionHash)](#GovernorTimelockControl-_execute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32-) +- [_executor()](#GovernorTimelockControl-_executor--) +- [updateTimelock(newTimelock)](#GovernorTimelockControl-updateTimelock-contract-TimelockController-) +#### Governor +- [receive()](#Governor-receive--) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernorTimelock +#### IGovernor +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+- [TimelockChange(oldTimelock, newTimelock)](#GovernorTimelockControl-TimelockChange-address-address-) +#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernorTimelock +- [ProposalQueued(proposalId, eta)](#IGovernorTimelock-ProposalQueued-uint256-uint256-) +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(contract TimelockController timelockAddress)

+
+

internal

+# +
+
+
+ +Set the timelock. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Overridden version of the [`Governor.state`](#Governor-state-uint256-) function with added support for the `Queued` state. + +
+
+ + + +
+
+

timelock() → address

+
+

public

+# +
+
+
+ +Public accessor to check the address of the timelock + +
+
+ + + +
+
+

proposalEta(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Public accessor to check the eta of a queued proposal + +
+
+ + + +
+
+

queue(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Function to queue a proposal to the timelock. + +
+
+ + + +
+
+

_execute(uint256, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash)

+
+

internal

+# +
+
+
+ +Overridden execute function that run the already queued proposal through the timelock. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Overridden version of the [`Governor._cancel`](#Governor-_cancel-address---uint256---bytes---bytes32-) function to cancel the timelocked proposal if it as already +been queued. + +
+
+ + + +
+
+

_executor() → address

+
+

internal

+# +
+
+
+ +Address through which the governor executes action. In this case, the timelock. + +
+
+ + + +
+
+

updateTimelock(contract TimelockController newTimelock)

+
+

external

+# +
+
+
+ +Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates +must be proposed, scheduled, and executed through governance proposals. + +CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + +
+
+ + + +
+
+

TimelockChange(address oldTimelock, address newTimelock)

+
+

event

+# +
+
+ +
+ +Emitted when the timelock controller used for proposal execution is modified. + +
+
+ + + +
+ +## `GovernorVotes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +``` + +Extension of [`Governor`](#Governor) for voting weight extraction from an [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes) token, or since v4.5 an [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes) token. + +_Available since v4.3._ + +
+

Functions

+
+- [constructor(tokenAddress)](#GovernorVotes-constructor-contract-IVotes-) +- [clock()](#GovernorVotes-clock--) +- [CLOCK_MODE()](#GovernorVotes-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotes-_getVotes-address-uint256-bytes-) +- [token()](#GovernorVotes-token-contract-IERC5805) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(contract IVotes tokenAddress)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token +does not implement EIP-6372. + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Machine-readable description of the clock as specified in EIP-6372. + +
+
+ + + +
+
+

_getVotes(address account, uint256 timepoint, bytes) → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

token() → contract IERC5805

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `GovernorVotesComp` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotesComp.sol"; +``` + +Extension of [`Governor`](#Governor) for voting weight extraction from a Comp token. + +_Available since v4.3._ + +
+

Functions

+
+- [constructor(token_)](#GovernorVotesComp-constructor-contract-ERC20VotesComp-) +- [clock()](#GovernorVotesComp-clock--) +- [CLOCK_MODE()](#GovernorVotesComp-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotesComp-_getVotes-address-uint256-bytes-) +- [token()](#GovernorVotesComp-token-contract-ERC20VotesComp) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(contract ERC20VotesComp token_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock (as specified in EIP-6372) is set to match the token's clock. Fallback to block numbers if the token +does not implement EIP-6372. + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Machine-readable description of the clock as specified in EIP-6372. + +
+
+ + + +
+
+

_getVotes(address account, uint256 timepoint, bytes) → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

token() → contract ERC20VotesComp

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `GovernorVotesQuorumFraction` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +``` + +Extension of [`Governor`](#Governor) for voting weight extraction from an [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes) token and a quorum expressed as a +fraction of the total supply. + +_Available since v4.3._ + +
+

Functions

+
+- [constructor(quorumNumeratorValue)](#GovernorVotesQuorumFraction-constructor-uint256-) +- [quorumNumerator()](#GovernorVotesQuorumFraction-quorumNumerator--) +- [quorumNumerator(timepoint)](#GovernorVotesQuorumFraction-quorumNumerator-uint256-) +- [quorumDenominator()](#GovernorVotesQuorumFraction-quorumDenominator--) +- [quorum(timepoint)](#GovernorVotesQuorumFraction-quorum-uint256-) +- [updateQuorumNumerator(newQuorumNumerator)](#GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-) +- [_updateQuorumNumerator(newQuorumNumerator)](#GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-) +#### GovernorVotes +- [clock()](#GovernorVotes-clock--) +- [CLOCK_MODE()](#GovernorVotes-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotes-_getVotes-address-uint256-bytes-) +- [token()](#GovernorVotes-token-contract-IERC5805) +#### Governor +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, weight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_execute(, targets, values, calldatas, )](#Governor-_execute-uint256-address---uint256---bytes---bytes32-) +- [_beforeExecute(, targets, , calldatas, )](#Governor-_beforeExecute-uint256-address---uint256---bytes---bytes32-) +- [_afterExecute(, , , , )](#Governor-_afterExecute-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#Governor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+- [QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator)](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) +#### GovernorVotes +#### Governor +#### IERC1155Receiver +#### IERC721Receiver +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(uint256 quorumNumeratorValue)

+
+

internal

+# +
+
+
+ +Initialize quorum as a fraction of the token's total supply. + +The fraction is specified as `numerator / denominator`. By default the denominator is 100, so quorum is +specified as a percent: a numerator of 10 corresponds to quorum being 10% of total supply. The denominator can be +customized by overriding [`GovernorVotesQuorumFraction.quorumDenominator`](#GovernorVotesQuorumFraction-quorumDenominator--). + +
+
+ + + +
+
+

quorumNumerator() → uint256

+
+

public

+# +
+
+
+ +Returns the current quorum numerator. See [`GovernorVotesQuorumFraction.quorumDenominator`](#GovernorVotesQuorumFraction-quorumDenominator--). + +
+
+ + + +
+
+

quorumNumerator(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the quorum numerator at a specific timepoint. See [`GovernorVotesQuorumFraction.quorumDenominator`](#GovernorVotesQuorumFraction-quorumDenominator--). + +
+
+ + + +
+
+

quorumDenominator() → uint256

+
+

public

+# +
+
+
+ +Returns the quorum denominator. Defaults to 100, but may be overridden. + +
+
+ + + +
+
+

quorum(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. + +
+
+ + + +
+
+

updateQuorumNumerator(uint256 newQuorumNumerator)

+
+

external

+# +
+
+
+ +Changes the quorum numerator. + +Emits a [`GovernorVotesQuorumFraction.QuorumNumeratorUpdated`](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) event. + +Requirements: + +- Must be called through a governance proposal. +- New numerator must be smaller or equal to the denominator. + +
+
+ + + +
+
+

_updateQuorumNumerator(uint256 newQuorumNumerator)

+
+

internal

+# +
+
+
+ +Changes the quorum numerator. + +Emits a [`GovernorVotesQuorumFraction.QuorumNumeratorUpdated`](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) event. + +Requirements: + +- New numerator must be smaller or equal to the denominator. + +
+
+ + + +
+
+

QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IGovernorTimelock` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/IGovernorTimelock.sol"; +``` + +Extension of the [`IGovernor`](#IGovernor) for timelock supporting modules. + +_Available since v4.3._ + +
+

Functions

+
+- [timelock()](#IGovernorTimelock-timelock--) +- [proposalEta(proposalId)](#IGovernorTimelock-proposalEta-uint256-) +- [queue(targets, values, calldatas, descriptionHash)](#IGovernorTimelock-queue-address---uint256---bytes---bytes32-) +#### IGovernor +- [name()](#IGovernor-name--) +- [version()](#IGovernor-version--) +- [clock()](#IGovernor-clock--) +- [CLOCK_MODE()](#IGovernor-CLOCK_MODE--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#IGovernor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#IGovernor-state-uint256-) +- [proposalSnapshot(proposalId)](#IGovernor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#IGovernor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#IGovernor-proposalProposer-uint256-) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [getVotes(account, timepoint)](#IGovernor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#IGovernor-getVotesWithParams-address-uint256-bytes-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +- [propose(targets, values, calldatas, description)](#IGovernor-propose-address---uint256---bytes---string-) +- [execute(targets, values, calldatas, descriptionHash)](#IGovernor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#IGovernor-cancel-address---uint256---bytes---bytes32-) +- [castVote(proposalId, support)](#IGovernor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#IGovernor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, v, r, s)](#IGovernor-castVoteBySig-uint256-uint8-uint8-bytes32-bytes32-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, reason, params, v, r, s)](#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-string-bytes-uint8-bytes32-bytes32-) +#### IERC6372 +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [ProposalQueued(proposalId, eta)](#IGovernorTimelock-ProposalQueued-uint256-uint256-) +#### IGovernor +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 +#### IERC165 +
+
+ + + +
+
+

timelock() → address

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

proposalEta(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

queue(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256 proposalId

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

ProposalQueued(uint256 proposalId, uint256 eta)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IVotes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/utils/IVotes.sol"; +``` + +Common interface for [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes), [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes), and other [`Votes`](#Votes)-enabled contracts. + +_Available since v4.5._ + +
+

Functions

+
+- [getVotes(account)](#IVotes-getVotes-address-) +- [getPastVotes(account, timepoint)](#IVotes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#IVotes-getPastTotalSupply-uint256-) +- [delegates(account)](#IVotes-delegates-address-) +- [delegate(delegatee)](#IVotes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +
+
+ +
+

Events

+
+- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousBalance, newBalance)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +
+
+ + + +
+
+

getVotes(address account) → uint256

+
+

external

+# +
+
+
+ +Returns the current amount of votes that `account` has. + +
+
+ + + +
+
+

getPastVotes(address account, uint256 timepoint) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +
+
+ + + +
+
+

getPastTotalSupply(uint256 timepoint) → uint256

+
+

external

+# +
+
+
+ +Returns the total supply of votes available at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. +Votes that have not been delegated are still part of total supply, even though they would not participate in a +vote. + +
+
+ + + +
+
+

delegates(address account) → address

+
+

external

+# +
+
+
+ +Returns the delegate that `account` has chosen. + +
+
+ + + +
+
+

delegate(address delegatee)

+
+

external

+# +
+
+
+ +Delegates votes from the sender to `delegatee`. + +
+
+ + + +
+
+

delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

+
+

external

+# +
+
+
+ +Delegates votes from signer to `delegatee`. + +
+
+ + + +
+
+

DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate)

+
+

event

+# +
+
+ +
+ +Emitted when an account changes their delegate. + +
+
+ + +
+
+

DelegateVotesChanged(address indexed delegate, uint256 previousBalance, uint256 newBalance)

+
+

event

+# +
+
+ +
+ +Emitted when a token transfer or delegate change results in changes to a delegate's number of votes. + +
+
+ + + +
+ +## `Votes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/utils/Votes.sol"; +``` + +This is a base abstract contract that tracks voting units, which are a measure of voting power that can be +transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of +"representative" that will pool delegated voting units from different accounts and can then use it to vote in +decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to +delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. + +This contract is often combined with a token contract such that voting units correspond to token units. For an +example, see [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes). + +The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed +at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the +cost of this history tracking optional. + +When using this module the derived contract must implement [`Votes._getVotingUnits`](#Votes-_getVotingUnits-address-) (for example, make it return +[`ERC721.balanceOf`](/contracts/4.x/api/token/ERC721#ERC721-balanceOf-address-)), and can use [`Votes._transferVotingUnits`](#Votes-_transferVotingUnits-address-address-uint256-) to track a change in the distribution of those units (in the +previous example, it would be included in [`ERC721._beforeTokenTransfer`](/contracts/4.x/api/token/ERC721#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-)). + +_Available since v4.5._ + +
+

Functions

+
+- [clock()](#Votes-clock--) +- [CLOCK_MODE()](#Votes-CLOCK_MODE--) +- [getVotes(account)](#Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#Votes-getPastTotalSupply-uint256-) +- [_getTotalSupply()](#Votes-_getTotalSupply--) +- [delegates(account)](#Votes-delegates-address-) +- [delegate(delegatee)](#Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#Votes-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#Votes-_transferVotingUnits-address-address-uint256-) +- [_useNonce(owner)](#Votes-_useNonce-address-) +- [nonces(owner)](#Votes-nonces-address-) +- [DOMAIN_SEPARATOR()](#Votes-DOMAIN_SEPARATOR--) +- [_getVotingUnits()](#Votes-_getVotingUnits-address-) +#### IERC5805 +#### IVotes +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +
+
+ +
+

Events

+
+#### IERC5805 +#### IVotes +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousBalance, newBalance)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based +checkpoints (and voting), in which case [`IGovernor.CLOCK_MODE`](#IGovernor-CLOCK_MODE--) should be overridden as well to match. + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Machine-readable description of the clock as specified in EIP-6372. + +
+
+ + + +
+
+

getVotes(address account) → uint256

+
+

public

+# +
+
+
+ +Returns the current amount of votes that `account` has. + +
+
+ + + +
+
+

getPastVotes(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

getPastTotalSupply(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the total supply of votes available at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. +Votes that have not been delegated are still part of total supply, even though they would not participate in a +vote. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

_getTotalSupply() → uint256

+
+

internal

+# +
+
+
+ +Returns the current total supply of votes. + +
+
+ + + +
+
+

delegates(address account) → address

+
+

public

+# +
+
+
+ +Returns the delegate that `account` has chosen. + +
+
+ + + +
+
+

delegate(address delegatee)

+
+

public

+# +
+
+
+ +Delegates votes from the sender to `delegatee`. + +
+
+ + + +
+
+

delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

+
+

public

+# +
+
+
+ +Delegates votes from signer to `delegatee`. + +
+
+ + + +
+
+

_delegate(address account, address delegatee)

+
+

internal

+# +
+
+
+ +Delegate all of `account`'s voting units to `delegatee`. + +Emits events [`IVotes.DelegateChanged`](#IVotes-DelegateChanged-address-address-address-) and [`IVotes.DelegateVotesChanged`](#IVotes-DelegateVotesChanged-address-uint256-uint256-). + +
+
+ + + +
+
+

_transferVotingUnits(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` +should be zero. Total supply of voting units will be adjusted with mints and burns. + +
+
+ + + +
+
+

_useNonce(address owner) → uint256 current

+
+

internal

+# +
+
+
+ +Consumes a nonce. + +Returns the current value and increments nonce. + +
+
+ + + +
+
+

nonces(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns an address nonce. + +
+
+ + + +
+
+

DOMAIN_SEPARATOR() → bytes32

+
+

external

+# +
+
+
+ +Returns the contract's [`EIP712`](/contracts/4.x/api/utils#EIP712) domain separator. + +
+
+ + + +
+
+

_getVotingUnits(address) → uint256

+
+

internal

+# +
+
+
+ +Must return the voting units held by an account. + +
+
diff --git a/docs/content/contracts/4.x/api/index.mdx b/docs/content/contracts/4.x/api/index.mdx new file mode 100644 index 00000000..f84a8f6c --- /dev/null +++ b/docs/content/contracts/4.x/api/index.mdx @@ -0,0 +1,32 @@ +--- +title: API Reference +--- + +This API reference is automatically generated from the OpenZeppelin Contracts repository. + +## Contract Categories + +### Access Control +- [Access Control](/contracts/4.x/api/access) - Role-based access control mechanisms +- [Ownable](/contracts/4.x/api/access#ownable) - Simple ownership access control + +### Tokens +- [ERC20](/contracts/4.x/api/token/ERC20) - Fungible token standard implementation +- [ERC721](/contracts/4.x/api/token/ERC721) - Non-fungible token standard implementation +- [ERC1155](/contracts/4.x/api/token/ERC1155) - Multi-token standard implementation + +### Utilities +- [Utils](/contracts/4.x/api/utils) - General utility functions and contracts + +### Governance +- [Governance](/contracts/4.x/api/governance) - On-chain governance systems + +### Proxy Patterns +- [Proxy](/contracts/4.x/api/proxy) - Upgradeable proxy patterns + +### Interfaces +- [Interfaces](/contracts/4.x/api/interfaces) - Standard interfaces + +--- + +*Generated from OpenZeppelin Contracts v$(cat ../temp-contracts/package.json | grep '"version"' | cut -d '"' -f 4)* diff --git a/docs/content/contracts/4.x/api/interfaces.mdx b/docs/content/contracts/4.x/api/interfaces.mdx new file mode 100644 index 00000000..1436ac97 --- /dev/null +++ b/docs/content/contracts/4.x/api/interfaces.mdx @@ -0,0 +1,1541 @@ +--- +title: "Interfaces" +description: "Smart contract interfaces utilities and implementations" +--- + +## List of standardized interfaces +These interfaces are available as `.sol` files, and also as compiler `.json` ABI files (through the npm package). These +are useful to interact with third party contracts that implement them. + +* [`IERC20`](/contracts/4.x/api/token/ERC20#IERC20) +* [`IERC20Metadata`](/contracts/4.x/api/token/ERC20#IERC20Metadata) +* [`IERC165`](/contracts/4.x/api/utils#IERC165) +* [`IERC721`](/contracts/4.x/api/token/ERC721#IERC721) +* [`IERC721Receiver`](/contracts/4.x/api/token/ERC721#IERC721Receiver) +* [`IERC721Enumerable`](/contracts/4.x/api/token/ERC721#IERC721Enumerable) +* [`IERC721Metadata`](/contracts/4.x/api/token/ERC721#IERC721Metadata) +* [`IERC777`](/contracts/4.x/api/token/ERC777#IERC777) +* [`IERC777Recipient`](/contracts/4.x/api/token/ERC777#IERC777Recipient) +* [`IERC777Sender`](/contracts/4.x/api/token/ERC777#IERC777Sender) +* [`IERC1155`](/contracts/4.x/api/token/ERC1155#IERC1155) +* [`IERC1155Receiver`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver) +* [`IERC1155MetadataURI`](/contracts/4.x/api/token/ERC1155#IERC1155MetadataURI) +* [`IERC1271`](#IERC1271) +* [`IERC1363`](#IERC1363) +* [`IERC1363Receiver`](#IERC1363Receiver) +* [`IERC1363Spender`](#IERC1363Spender) +* [`IERC1820Implementer`](/contracts/4.x/api/utils#IERC1820Implementer) +* [`IERC1820Registry`](/contracts/4.x/api/utils#IERC1820Registry) +* [`IERC1822Proxiable`](#IERC1822Proxiable) +* [`IERC2612`](#IERC2612) +* [`IERC2981`](#IERC2981) +* [`IERC3156FlashLender`](#IERC3156FlashLender) +* [`IERC3156FlashBorrower`](#IERC3156FlashBorrower) +* [`IERC4626`](#IERC4626) +* [`IERC4906`](#IERC4906) +* [`IERC5267`](#IERC5267) +* [`IERC5313`](#IERC5313) +* [`IERC5805`](#IERC5805) +* [`IERC6372`](#IERC6372) + +## Detailed ABI + +[`IERC1271`](#IERC1271) + +[`IERC1363`](#IERC1363) + +[`IERC1363Receiver`](#IERC1363Receiver) + +[`IERC1363Spender`](#IERC1363Spender) + +[`IERC1820Implementer`](/contracts/4.x/api/utils#IERC1820Implementer) + +[`IERC1820Registry`](/contracts/4.x/api/utils#IERC1820Registry) + +[`IERC1822Proxiable`](#IERC1822Proxiable) + +[`IERC2612`](#IERC2612) + +[`IERC2981`](#IERC2981) + +[`IERC3156FlashLender`](#IERC3156FlashLender) + +[`IERC3156FlashBorrower`](#IERC3156FlashBorrower) + +[`IERC4626`](#IERC4626) + +[`IERC5313`](#IERC5313) + +[`IERC5267`](#IERC5267) + +[`IERC5805`](#IERC5805) + +[`IERC6372`](#IERC6372) + + + +
+ +## `IERC1271` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1271.sol"; +``` + +Interface of the ERC1271 standard signature validation method for +contracts as defined in [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271). + +_Available since v4.1._ + +
+

Functions

+
+- [isValidSignature(hash, signature)](#IERC1271-isValidSignature-bytes32-bytes-) +
+
+ + + +
+
+

isValidSignature(bytes32 hash, bytes signature) → bytes4 magicValue

+
+

external

+# +
+
+
+ +Should return whether the signature provided is valid for the provided data + +
+
+ + + +
+ +## `IERC1363` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363.sol"; +``` + +Interface of an ERC1363 compliant contract, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1363). + +Defines a interface for ERC20 tokens that supports executing recipient +code after `transfer` or `transferFrom`, or spender code after `approve`. + +
+

Functions

+
+- [transferAndCall(to, amount)](#IERC1363-transferAndCall-address-uint256-) +- [transferAndCall(to, amount, data)](#IERC1363-transferAndCall-address-uint256-bytes-) +- [transferFromAndCall(from, to, amount)](#IERC1363-transferFromAndCall-address-address-uint256-) +- [transferFromAndCall(from, to, amount, data)](#IERC1363-transferFromAndCall-address-address-uint256-bytes-) +- [approveAndCall(spender, amount)](#IERC1363-approveAndCall-address-uint256-) +- [approveAndCall(spender, amount, data)](#IERC1363-approveAndCall-address-uint256-bytes-) +#### IERC20 +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, amount)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, amount)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#IERC20-transferFrom-address-address-uint256-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +#### IERC165 +
+
+ + + +
+
+

transferAndCall(address to, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + +
+
+ + + +
+
+

transferAndCall(address to, uint256 amount, bytes data) → bool

+
+

external

+# +
+
+
+ +Transfer tokens from `msg.sender` to another address and then call `onTransferReceived` on receiver + +
+
+ + + +
+
+

transferFromAndCall(address from, address to, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Transfer tokens from one address to another and then call `onTransferReceived` on receiver + +
+
+ + + +
+
+

transferFromAndCall(address from, address to, uint256 amount, bytes data) → bool

+
+

external

+# +
+
+
+ +Transfer tokens from one address to another and then call `onTransferReceived` on receiver + +
+
+ + + +
+
+

approveAndCall(address spender, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Approve the passed address to spend the specified amount of tokens on behalf of msg.sender +and then call `onApprovalReceived` on spender. + +
+
+ + + +
+
+

approveAndCall(address spender, uint256 amount, bytes data) → bool

+
+

external

+# +
+
+
+ +Approve the passed address to spend the specified amount of tokens on behalf of msg.sender +and then call `onApprovalReceived` on spender. + +
+
+ + + +
+ +## `IERC1363Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; +``` + +Interface for any contract that wants to support [`IERC1363.transferAndCall`](#IERC1363-transferAndCall-address-uint256-bytes-) +or [`IERC1363.transferFromAndCall`](#IERC1363-transferFromAndCall-address-address-uint256-bytes-) from [`IERC1363`](#IERC1363) token contracts. + +
+

Functions

+
+- [onTransferReceived(operator, from, amount, data)](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) +
+
+ + + +
+
+

onTransferReceived(address operator, address from, uint256 amount, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Any ERC1363 smart contract calls this function on the recipient +after a `transfer` or a `transferFrom`. This function MAY throw to revert and reject the +transfer. Return of other than the magic value MUST result in the +transaction being reverted. +Note: the token contract address is always the message sender. + +
+
+ + + +
+ +## `IERC1363Spender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; +``` + +Interface for any contract that wants to support [`IERC1363.approveAndCall`](#IERC1363-approveAndCall-address-uint256-bytes-) +from [`IERC1363`](#IERC1363) token contracts. + +
+

Functions

+
+- [onApprovalReceived(owner, amount, data)](#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) +
+
+ + + +
+
+

onApprovalReceived(address owner, uint256 amount, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Any ERC1363 smart contract calls this function on the recipient +after an `approve`. This function MAY throw to revert and reject the +approval. Return of other than the magic value MUST result in the +transaction being reverted. +Note: the token contract address is always the message sender. + +
+
+ + + +
+ +## `IERC1967` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1967.sol"; +``` + +ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + +_Available since v4.8.3._ + +
+

Events

+
+- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +
+
+ + + +
+
+

Upgraded(address indexed implementation)

+
+

event

+# +
+
+ +
+ +Emitted when the implementation is upgraded. + +
+
+ + +
+
+

AdminChanged(address previousAdmin, address newAdmin)

+
+

event

+# +
+
+ +
+ +Emitted when the admin account has changed. + +
+
+ + +
+
+

BeaconUpgraded(address indexed beacon)

+
+

event

+# +
+
+ +
+ +Emitted when the beacon is changed. + +
+
+ + + +
+ +## `IERC2309` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC2309.sol"; +``` + +ERC-2309: ERC-721 Consecutive Transfer Extension. + +_Available since v4.8._ + +
+

Events

+
+- [ConsecutiveTransfer(fromTokenId, toTokenId, fromAddress, toAddress)](#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-) +
+
+ + + +
+
+

ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed fromAddress, address indexed toAddress)

+
+

event

+# +
+
+ +
+ +Emitted when the tokens from `fromTokenId` to `toTokenId` are transferred from `fromAddress` to `toAddress`. + +
+
+ + + +
+ +## `IERC2612` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC2612.sol"; +``` + +
+

Functions

+
+#### IERC20Permit +- [permit(owner, spender, value, deadline, v, r, s)](#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#IERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#IERC20Permit-DOMAIN_SEPARATOR--) +
+
+ + + +
+ +## `IERC2981` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC2981.sol"; +``` + +Interface for the NFT Royalty Standard. + +A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal +support for royalty payments across all NFT marketplaces and ecosystem participants. + +_Available since v4.5._ + +
+

Functions

+
+- [royaltyInfo(tokenId, salePrice)](#IERC2981-royaltyInfo-uint256-uint256-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ + + +
+
+

royaltyInfo(uint256 tokenId, uint256 salePrice) → address receiver, uint256 royaltyAmount

+
+

external

+# +
+
+
+ +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of +exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + +
+
+ + + +
+ +## `IERC3156FlashBorrower` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; +``` + +Interface of the ERC3156 FlashBorrower, as defined in +[ERC-3156](https://eips.ethereum.org/EIPS/eip-3156). + +_Available since v4.1._ + +
+

Functions

+
+- [onFlashLoan(initiator, token, amount, fee, data)](#IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-) +
+
+ + + +
+
+

onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes data) → bytes32

+
+

external

+# +
+
+
+ +Receive a flash loan. + +
+
+ + + +
+ +## `IERC3156FlashLender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol"; +``` + +Interface of the ERC3156 FlashLender, as defined in +[ERC-3156](https://eips.ethereum.org/EIPS/eip-3156). + +_Available since v4.1._ + +
+

Functions

+
+- [maxFlashLoan(token)](#IERC3156FlashLender-maxFlashLoan-address-) +- [flashFee(token, amount)](#IERC3156FlashLender-flashFee-address-uint256-) +- [flashLoan(receiver, token, amount, data)](#IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-) +
+
+ + + +
+
+

maxFlashLoan(address token) → uint256

+
+

external

+# +
+
+
+ +The amount of currency available to be lended. + +
+
+ + + +
+
+

flashFee(address token, uint256 amount) → uint256

+
+

external

+# +
+
+
+ +The fee to be charged for a given loan. + +
+
+ + + +
+
+

flashLoan(contract IERC3156FlashBorrower receiver, address token, uint256 amount, bytes data) → bool

+
+

external

+# +
+
+
+ +Initiate a flash loan. + +
+
+ + + +
+ +## `IERC4626` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC4626.sol"; +``` + +Interface of the ERC4626 "Tokenized Vault Standard", as defined in +[ERC-4626](https://eips.ethereum.org/EIPS/eip-4626). + +_Available since v4.7._ + +
+

Functions

+
+- [asset()](#IERC4626-asset--) +- [totalAssets()](#IERC4626-totalAssets--) +- [convertToShares(assets)](#IERC4626-convertToShares-uint256-) +- [convertToAssets(shares)](#IERC4626-convertToAssets-uint256-) +- [maxDeposit(receiver)](#IERC4626-maxDeposit-address-) +- [previewDeposit(assets)](#IERC4626-previewDeposit-uint256-) +- [deposit(assets, receiver)](#IERC4626-deposit-uint256-address-) +- [maxMint(receiver)](#IERC4626-maxMint-address-) +- [previewMint(shares)](#IERC4626-previewMint-uint256-) +- [mint(shares, receiver)](#IERC4626-mint-uint256-address-) +- [maxWithdraw(owner)](#IERC4626-maxWithdraw-address-) +- [previewWithdraw(assets)](#IERC4626-previewWithdraw-uint256-) +- [withdraw(assets, receiver, owner)](#IERC4626-withdraw-uint256-address-address-) +- [maxRedeem(owner)](#IERC4626-maxRedeem-address-) +- [previewRedeem(shares)](#IERC4626-previewRedeem-uint256-) +- [redeem(shares, receiver, owner)](#IERC4626-redeem-uint256-address-address-) +#### IERC20Metadata +- [name()](#IERC20Metadata-name--) +- [symbol()](#IERC20Metadata-symbol--) +- [decimals()](#IERC20Metadata-decimals--) +#### IERC20 +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, amount)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, amount)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+- [Deposit(sender, owner, assets, shares)](#IERC4626-Deposit-address-address-uint256-uint256-) +- [Withdraw(sender, receiver, owner, assets, shares)](#IERC4626-Withdraw-address-address-address-uint256-uint256-) +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

asset() → address assetTokenAddress

+
+

external

+# +
+
+
+ +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + +- MUST be an ERC-20 token contract. +- MUST NOT revert. + +
+
+ + + +
+
+

totalAssets() → uint256 totalManagedAssets

+
+

external

+# +
+
+
+ +Returns the total amount of the underlying asset that is “managed” by Vault. + +- SHOULD include any compounding that occurs from yield. +- MUST be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT revert. + +
+
+ + + +
+
+

convertToShares(uint256 assets) → uint256 shares

+
+

external

+# +
+
+
+ +Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + +NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the +“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + +
+
+ + + +
+
+

convertToAssets(uint256 shares) → uint256 assets

+
+

external

+# +
+
+
+ +Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + +NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the +“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + +
+
+ + + +
+
+

maxDeposit(address receiver) → uint256 maxAssets

+
+

external

+# +
+
+
+ +Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, +through a deposit call. + +- MUST return a limited value if receiver is subject to some deposit limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. +- MUST NOT revert. + +
+
+ + + +
+
+

previewDeposit(uint256 assets) → uint256 shares

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given +current on-chain conditions. + +- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + in the same transaction. +- MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + deposit would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + +
+
+ + + +
+
+

deposit(uint256 assets, address receiver) → uint256 shares

+
+

external

+# +
+
+
+ +Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + deposit execution, and are accounted for during deposit. +- MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + +NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + +
+
+ + + +
+
+

maxMint(address receiver) → uint256 maxShares

+
+

external

+# +
+
+
+ +Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. +- MUST return a limited value if receiver is subject to some mint limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. +- MUST NOT revert. + +
+
+ + + +
+
+

previewMint(uint256 shares) → uint256 assets

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given +current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + same transaction. +- MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by minting. + +
+
+ + + +
+
+

mint(uint256 shares, address receiver) → uint256 assets

+
+

external

+# +
+
+
+ +Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + execution, and are accounted for during mint. +- MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + +NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + +
+
+ + + +
+
+

maxWithdraw(address owner) → uint256 maxAssets

+
+

external

+# +
+
+
+ +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the +Vault, through a withdraw call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST NOT revert. + +
+
+ + + +
+
+

previewWithdraw(uint256 assets) → uint256 shares

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, +given current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + called + in the same transaction. +- MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + the withdrawal would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + +
+
+ + + +
+
+

withdraw(uint256 assets, address receiver, address owner) → uint256 shares

+
+

external

+# +
+
+
+ +Burns shares from owner and sends exactly assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + withdraw execution, and are accounted for during withdraw. +- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + +Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + +
+
+ + + +
+
+

maxRedeem(address owner) → uint256 maxShares

+
+

external

+# +
+
+
+ +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, +through a redeem call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. +- MUST NOT revert. + +
+
+ + + +
+
+

previewRedeem(uint256 shares) → uint256 assets

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, +given current on-chain conditions. + +- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + same transaction. +- MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + redemption would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + +NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by redeeming. + +
+
+ + + +
+
+

redeem(uint256 shares, address receiver, address owner) → uint256 assets

+
+

external

+# +
+
+
+ +Burns exactly shares from owner and sends assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + redeem execution, and are accounted for during redeem. +- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + +NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + +
+
+ + + +
+
+

Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC4906` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC4906.sol"; +``` + +
+

Functions

+
+#### IERC721 +- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [MetadataUpdate(_tokenId)](#IERC4906-MetadataUpdate-uint256-) +- [BatchMetadataUpdate(_fromTokenId, _toTokenId)](#IERC4906-BatchMetadataUpdate-uint256-uint256-) +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 +
+
+ + + +
+
+

MetadataUpdate(uint256 _tokenId)

+
+

event

+# +
+
+ +
+ +This event emits when the metadata of a token is changed. +So that the third-party platforms such as NFT market could +timely update the images and related attributes of the NFT. + +
+
+ + +
+
+

BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId)

+
+

event

+# +
+
+ +
+ +This event emits when the metadata of a range of tokens is changed. +So that the third-party platforms such as NFT market could +timely update the images and related attributes of the NFTs. + +
+
+ + + +
+ +## `IERC5267` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC5267.sol"; +``` + +
+

Functions

+
+- [eip712Domain()](#IERC5267-eip712Domain--) +
+
+ +
+

Events

+
+- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ + + +
+
+

eip712Domain() → bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions

+
+

external

+# +
+
+
+ +returns the fields and values that describe the domain separator used by this contract for EIP-712 +signature. + +
+
+ + + +
+
+

EIP712DomainChanged()

+
+

event

+# +
+
+ +
+ +MAY be emitted to signal that the domain could have changed. + +
+
+ + + +
+ +## `IERC5313` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC5313.sol"; +``` + +Interface for the Light Contract Ownership Standard. + +A standardized minimal interface required to identify an account that controls a contract + +_Available since v4.9._ + +
+

Functions

+
+- [owner()](#IERC5313-owner--) +
+
+ + + +
+
+

owner() → address

+
+

external

+# +
+
+
+ +Gets the address of the owner. + +
+
+ + + +
+ +## `IERC5805` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC5805.sol"; +``` + +
+

Functions

+
+#### IVotes +- [getVotes(account)](#IVotes-getVotes-address-) +- [getPastVotes(account, timepoint)](#IVotes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#IVotes-getPastTotalSupply-uint256-) +- [delegates(account)](#IVotes-delegates-address-) +- [delegate(delegatee)](#IVotes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +#### IERC6372 +- [clock()](#IERC6372-clock--) +- [CLOCK_MODE()](#IERC6372-CLOCK_MODE--) +
+
+ +
+

Events

+
+#### IVotes +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousBalance, newBalance)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 +
+
+ + + +
+ +## `IERC6372` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC6372.sol"; +``` + +
+

Functions

+
+- [clock()](#IERC6372-clock--) +- [CLOCK_MODE()](#IERC6372-CLOCK_MODE--) +
+
+ + + +
+
+

clock() → uint48

+
+

external

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

external

+# +
+
+
+ +Description of the clock + +
+
+ + + +
+ +## `IERC1822Proxiable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; +``` + +ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified +proxy whose upgrades are fully controlled by the current implementation. + +
+

Functions

+
+- [proxiableUUID()](#IERC1822Proxiable-proxiableUUID--) +
+
+ + + +
+
+

proxiableUUID() → bytes32

+
+

external

+# +
+
+
+ +Returns the storage slot that the proxiable contract assumes is being used to store the implementation +address. + + +A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks +bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this +function revert if invoked through a proxy. + + +
+
diff --git a/docs/content/contracts/4.x/api/metatx.mdx b/docs/content/contracts/4.x/api/metatx.mdx new file mode 100644 index 00000000..3d9c8e56 --- /dev/null +++ b/docs/content/contracts/4.x/api/metatx.mdx @@ -0,0 +1,232 @@ +--- +title: "Metatx" +description: "Smart contract metatx utilities and implementations" +--- + +## Core + +[`ERC2771Context`](#ERC2771Context) + +## Utils + +[`MinimalForwarder`](#MinimalForwarder) + + + +
+ +## `ERC2771Context` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +``` + +Context variant with ERC2771 support. + + +The usage of `delegatecall` in this contract is dangerous and may result in context corruption. +Any forwarded request to this contract triggering a `delegatecall` to itself will result in an invalid [`ERC2771Context._msgSender`](#ERC2771Context-_msgSender--) +recovery. + + +
+

Functions

+
+- [constructor(trustedForwarder)](#ERC2771Context-constructor-address-) +- [isTrustedForwarder(forwarder)](#ERC2771Context-isTrustedForwarder-address-) +- [_msgSender()](#ERC2771Context-_msgSender--) +- [_msgData()](#ERC2771Context-_msgData--) +- [_contextSuffixLength()](#ERC2771Context-_contextSuffixLength--) +
+
+ + + +
+
+

constructor(address trustedForwarder)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isTrustedForwarder(address forwarder) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_msgSender() → address

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_msgData() → bytes

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_contextSuffixLength() → uint256

+
+

internal

+# +
+
+
+ +ERC-2771 specifies the context as being a single address (20 bytes). + +
+
+ + + +
+ +## `MinimalForwarder` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/metatx/MinimalForwarder.sol"; +``` + +Simple minimal forwarder to be used together with an ERC2771 compatible contract. See [`ERC2771Context`](#ERC2771Context). + +MinimalForwarder is mainly meant for testing, as it is missing features to be a good production-ready forwarder. This +contract does not intend to have all the properties that are needed for a sound forwarding system. A fully +functioning forwarding system with good properties requires more complexity. We suggest you look at other projects +such as the GSN which do have the goal of building a system like that. + +
+

Functions

+
+- [constructor()](#MinimalForwarder-constructor--) +- [getNonce(from)](#MinimalForwarder-getNonce-address-) +- [verify(req, signature)](#MinimalForwarder-verify-struct-MinimalForwarder-ForwardRequest-bytes-) +- [execute(req, signature)](#MinimalForwarder-execute-struct-MinimalForwarder-ForwardRequest-bytes-) +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +
+
+ +
+

Events

+
+#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ + + +
+
+

constructor()

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

getNonce(address from) → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

verify(struct MinimalForwarder.ForwardRequest req, bytes signature) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

execute(struct MinimalForwarder.ForwardRequest req, bytes signature) → bool, bytes

+
+

public

+# +
+
+
+ +
+
+ diff --git a/docs/content/contracts/4.x/api/proxy.mdx b/docs/content/contracts/4.x/api/proxy.mdx new file mode 100644 index 00000000..32da261a --- /dev/null +++ b/docs/content/contracts/4.x/api/proxy.mdx @@ -0,0 +1,1827 @@ +--- +title: "Proxy" +description: "Smart contract proxy utilities and implementations" +--- + +This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the [Proxy Upgrade Pattern](/upgrades-plugins/proxies) page. + +Most of the proxies below are built on an abstract base contract. + +* [`Proxy`](#Proxy): Abstract contract implementing the core delegation functionality. + +In order to avoid clashes with the storage variables of the implementation contract behind a proxy, we use [EIP1967](https://eips.ethereum.org/EIPS/eip-1967) storage slots. + +* [`ERC1967Upgrade`](#ERC1967Upgrade): Internal functions to get and set the storage slots defined in EIP1967. +* [`ERC1967Proxy`](#ERC1967Proxy): A proxy using EIP1967 storage slots. Not upgradeable by default. + +There are two alternative ways to add upgradeability to an ERC1967 proxy. Their differences are explained below in [Transparent vs UUPS Proxies](#transparent-vs-uups-proxies). + +* [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy): A proxy with a built in admin and upgrade interface. +* [`UUPSUpgradeable`](#UUPSUpgradeable): An upgradeability mechanism to be included in the implementation contract. + +**🔥 CAUTION**\ +Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the [OpenZeppelin Upgrades Plugins](/upgrades-plugins) for Truffle and Hardhat. + +A different family of proxies are beacon proxies. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. + +* [`BeaconProxy`](#BeaconProxy): A proxy that retrieves its implementation from a beacon contract. +* [`UpgradeableBeacon`](#UpgradeableBeacon): A beacon contract with a built in admin that can upgrade the [`BeaconProxy`](#BeaconProxy) pointing to it. + +In this pattern, the proxy contract doesn’t hold the implementation address in storage like an ERC1967 proxy. Instead, the address is stored in a separate beacon contract. The `upgrade` operations are sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. + +Outside the realm of upgradeability, proxies can also be useful to make cheap contract clones, such as those created by an on-chain factory contract that creates many instances of the same contract. These instances are designed to be both cheap to deploy, and cheap to call. + +* [`Clones`](#Clones): A library that can deploy cheap minimal non-upgradeable proxies. + +## Transparent vs UUPS Proxies + +The original proxies included in OpenZeppelin followed the [Transparent Proxy Pattern](https://blog.openzeppelin.com/the-transparent-proxy-pattern/). While this pattern is still provided, our recommendation is now shifting towards UUPS proxies, which are both lightweight and versatile. The name UUPS comes from [EIP1822](https://eips.ethereum.org/EIPS/eip-1822), which first documented the pattern. + +While both of these share the same interface for upgrades, in UUPS proxies the upgrade is handled by the implementation, and can eventually be removed. Transparent proxies, on the other hand, include the upgrade and admin logic in the proxy itself. This means [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) is more expensive to deploy than what is possible with UUPS proxies. + +UUPS proxies are implemented using an [`ERC1967Proxy`](#ERC1967Proxy). Note that this proxy is not by itself upgradeable. It is the role of the implementation to include, alongside the contract’s logic, all the code necessary to update the implementation’s address that is stored at a specific slot in the proxy’s storage space. This is where the [`UUPSUpgradeable`](#UUPSUpgradeable) contract comes in. Inheriting from it (and overriding the [`_authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-) function with the relevant access control mechanism) will turn your contract into a UUPS compliant implementation. + +Note that since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) might allow non-admins to perform upgrade operations. + +By default, the upgrade functionality included in [`UUPSUpgradeable`](#UUPSUpgradeable) contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn’t contain the necessary upgrade mechanism, as it would lock the upgradeability of the proxy forever. This security mechanism can be bypassed by either of: + +* Adding a flag mechanism in the implementation that will disable the upgrade function when triggered. +* Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrading again to another implementation without the upgrade mechanism. + +The current implementation of this security mechanism uses [EIP1822](https://eips.ethereum.org/EIPS/eip-1822) to detect the storage slot used by the implementation. A previous implementation, now deprecated, relied on a rollback check. It is possible to upgrade from a contract using the old mechanism to a new one. The inverse is however not possible, as old implementations (before version 4.5) did not include the `ERC1822` interface. + +## Core + +[`Proxy`](#Proxy) + +## ERC1967 + +[`IERC1967`](/contracts/4.x/api/interfaces#IERC1967) + +[`ERC1967Proxy`](#ERC1967Proxy) + +[`ERC1967Upgrade`](#ERC1967Upgrade) + +## Transparent Proxy + +[`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) + +[`ProxyAdmin`](#ProxyAdmin) + +## Beacon + +[`BeaconProxy`](#BeaconProxy) + +[`IBeacon`](#IBeacon) + +[`UpgradeableBeacon`](#UpgradeableBeacon) + +## Minimal Clones + +[`Clones`](#Clones) + +## Utils + +[`Initializable`](#Initializable) + +[`UUPSUpgradeable`](#UUPSUpgradeable) + + + +
+ +## `Clones` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/Clones.sol"; +``` + +[EIP 1167](https://eips.ethereum.org/EIPS/eip-1167) is a standard for +deploying minimal proxy contracts, also known as "clones". + +> To simply and cheaply clone contract functionality in an immutable way, this standard specifies +> a minimal bytecode implementation that delegates all calls to a known, fixed address. + +The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` +(salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the +deterministic method. + +_Available since v3.4._ + +
+

Functions

+
+- [clone(implementation)](#Clones-clone-address-) +- [cloneDeterministic(implementation, salt)](#Clones-cloneDeterministic-address-bytes32-) +- [predictDeterministicAddress(implementation, salt, deployer)](#Clones-predictDeterministicAddress-address-bytes32-address-) +- [predictDeterministicAddress(implementation, salt)](#Clones-predictDeterministicAddress-address-bytes32-) +
+
+ + + +
+
+

clone(address implementation) → address instance

+
+

internal

+# +
+
+
+ +Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + +This function uses the create opcode, which should never revert. + +
+
+ + + +
+
+

cloneDeterministic(address implementation, bytes32 salt) → address instance

+
+

internal

+# +
+
+
+ +Deploys and returns the address of a clone that mimics the behaviour of `implementation`. + +This function uses the create2 opcode and a `salt` to deterministically deploy +the clone. Using the same `implementation` and `salt` multiple time will revert, since +the clones cannot be deployed twice at the same address. + +
+
+ + + +
+
+

predictDeterministicAddress(address implementation, bytes32 salt, address deployer) → address predicted

+
+

internal

+# +
+
+
+ +Computes the address of a clone deployed using [`Clones.cloneDeterministic`](#Clones-cloneDeterministic-address-bytes32-). + +
+
+ + + +
+
+

predictDeterministicAddress(address implementation, bytes32 salt) → address predicted

+
+

internal

+# +
+
+
+ +Computes the address of a clone deployed using [`Clones.cloneDeterministic`](#Clones-cloneDeterministic-address-bytes32-). + +
+
+ + + +
+ +## `ERC1967Proxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +``` + +This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an +implementation address that can be changed. This address is stored in storage in the location specified by +[EIP1967](https://eips.ethereum.org/EIPS/eip-1967), so that it doesn't conflict with the storage layout of the +implementation behind the proxy. + +
+

Functions

+
+- [constructor(_logic, _data)](#ERC1967Proxy-constructor-address-bytes-) +- [_implementation()](#ERC1967Proxy-_implementation--) +#### ERC1967Upgrade +- [_getImplementation()](#ERC1967Upgrade-_getImplementation--) +- [_upgradeTo(newImplementation)](#ERC1967Upgrade-_upgradeTo-address-) +- [_upgradeToAndCall(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCall-address-bytes-bool-) +- [_upgradeToAndCallUUPS(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCallUUPS-address-bytes-bool-) +- [_getAdmin()](#ERC1967Upgrade-_getAdmin--) +- [_changeAdmin(newAdmin)](#ERC1967Upgrade-_changeAdmin-address-) +- [_getBeacon()](#ERC1967Upgrade-_getBeacon--) +- [_upgradeBeaconToAndCall(newBeacon, data, forceCall)](#ERC1967Upgrade-_upgradeBeaconToAndCall-address-bytes-bool-) +#### IERC1967 +#### Proxy +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +- [receive()](#Proxy-receive--) +- [_beforeFallback()](#Proxy-_beforeFallback--) +
+
+ +
+

Events

+
+#### ERC1967Upgrade +#### IERC1967 +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +#### Proxy +
+
+ + + +
+
+

constructor(address _logic, bytes _data)

+
+

public

+# +
+
+
+ +Initializes the upgradeable proxy with an initial implementation specified by `_logic`. + +If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded +function call, and allows initializing the storage of the proxy like a Solidity constructor. + +
+
+ + + +
+
+

_implementation() → address impl

+
+

internal

+# +
+
+
+ +Returns the current implementation address. + +
+
+ + + +
+ +## `ERC1967Upgrade` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; +``` + +This abstract contract provides getters and event emitting update functions for +[EIP1967](https://eips.ethereum.org/EIPS/eip-1967) slots. + +_Available since v4.1._ + +
+

Functions

+
+- [_getImplementation()](#ERC1967Upgrade-_getImplementation--) +- [_upgradeTo(newImplementation)](#ERC1967Upgrade-_upgradeTo-address-) +- [_upgradeToAndCall(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCall-address-bytes-bool-) +- [_upgradeToAndCallUUPS(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCallUUPS-address-bytes-bool-) +- [_getAdmin()](#ERC1967Upgrade-_getAdmin--) +- [_changeAdmin(newAdmin)](#ERC1967Upgrade-_changeAdmin-address-) +- [_getBeacon()](#ERC1967Upgrade-_getBeacon--) +- [_upgradeBeaconToAndCall(newBeacon, data, forceCall)](#ERC1967Upgrade-_upgradeBeaconToAndCall-address-bytes-bool-) +#### IERC1967 +
+
+ +
+

Events

+
+#### IERC1967 +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +
+
+ + + +
+
+

_getImplementation() → address

+
+

internal

+# +
+
+
+ +Returns the current implementation address. + +
+
+ + + +
+
+

_upgradeTo(address newImplementation)

+
+

internal

+# +
+
+
+ +Perform implementation upgrade + +Emits an [`IERC1967.Upgraded`](/contracts/4.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

_upgradeToAndCall(address newImplementation, bytes data, bool forceCall)

+
+

internal

+# +
+
+
+ +Perform implementation upgrade with additional setup call. + +Emits an [`IERC1967.Upgraded`](/contracts/4.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

_upgradeToAndCallUUPS(address newImplementation, bytes data, bool forceCall)

+
+

internal

+# +
+
+
+ +Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. + +Emits an [`IERC1967.Upgraded`](/contracts/4.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

_getAdmin() → address

+
+

internal

+# +
+
+
+ +Returns the current admin. + +
+
+ + + +
+
+

_changeAdmin(address newAdmin)

+
+

internal

+# +
+
+
+ +Changes the admin of the proxy. + +Emits an [`IERC1967.AdminChanged`](/contracts/4.x/api/interfaces#IERC1967-AdminChanged-address-address-) event. + +
+
+ + + +
+
+

_getBeacon() → address

+
+

internal

+# +
+
+
+ +Returns the current beacon. + +
+
+ + + +
+
+

_upgradeBeaconToAndCall(address newBeacon, bytes data, bool forceCall)

+
+

internal

+# +
+
+
+ +Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does +not upgrade the implementation contained in the beacon (see [`UpgradeableBeacon._setImplementation`](#UpgradeableBeacon-_setImplementation-address-) for that). + +Emits a [`IERC1967.BeaconUpgraded`](/contracts/4.x/api/interfaces#IERC1967-BeaconUpgraded-address-) event. + +
+
+ + + +
+ +## `Proxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/Proxy.sol"; +``` + +This abstract contract provides a fallback function that delegates all calls to another contract using the EVM +instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to +be specified by overriding the virtual [`ERC1967Proxy._implementation`](#ERC1967Proxy-_implementation--) function. + +Additionally, delegation to the implementation can be triggered manually through the [`Proxy._fallback`](#Proxy-_fallback--) function, or to a +different contract through the [`Votes._delegate`](/contracts/4.x/api/governance#Votes-_delegate-address-address-) function. + +The success and return data of the delegated call will be returned back to the caller of the proxy. + +
+

Functions

+
+- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_implementation()](#Proxy-_implementation--) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +- [receive()](#Proxy-receive--) +- [_beforeFallback()](#Proxy-_beforeFallback--) +
+
+ + + +
+
+

_delegate(address implementation)

+
+

internal

+# +
+
+
+ +Delegates the current call to `implementation`. + +This function does not return to its internal call site, it will return directly to the external caller. + +
+
+ + + +
+
+

_implementation() → address

+
+

internal

+# +
+
+
+ +This is a virtual function that should be overridden so it returns the address to which the fallback function +and [`Proxy._fallback`](#Proxy-_fallback--) should delegate. + +
+
+ + + +
+
+

_fallback()

+
+

internal

+# +
+
+
+ +Delegates the current call to the address returned by `_implementation()`. + +This function does not return to its internal call site, it will return directly to the external caller. + +
+
+ + + +
+
+

fallback()

+
+

external

+# +
+
+
+ +Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other +function in the contract matches the call data. + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data +is empty. + +
+
+ + + +
+
+

_beforeFallback()

+
+

internal

+# +
+
+
+ +Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` +call, or as part of the Solidity `fallback` or `receive` functions. + +If overridden should call `super._beforeFallback()`. + +
+
+ + + +
+ +## `BeaconProxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +``` + +This contract implements a proxy that gets the implementation address for each call from an [`UpgradeableBeacon`](#UpgradeableBeacon). + +The beacon address is stored in storage slot `uint256(keccak256('eip1967.proxy.beacon')) - 1`, so that it doesn't +conflict with the storage layout of the implementation behind the proxy. + +_Available since v3.4._ + +
+

Functions

+
+- [constructor(beacon, data)](#BeaconProxy-constructor-address-bytes-) +- [_beacon()](#BeaconProxy-_beacon--) +- [_implementation()](#BeaconProxy-_implementation--) +- [_setBeacon(beacon, data)](#BeaconProxy-_setBeacon-address-bytes-) +#### ERC1967Upgrade +- [_getImplementation()](#ERC1967Upgrade-_getImplementation--) +- [_upgradeTo(newImplementation)](#ERC1967Upgrade-_upgradeTo-address-) +- [_upgradeToAndCall(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCall-address-bytes-bool-) +- [_upgradeToAndCallUUPS(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCallUUPS-address-bytes-bool-) +- [_getAdmin()](#ERC1967Upgrade-_getAdmin--) +- [_changeAdmin(newAdmin)](#ERC1967Upgrade-_changeAdmin-address-) +- [_getBeacon()](#ERC1967Upgrade-_getBeacon--) +- [_upgradeBeaconToAndCall(newBeacon, data, forceCall)](#ERC1967Upgrade-_upgradeBeaconToAndCall-address-bytes-bool-) +#### IERC1967 +#### Proxy +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +- [receive()](#Proxy-receive--) +- [_beforeFallback()](#Proxy-_beforeFallback--) +
+
+ +
+

Events

+
+#### ERC1967Upgrade +#### IERC1967 +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +#### Proxy +
+
+ + + +
+
+

constructor(address beacon, bytes data)

+
+

public

+# +
+
+
+ +Initializes the proxy with `beacon`. + +If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This +will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity +constructor. + +Requirements: + +- `beacon` must be a contract with the interface [`IBeacon`](#IBeacon). + +
+
+ + + +
+
+

_beacon() → address

+
+

internal

+# +
+
+
+ +Returns the current beacon address. + +
+
+ + + +
+
+

_implementation() → address

+
+

internal

+# +
+
+
+ +Returns the current implementation address of the associated beacon. + +
+
+ + + +
+
+

_setBeacon(address beacon, bytes data)

+
+

internal

+# +
+
+
+ +Changes the proxy to use a new beacon. Deprecated: see [`ERC1967Upgrade._upgradeBeaconToAndCall`](#ERC1967Upgrade-_upgradeBeaconToAndCall-address-bytes-bool-). + +If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. + +Requirements: + +- `beacon` must be a contract. +- The implementation returned by `beacon` must be a contract. + +
+
+ + + +
+ +## `IBeacon` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; +``` + +This is the interface that [`BeaconProxy`](#BeaconProxy) expects of its beacon. + +
+

Functions

+
+- [implementation()](#IBeacon-implementation--) +
+
+ + + +
+
+

implementation() → address

+
+

external

+# +
+
+
+ +Must return an address that can be used as a delegate call target. + +[`BeaconProxy`](#BeaconProxy) will check that this address is a contract. + +
+
+ + + +
+ +## `UpgradeableBeacon` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +``` + +This contract is used in conjunction with one or more instances of [`BeaconProxy`](#BeaconProxy) to determine their +implementation contract, which is where they will delegate all function calls. + +An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. + +
+

Functions

+
+- [constructor(implementation_)](#UpgradeableBeacon-constructor-address-) +- [implementation()](#UpgradeableBeacon-implementation--) +- [upgradeTo(newImplementation)](#UpgradeableBeacon-upgradeTo-address-) +#### Ownable +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +#### IBeacon +
+
+ +
+

Events

+
+- [Upgraded(implementation)](#UpgradeableBeacon-Upgraded-address-) +#### Ownable +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +#### IBeacon +
+
+ + + +
+
+

constructor(address implementation_)

+
+

public

+# +
+
+
+ +Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the +beacon. + +
+
+ + + +
+
+

implementation() → address

+
+

public

+# +
+
+
+ +Returns the current implementation address. + +
+
+ + + +
+
+

upgradeTo(address newImplementation)

+
+

public

+# +
+
+
+ +Upgrades the beacon to a new implementation. + +Emits an [`IERC1967.Upgraded`](/contracts/4.x/api/interfaces#IERC1967-Upgraded-address-) event. + +Requirements: + +- msg.sender must be the owner of the contract. +- `newImplementation` must be a contract. + +
+
+ + + +
+
+

Upgraded(address indexed implementation)

+
+

event

+# +
+
+ +
+ +Emitted when the implementation returned by the beacon is changed. + +
+
+ + + +
+ +## `ProxyAdmin` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +``` + +This is an auxiliary contract meant to be assigned as the admin of a [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy). For an +explanation of why you would want to use this see the documentation for [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy). + +
+

Functions

+
+- [getProxyImplementation(proxy)](#ProxyAdmin-getProxyImplementation-contract-ITransparentUpgradeableProxy-) +- [getProxyAdmin(proxy)](#ProxyAdmin-getProxyAdmin-contract-ITransparentUpgradeableProxy-) +- [changeProxyAdmin(proxy, newAdmin)](#ProxyAdmin-changeProxyAdmin-contract-ITransparentUpgradeableProxy-address-) +- [upgrade(proxy, implementation)](#ProxyAdmin-upgrade-contract-ITransparentUpgradeableProxy-address-) +- [upgradeAndCall(proxy, implementation, data)](#ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes-) +#### Ownable +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+#### Ownable +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ + + +
+
+

getProxyImplementation(contract ITransparentUpgradeableProxy proxy) → address

+
+

public

+# +
+
+
+ +Returns the current implementation of `proxy`. + +Requirements: + +- This contract must be the admin of `proxy`. + +
+
+ + + +
+
+

getProxyAdmin(contract ITransparentUpgradeableProxy proxy) → address

+
+

public

+# +
+
+
+ +Returns the current admin of `proxy`. + +Requirements: + +- This contract must be the admin of `proxy`. + +
+
+ + + +
+
+

changeProxyAdmin(contract ITransparentUpgradeableProxy proxy, address newAdmin)

+
+

public

+# +
+
+
+ +Changes the admin of `proxy` to `newAdmin`. + +Requirements: + +- This contract must be the current admin of `proxy`. + +
+
+ + + +
+
+

upgrade(contract ITransparentUpgradeableProxy proxy, address implementation)

+
+

public

+# +
+
+
+ +Upgrades `proxy` to `implementation`. See [`ITransparentUpgradeableProxy.upgradeTo`](#ITransparentUpgradeableProxy-upgradeTo-address-). + +Requirements: + +- This contract must be the admin of `proxy`. + +
+
+ + + +
+
+

upgradeAndCall(contract ITransparentUpgradeableProxy proxy, address implementation, bytes data)

+
+

public

+# +
+
+
+ +Upgrades `proxy` to `implementation` and calls a function on the new implementation. See +[`ITransparentUpgradeableProxy.upgradeToAndCall`](#ITransparentUpgradeableProxy-upgradeToAndCall-address-bytes-). + +Requirements: + +- This contract must be the admin of `proxy`. + +
+
+ + + +
+ +## `ITransparentUpgradeableProxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +``` + +Interface for [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy). In order to implement transparency, [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) +does not implement this interface directly, and some of its functions are implemented by an internal dispatch +mechanism. The compiler is unaware that these functions are implemented by [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) and will not +include them in the ABI so this interface must be used to interact with it. + +
+

Functions

+
+- [admin()](#ITransparentUpgradeableProxy-admin--) +- [implementation()](#ITransparentUpgradeableProxy-implementation--) +- [changeAdmin()](#ITransparentUpgradeableProxy-changeAdmin-address-) +- [upgradeTo()](#ITransparentUpgradeableProxy-upgradeTo-address-) +- [upgradeToAndCall(, )](#ITransparentUpgradeableProxy-upgradeToAndCall-address-bytes-) +#### IERC1967 +
+
+ +
+

Events

+
+#### IERC1967 +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +
+
+ + + +
+
+

admin() → address

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

implementation() → address

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

changeAdmin(address)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

upgradeTo(address)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

upgradeToAndCall(address, bytes)

+
+

external

+# +
+
+
+ +
+
+ + + +
+ +## `TransparentUpgradeableProxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +``` + +This contract implements a proxy that is upgradeable by an admin. + +To avoid [proxy selector +clashing](https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357), which can potentially be used in an attack, this contract uses the +[transparent proxy pattern](https://blog.openzeppelin.com/the-transparent-proxy-pattern/). This pattern implies two +things that go hand in hand: + +1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if +that call matches one of the admin functions exposed by the proxy itself. +2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the +implementation. If the admin tries to call a function on the implementation it will fail with an error that says +"admin cannot fallback to proxy target". + +These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing +the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due +to sudden errors when trying to call a function from the proxy implementation. + +Our recommendation is for the dedicated account to be an instance of the [`ProxyAdmin`](#ProxyAdmin) contract. If set up this way, +you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy. + +NOTE: The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not +inherit from that interface, and instead the admin functions are implicitly implemented using a custom dispatch +mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to +fully implement transparency without decoding reverts caused by selector clashes between the proxy and the +implementation. + + +It is not recommended to extend this contract to add additional external functions. If you do so, the compiler +will not check that there are no selector conflicts, due to the note above. A selector clash between any new function +and the functions declared in [`ITransparentUpgradeableProxy`](#ITransparentUpgradeableProxy) will be resolved in favor of the new one. This could +render the admin operations inaccessible, which could prevent upgradeability. Transparency may also be compromised. + + +
+

Modifiers

+
+- [ifAdmin()](#TransparentUpgradeableProxy-ifAdmin--) +
+
+ +
+

Functions

+
+- [constructor(_logic, admin_, _data)](#TransparentUpgradeableProxy-constructor-address-address-bytes-) +- [_fallback()](#TransparentUpgradeableProxy-_fallback--) +- [_admin()](#TransparentUpgradeableProxy-_admin--) +#### ERC1967Proxy +- [_implementation()](#ERC1967Proxy-_implementation--) +#### ERC1967Upgrade +- [_getImplementation()](#ERC1967Upgrade-_getImplementation--) +- [_upgradeTo(newImplementation)](#ERC1967Upgrade-_upgradeTo-address-) +- [_upgradeToAndCall(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCall-address-bytes-bool-) +- [_upgradeToAndCallUUPS(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCallUUPS-address-bytes-bool-) +- [_getAdmin()](#ERC1967Upgrade-_getAdmin--) +- [_changeAdmin(newAdmin)](#ERC1967Upgrade-_changeAdmin-address-) +- [_getBeacon()](#ERC1967Upgrade-_getBeacon--) +- [_upgradeBeaconToAndCall(newBeacon, data, forceCall)](#ERC1967Upgrade-_upgradeBeaconToAndCall-address-bytes-bool-) +#### IERC1967 +#### Proxy +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [fallback()](#Proxy-fallback--) +- [receive()](#Proxy-receive--) +- [_beforeFallback()](#Proxy-_beforeFallback--) +
+
+ +
+

Events

+
+#### ERC1967Proxy +#### ERC1967Upgrade +#### IERC1967 +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +#### Proxy +
+
+ + + +
+
+

ifAdmin()

+
+

internal

+# +
+
+ +
+ +Modifier used internally that will delegate the call to the implementation unless the sender is the admin. + +CAUTION: This modifier is deprecated, as it could cause issues if the modified function has arguments, and the +implementation provides a function with the same selector. + +
+
+ + + +
+
+

constructor(address _logic, address admin_, bytes _data)

+
+

public

+# +
+
+
+ +Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and +optionally initialized with `_data` as explained in [`ERC1967Proxy.constructor`](#ERC1967Proxy-constructor-address-bytes-). + +
+
+ + + +
+
+

_fallback()

+
+

internal

+# +
+
+
+ +If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior + +
+
+ + + +
+
+

_admin() → address

+
+

internal

+# +
+
+
+ +Returns the current admin. + +CAUTION: This function is deprecated. Use [`ERC1967Upgrade._getAdmin`](#ERC1967Upgrade-_getAdmin--) instead. + +
+
+ + + +
+ +## `Initializable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +``` + +This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed +behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an +external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer +function so it can only be called once. The [`Initializable.initializer`](#Initializable-initializer--) modifier provided by this contract will have this effect. + +The initialization functions use a version number. Once a version number is used, it is consumed and cannot be +reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in +case an upgrade adds a module that needs to be initialized. + +For example: + +[.hljs-theme-light.nopadding] +```solidity +contract MyToken is ERC20Upgradeable { + function initialize() initializer public { + __ERC20_init("MyToken", "MTK"); + } +} + +contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + function initializeV2() reinitializer(2) public { + __ERC20Permit_init("MyToken"); + } +} +``` + +TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as +possible by providing the encoded function call as the `_data` argument to [`ERC1967Proxy.constructor`](#ERC1967Proxy-constructor-address-bytes-). + +CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure +that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + +[CAUTION] +==== +Avoid leaving a contract uninitialized. + +An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation +contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke +the [`Initializable._disableInitializers`](#Initializable-_disableInitializers--) function in the constructor to automatically lock it when it is deployed: + +[.hljs-theme-light.nopadding] +``` +/// @custom:oz-upgrades-unsafe-allow constructor +constructor() { + _disableInitializers(); +} +``` +==== + +
+

Modifiers

+
+- [initializer()](#Initializable-initializer--) +- [reinitializer(version)](#Initializable-reinitializer-uint8-) +- [onlyInitializing()](#Initializable-onlyInitializing--) +
+
+ +
+

Functions

+
+- [_disableInitializers()](#Initializable-_disableInitializers--) +- [_getInitializedVersion()](#Initializable-_getInitializedVersion--) +- [_isInitializing()](#Initializable-_isInitializing--) +
+
+ +
+

Events

+
+- [Initialized(version)](#Initializable-Initialized-uint8-) +
+
+ + + +
+
+

initializer()

+
+

internal

+# +
+
+ +
+ +A modifier that defines a protected initializer function that can be invoked at most once. In its scope, +`onlyInitializing` functions can be used to initialize parent contracts. + +Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a +constructor. + +Emits an [`Initializable.Initialized`](#Initializable-Initialized-uint8-) event. + +
+
+ + + +
+
+

reinitializer(uint8 version)

+
+

internal

+# +
+
+ +
+ +A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the +contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be +used to initialize parent contracts. + +A reinitializer may be used after the original initialization step. This is essential to configure modules that +are added through upgrades and that require initialization. + +When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` +cannot be nested. If one is invoked in the context of another, execution will revert. + +Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in +a contract, executing them in the right order is up to the developer or operator. + + +setting the version to 255 will prevent any future reinitialization. + + +Emits an [`Initializable.Initialized`](#Initializable-Initialized-uint8-) event. + +
+
+ + + +
+
+

onlyInitializing()

+
+

internal

+# +
+
+ +
+ +Modifier to protect an initialization function so that it can only be invoked by functions with the +[`Initializable.initializer`](#Initializable-initializer--) and [`Initializable.reinitializer`](#Initializable-reinitializer-uint8-) modifiers, directly or indirectly. + +
+
+ + + +
+
+

_disableInitializers()

+
+

internal

+# +
+
+
+ +Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. +Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized +to any version. It is recommended to use this to lock implementation contracts that are designed to be called +through proxies. + +Emits an [`Initializable.Initialized`](#Initializable-Initialized-uint8-) event the first time it is successfully executed. + +
+
+ + + +
+
+

_getInitializedVersion() → uint8

+
+

internal

+# +
+
+
+ +Returns the highest version that has been initialized. See [`Initializable.reinitializer`](#Initializable-reinitializer-uint8-). + +
+
+ + + +
+
+

_isInitializing() → bool

+
+

internal

+# +
+
+
+ +Returns `true` if the contract is currently initializing. See [`Initializable.onlyInitializing`](#Initializable-onlyInitializing--). + +
+
+ + + +
+
+

Initialized(uint8 version)

+
+

event

+# +
+
+ +
+ +Triggered when the contract has been initialized or reinitialized. + +
+
+ + + +
+ +## `UUPSUpgradeable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +``` + +An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an +[`ERC1967Proxy`](#ERC1967Proxy), when this contract is set as the implementation behind such a proxy. + +A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is +reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing +`UUPSUpgradeable` with a custom implementation of upgrades. + +The [`UUPSUpgradeable._authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-) function must be overridden to include access restriction to the upgrade mechanism. + +_Available since v4.1._ + +
+

Modifiers

+
+- [onlyProxy()](#UUPSUpgradeable-onlyProxy--) +- [notDelegated()](#UUPSUpgradeable-notDelegated--) +
+
+ +
+

Functions

+
+- [proxiableUUID()](#UUPSUpgradeable-proxiableUUID--) +- [upgradeTo(newImplementation)](#UUPSUpgradeable-upgradeTo-address-) +- [upgradeToAndCall(newImplementation, data)](#UUPSUpgradeable-upgradeToAndCall-address-bytes-) +- [_authorizeUpgrade(newImplementation)](#UUPSUpgradeable-_authorizeUpgrade-address-) +#### ERC1967Upgrade +- [_getImplementation()](#ERC1967Upgrade-_getImplementation--) +- [_upgradeTo(newImplementation)](#ERC1967Upgrade-_upgradeTo-address-) +- [_upgradeToAndCall(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCall-address-bytes-bool-) +- [_upgradeToAndCallUUPS(newImplementation, data, forceCall)](#ERC1967Upgrade-_upgradeToAndCallUUPS-address-bytes-bool-) +- [_getAdmin()](#ERC1967Upgrade-_getAdmin--) +- [_changeAdmin(newAdmin)](#ERC1967Upgrade-_changeAdmin-address-) +- [_getBeacon()](#ERC1967Upgrade-_getBeacon--) +- [_upgradeBeaconToAndCall(newBeacon, data, forceCall)](#ERC1967Upgrade-_upgradeBeaconToAndCall-address-bytes-bool-) +#### IERC1967 +#### IERC1822Proxiable +
+
+ +
+

Events

+
+#### ERC1967Upgrade +#### IERC1967 +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +#### IERC1822Proxiable +
+
+ + + +
+
+

onlyProxy()

+
+

internal

+# +
+
+ +
+ +Check that the execution is being performed through a delegatecall call and that the execution context is +a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case +for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a +function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to +fail. + +
+
+ + + +
+
+

notDelegated()

+
+

internal

+# +
+
+ +
+ +Check that the execution is not being performed through a delegate call. This allows a function to be +callable on the implementing contract but not through proxies. + +
+
+ + + +
+
+

proxiableUUID() → bytes32

+
+

external

+# +
+
+
+ +Implementation of the ERC1822 [`IERC1822Proxiable.proxiableUUID`](/contracts/4.x/api/interfaces#IERC1822Proxiable-proxiableUUID--) function. This returns the storage slot used by the +implementation. It is used to validate the implementation's compatibility when performing an upgrade. + + +A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks +bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this +function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. + + +
+
+ + + +
+
+

upgradeTo(address newImplementation)

+
+

public

+# +
+
+
+ +Upgrade the implementation of the proxy to `newImplementation`. + +Calls [`UUPSUpgradeable._authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-). + +Emits an [`IERC1967.Upgraded`](/contracts/4.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

upgradeToAndCall(address newImplementation, bytes data)

+
+

public

+# +
+
+
+ +Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call +encoded in `data`. + +Calls [`UUPSUpgradeable._authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-). + +Emits an [`IERC1967.Upgraded`](/contracts/4.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

_authorizeUpgrade(address newImplementation)

+
+

internal

+# +
+
+
+ +Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by +[`UpgradeableBeacon.upgradeTo`](#UpgradeableBeacon-upgradeTo-address-) and [`ITransparentUpgradeableProxy.upgradeToAndCall`](#ITransparentUpgradeableProxy-upgradeToAndCall-address-bytes-). + +Normally, this function will use an xref:access[access control] modifier such as [`Ownable.onlyOwner`](/contracts/4.x/api/access#Ownable-onlyOwner--). + +```solidity +function _authorizeUpgrade(address) internal override onlyOwner {} +``` + +
+
diff --git a/docs/content/contracts/4.x/api/security.mdx b/docs/content/contracts/4.x/api/security.mdx new file mode 100644 index 00000000..bd39c415 --- /dev/null +++ b/docs/content/contracts/4.x/api/security.mdx @@ -0,0 +1,486 @@ +--- +title: "Security" +description: "Smart contract security utilities and implementations" +--- + +These contracts aim to cover common security practices. + +* [`PullPayment`](#PullPayment): A pattern that can be used to avoid reentrancy attacks. +* [`ReentrancyGuard`](#ReentrancyGuard): A modifier that can prevent reentrancy during certain functions. +* [`Pausable`](#Pausable): A common emergency response mechanism that can pause functionality while a remediation is pending. + + +For an overview on reentrancy and the possible mechanisms to prevent it, read our article [Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + + +## Contracts + +[`PullPayment`](#PullPayment) + +[`ReentrancyGuard`](#ReentrancyGuard) + +[`Pausable`](#Pausable) + + + +
+ +## `Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/security/Pausable.sol"; +``` + +Contract module which allows children to implement an emergency stop +mechanism that can be triggered by an authorized account. + +This module is used through inheritance. It will make available the +modifiers `whenNotPaused` and `whenPaused`, which can be applied to +the functions of your contract. Note that they will not be pausable by +simply including this module, only once the modifiers are put in place. + +
+

Modifiers

+
+- [whenNotPaused()](#Pausable-whenNotPaused--) +- [whenPaused()](#Pausable-whenPaused--) +
+
+ +
+

Functions

+
+- [constructor()](#Pausable-constructor--) +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +
+
+ +
+

Events

+
+- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +
+
+ + + +
+
+

whenNotPaused()

+
+

internal

+# +
+
+ +
+ +Modifier to make a function callable only when the contract is not paused. + +Requirements: + +- The contract must not be paused. + +
+
+ + + +
+
+

whenPaused()

+
+

internal

+# +
+
+ +
+ +Modifier to make a function callable only when the contract is paused. + +Requirements: + +- The contract must be paused. + +
+
+ + + +
+
+

constructor()

+
+

internal

+# +
+
+
+ +Initializes the contract in unpaused state. + +
+
+ + + +
+
+

paused() → bool

+
+

public

+# +
+
+
+ +Returns true if the contract is paused, and false otherwise. + +
+
+ + + +
+
+

_requireNotPaused()

+
+

internal

+# +
+
+
+ +Throws if the contract is paused. + +
+
+ + + +
+
+

_requirePaused()

+
+

internal

+# +
+
+
+ +Throws if the contract is not paused. + +
+
+ + + +
+
+

_pause()

+
+

internal

+# +
+
+
+ +Triggers stopped state. + +Requirements: + +- The contract must not be paused. + +
+
+ + + +
+
+

_unpause()

+
+

internal

+# +
+
+
+ +Returns to normal state. + +Requirements: + +- The contract must be paused. + +
+
+ + + +
+
+

Paused(address account)

+
+

event

+# +
+
+ +
+ +Emitted when the pause is triggered by `account`. + +
+
+ + +
+
+

Unpaused(address account)

+
+

event

+# +
+
+ +
+ +Emitted when the pause is lifted by `account`. + +
+
+ + + +
+ +## `PullPayment` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/security/PullPayment.sol"; +``` + +Simple implementation of a +[pull-payment](https://consensys.github.io/smart-contract-best-practices/development-recommendations/general/external-calls/#favor-pull-over-push-for-external-calls) +strategy, where the paying contract doesn't interact directly with the +receiver account, which must withdraw its payments itself. + +Pull-payments are often considered the best practice when it comes to sending +Ether, security-wise. It prevents recipients from blocking execution, and +eliminates reentrancy concerns. + +TIP: If you would like to learn more about reentrancy and alternative ways +to protect against it, check out our blog post +[Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + +To use, derive from the `PullPayment` contract, and use [`PullPayment._asyncTransfer`](#PullPayment-_asyncTransfer-address-uint256-) +instead of Solidity's `transfer` function. Payees can query their due +payments with [`PullPayment.payments`](#PullPayment-payments-address-), and retrieve them with [`PullPayment.withdrawPayments`](#PullPayment-withdrawPayments-address-payable-). + +
+

Functions

+
+- [constructor()](#PullPayment-constructor--) +- [withdrawPayments(payee)](#PullPayment-withdrawPayments-address-payable-) +- [payments(dest)](#PullPayment-payments-address-) +- [_asyncTransfer(dest, amount)](#PullPayment-_asyncTransfer-address-uint256-) +
+
+ + + +
+
+

constructor()

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

withdrawPayments(address payable payee)

+
+

public

+# +
+
+
+ +Withdraw accumulated payments, forwarding all gas to the recipient. + +Note that _any_ account can call this function, not just the `payee`. +This means that contracts unaware of the `PullPayment` protocol can still +receive funds this way, by having a separate account call +[`PullPayment.withdrawPayments`](#PullPayment-withdrawPayments-address-payable-). + + +Forwarding all gas opens the door to reentrancy vulnerabilities. +Make sure you trust the recipient, or are either following the +checks-effects-interactions pattern or using [`ReentrancyGuard`](#ReentrancyGuard). + + +
+
+ + + +
+
+

payments(address dest) → uint256

+
+

public

+# +
+
+
+ +Returns the payments owed to an address. + +
+
+ + + +
+
+

_asyncTransfer(address dest, uint256 amount)

+
+

internal

+# +
+
+
+ +Called by the payer to store the sent amount as credit to be pulled. +Funds sent in this way are stored in an intermediate [`Escrow`](/contracts/4.x/api/utils#Escrow) contract, so +there is no danger of them being spent before withdrawal. + +
+
+ + + +
+ +## `ReentrancyGuard` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; +``` + +Contract module that helps prevent reentrant calls to a function. + +Inheriting from `ReentrancyGuard` will make the [`ReentrancyGuard.nonReentrant`](#ReentrancyGuard-nonReentrant--) modifier +available, which can be applied to functions to make sure there are no nested +(reentrant) calls to them. + +Note that because there is a single `nonReentrant` guard, functions marked as +`nonReentrant` may not call one another. This can be worked around by making +those functions `private`, and then adding `external` `nonReentrant` entry +points to them. + +TIP: If you would like to learn more about reentrancy and alternative ways +to protect against it, check out our blog post +[Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + +
+

Modifiers

+
+- [nonReentrant()](#ReentrancyGuard-nonReentrant--) +
+
+ +
+

Functions

+
+- [constructor()](#ReentrancyGuard-constructor--) +- [_reentrancyGuardEntered()](#ReentrancyGuard-_reentrancyGuardEntered--) +
+
+ + + +
+
+

nonReentrant()

+
+

internal

+# +
+
+ +
+ +Prevents a contract from calling itself, directly or indirectly. +Calling a `nonReentrant` function from another `nonReentrant` +function is not supported. It is possible to prevent this from happening +by making the `nonReentrant` function external, and making it call a +`private` function that does the actual work. + +
+
+ + + +
+
+

constructor()

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_reentrancyGuardEntered() → bool

+
+

internal

+# +
+
+
+ +Returns true if the reentrancy guard is currently set to "entered", which indicates there is a +`nonReentrant` function in the call stack. + +
+
diff --git a/docs/content/contracts/4.x/api/token/ERC1155.mdx b/docs/content/contracts/4.x/api/token/ERC1155.mdx new file mode 100644 index 00000000..55b88af6 --- /dev/null +++ b/docs/content/contracts/4.x/api/token/ERC1155.mdx @@ -0,0 +1,1802 @@ +--- +title: "ERC1155" +description: "Smart contract ERC1155 utilities and implementations" +--- + +This set of interfaces and contracts are all related to the [ERC1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155). + +The EIP consists of three interfaces which fulfill different roles, found here as [`IERC1155`](#IERC1155), [`IERC1155MetadataURI`](#IERC1155MetadataURI) and [`IERC1155Receiver`](#IERC1155Receiver). + +[`ERC1155`](#ERC1155) implements the mandatory [`IERC1155`](#IERC1155) interface, as well as the optional extension [`IERC1155MetadataURI`](#IERC1155MetadataURI), by relying on the substitution mechanism to use the same URI for all token types, dramatically reducing gas costs. + +Additionally there are multiple custom extensions, including: + +* designation of addresses that can pause token transfers for all users ([`ERC1155Pausable`](#ERC1155Pausable)). +* destruction of own tokens ([`ERC1155Burnable`](#ERC1155Burnable)). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC1155 (such as [`_mint`](#ERC1155-_mint-address-uint256-uint256-bytes-)) and expose them as external functions in the way they prefer. On the other hand, [ERC1155 Presets](/contracts/4.x/erc1155#preset-erc1155-contract) (such as [`ERC1155PresetMinterPauser`](#ERC1155PresetMinterPauser)) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. + + +## Core + +[`IERC1155`](#IERC1155) + +[`IERC1155MetadataURI`](#IERC1155MetadataURI) + +[`ERC1155`](#ERC1155) + +[`IERC1155Receiver`](#IERC1155Receiver) + +[`ERC1155Receiver`](#ERC1155Receiver) + +## Extensions + +[`ERC1155Pausable`](#ERC1155Pausable) + +[`ERC1155Burnable`](#ERC1155Burnable) + +[`ERC1155Supply`](#ERC1155Supply) + +[`ERC1155URIStorage`](#ERC1155URIStorage) + +## Presets + +These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code. + +[`ERC1155PresetMinterPauser`](#ERC1155PresetMinterPauser) + +## Utilities + +[`ERC1155Holder`](#ERC1155Holder) + + + +
+ +## `ERC1155` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +``` + +Implementation of the basic standard multi-token. +See https://eips.ethereum.org/EIPS/eip-1155 +Originally based on code by Enjin: https://github.com/enjin/erc-1155 + +_Available since v3.1._ + +
+

Functions

+
+- [constructor(uri_)](#ERC1155-constructor-string-) +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, amount, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, amount, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, amounts, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, amount)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, amounts)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +- [_beforeTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) +- [_afterTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### IERC1155MetadataURI +#### IERC1155 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### IERC1155MetadataURI +#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(string uri_)

+
+

public

+# +
+
+
+ +See [`ERC1155._setURI`](#ERC1155-_setURI-string-). + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

uri(uint256) → string

+
+

public

+# +
+
+
+ +See [`IERC1155MetadataURI.uri`](#IERC1155MetadataURI-uri-uint256-). + +This implementation returns the same URI for *all* token types. It relies +on the token type ID substitution mechanism +[defined in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +Clients calling this function must replace the `\{id\}` substring with the +actual token type ID. + +
+
+ + + +
+
+

balanceOf(address account, uint256 id) → uint256

+
+

public

+# +
+
+
+ +See [`IERC1155.balanceOf`](#IERC1155-balanceOf-address-uint256-). + +Requirements: + +- `account` cannot be the zero address. + +
+
+ + + +
+
+

balanceOfBatch(address[] accounts, uint256[] ids) → uint256[]

+
+

public

+# +
+
+
+ +See [`IERC1155.balanceOfBatch`](#IERC1155-balanceOfBatch-address---uint256---). + +Requirements: + +- `accounts` and `ids` must have the same length. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

public

+# +
+
+
+ +See [`IERC1155.setApprovalForAll`](#IERC1155-setApprovalForAll-address-bool-). + +
+
+ + + +
+
+

isApprovedForAll(address account, address operator) → bool

+
+

public

+# +
+
+
+ +See [`IERC1155.isApprovedForAll`](#IERC1155-isApprovedForAll-address-address-). + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)

+
+

public

+# +
+
+
+ +See [`IERC1155.safeTransferFrom`](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-). + +
+
+ + + +
+
+

safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

public

+# +
+
+
+ +See [`IERC1155.safeBatchTransferFrom`](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-). + +
+
+ + + +
+
+

_safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)

+
+

internal

+# +
+
+
+ +Transfers `amount` tokens of token type `id` from `from` to `to`. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- `from` must have a balance of tokens of type `id` of at least `amount`. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +xref:ROOT:erc1155#batch-operations[Batched] version of [`ERC1155._safeTransferFrom`](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_setURI(string newuri)

+
+

internal

+# +
+
+
+ +Sets a new URI for all token types, by relying on the token type ID +substitution mechanism +[defined in the EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +By this mechanism, any occurrence of the `\{id\}` substring in either the +URI or any of the amounts in the JSON file at said URI will be replaced by +clients with the token type ID. + +For example, the `https://token-cdn-domain/\{id\}.json` URI would be +interpreted by clients as +`https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` +for token type ID 0x4cce0. + +See [`ERC1155.uri`](#ERC1155-uri-uint256-). + +Because these URIs cannot be meaningfully represented by the [`IERC1155.URI`](#IERC1155-URI-string-uint256-) event, +this function emits no events. + +
+
+ + + +
+
+

_mint(address to, uint256 id, uint256 amount, bytes data)

+
+

internal

+# +
+
+
+ +Creates `amount` tokens of token type `id`, and assigns them to `to`. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_mintBatch(address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +xref:ROOT:erc1155#batch-operations[Batched] version of [`ERC1155._mint`](#ERC1155-_mint-address-uint256-uint256-bytes-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- `ids` and `amounts` must have the same length. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_burn(address from, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Destroys `amount` tokens of token type `id` from `from` + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `amount` tokens of token type `id`. + +
+
+ + + +
+
+

_burnBatch(address from, uint256[] ids, uint256[] amounts)

+
+

internal

+# +
+
+
+ +xref:ROOT:erc1155#batch-operations[Batched] version of [`ERC1155._burn`](#ERC1155-_burn-address-uint256-uint256-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- `ids` and `amounts` must have the same length. + +
+
+ + + +
+
+

_setApprovalForAll(address owner, address operator, bool approved)

+
+

internal

+# +
+
+
+ +Approve `operator` to operate on all of `owner` tokens + +Emits an [`IERC1155.ApprovalForAll`](#IERC1155-ApprovalForAll-address-address-bool-) event. + +
+
+ + + +
+
+

_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +Hook that is called before any token transfer. This includes minting +and burning, as well as batched variants. + +The same hook is called on both single and batched variants. For single +transfers, the length of the `ids` and `amounts` arrays will be 1. + +Calling conditions (for each `id` and `amount` pair): + +- When `from` and `to` are both non-zero, `amount` of ``from``'s tokens +of token type `id` will be transferred to `to`. +- When `from` is zero, `amount` tokens of token type `id` will be minted +for `to`. +- when `to` is zero, `amount` of ``from``'s tokens of token type `id` +will be burned. +- `from` and `to` are never both zero. +- `ids` and `amounts` have the same, non-zero length. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+
+

_afterTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +Hook that is called after any token transfer. This includes minting +and burning, as well as batched variants. + +The same hook is called on both single and batched variants. For single +transfers, the length of the `id` and `amount` arrays will be 1. + +Calling conditions (for each `id` and `amount` pair): + +- When `from` and `to` are both non-zero, `amount` of ``from``'s tokens +of token type `id` will be transferred to `to`. +- When `from` is zero, `amount` tokens of token type `id` will be minted +for `to`. +- when `to` is zero, `amount` of ``from``'s tokens of token type `id` +will be burned. +- `from` and `to` are never both zero. +- `ids` and `amounts` have the same, non-zero length. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+ +## `IERC1155` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +``` + +Required interface of an ERC1155 compliant contract, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1155). + +_Available since v3.1._ + +
+

Functions

+
+- [balanceOf(account, id)](#IERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#IERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#IERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#IERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### IERC165 +
+
+ + + +
+
+

balanceOf(address account, uint256 id) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens of token type `id` owned by `account`. + +Requirements: + +- `account` cannot be the zero address. + +
+
+ + + +
+
+

balanceOfBatch(address[] accounts, uint256[] ids) → uint256[]

+
+

external

+# +
+
+
+ +xref:ROOT:erc1155#batch-operations[Batched] version of [`ERC1155.balanceOf`](#ERC1155-balanceOf-address-uint256-). + +Requirements: + +- `accounts` and `ids` must have the same length. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

external

+# +
+
+
+ +Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + +Emits an [`IERC1155.ApprovalForAll`](#IERC1155-ApprovalForAll-address-address-bool-) event. + +Requirements: + +- `operator` cannot be the caller. + +
+
+ + + +
+
+

isApprovedForAll(address account, address operator) → bool

+
+

external

+# +
+
+
+ +Returns true if `operator` is approved to transfer ``account``'s tokens. + +See [`ERC1155.setApprovalForAll`](#ERC1155-setApprovalForAll-address-bool-). + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes data)

+
+

external

+# +
+
+
+ +Transfers `amount` tokens of token type `id` from `from` to `to`. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- If the caller is not `from`, it must have been approved to spend ``from``'s tokens via [`ERC1155.setApprovalForAll`](#ERC1155-setApprovalForAll-address-bool-). +- `from` must have a balance of tokens of type `id` of at least `amount`. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

external

+# +
+
+
+ +xref:ROOT:erc1155#batch-operations[Batched] version of [`ERC1155.safeTransferFrom`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- `ids` and `amounts` must have the same length. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)

+
+

event

+# +
+
+ +
+ +Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`. + +
+
+ + +
+
+

TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)

+
+

event

+# +
+
+ +
+ +Equivalent to multiple [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) events, where `operator`, `from` and `to` are the same for all +transfers. + +
+
+ + +
+
+

ApprovalForAll(address indexed account, address indexed operator, bool approved)

+
+

event

+# +
+
+ +
+ +Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to +`approved`. + +
+
+ + +
+
+

URI(string value, uint256 indexed id)

+
+

event

+# +
+
+ +
+ +Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + +If an [`IERC1155.URI`](#IERC1155-URI-string-uint256-) event was emitted for `id`, the standard +[guarantees](https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions) that `value` will equal the value +returned by [`IERC1155MetadataURI.uri`](#IERC1155MetadataURI-uri-uint256-). + +
+
+ + + +
+ +## `IERC1155Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +``` + +_Available since v3.1._ + +
+

Functions

+
+- [onERC1155Received(operator, from, id, value, data)](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(operator, from, ids, values, data)](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ + + +
+
+

onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Handles the receipt of a single ERC1155 token type. This function is +called at the end of a `safeTransferFrom` after the balance has been updated. + +NOTE: To accept the transfer, this must return +`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` +(i.e. 0xf23a6e61, or its own function selector). + +
+
+ + + +
+
+

onERC1155BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Handles the receipt of a multiple ERC1155 token types. This function +is called at the end of a `safeBatchTransferFrom` after the balances have +been updated. + +NOTE: To accept the transfer(s), this must return +`bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` +(i.e. 0xbc197c81, or its own function selector). + +
+
+ + + +
+ +## `ERC1155Burnable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; +``` + +Extension of [`ERC1155`](#ERC1155) that allows token holders to destroy both their +own tokens and those that they have been approved to use. + +_Available since v3.1._ + +
+

Functions

+
+- [burn(account, id, value)](#ERC1155Burnable-burn-address-uint256-uint256-) +- [burnBatch(account, ids, values)](#ERC1155Burnable-burnBatch-address-uint256---uint256---) +#### ERC1155 +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, amount, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, amount, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, amounts, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, amount)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, amounts)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +- [_beforeTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) +- [_afterTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### IERC1155MetadataURI +#### IERC1155 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### ERC1155 +#### IERC1155MetadataURI +#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

burn(address account, uint256 id, uint256 value)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

burnBatch(address account, uint256[] ids, uint256[] values)

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC1155Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol"; +``` + +ERC1155 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + + +This contract does not include public pause and unpause functions. In +addition to inheriting this contract, you must define both functions, invoking the +[`Pausable._pause`](../security#Pausable-_pause--) and [`Pausable._unpause`](../security#Pausable-_unpause--) internal functions, with appropriate +access control, e.g. using [`AccessControl`](../access#AccessControl) or [`Ownable`](../access#Ownable). Not doing so will +make the contract unpausable. + + +_Available since v3.1._ + +
+

Functions

+
+- [_beforeTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155Pausable-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### Pausable +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC1155 +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, amount, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, amount, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, amounts, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, amount)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, amounts)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +- [_afterTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### IERC1155MetadataURI +#### IERC1155 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Pausable +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC1155 +#### IERC1155MetadataURI +#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +See [`ERC1155._beforeTokenTransfer`](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-). + +Requirements: + +- the contract must not be paused. + +
+
+ + + +
+ +## `ERC1155Supply` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +``` + +Extension of ERC1155 that adds tracking of total supply per id. + +Useful for scenarios where Fungible and Non-fungible tokens have to be +clearly identified. Note: While a totalSupply of 1 might mean the +corresponding is an NFT, there is no guarantees that no other token with the +same id are not going to be minted. + +
+

Functions

+
+- [totalSupply(id)](#ERC1155Supply-totalSupply-uint256-) +- [exists(id)](#ERC1155Supply-exists-uint256-) +- [_beforeTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155Supply-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### ERC1155 +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, amount, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, amount, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, amounts, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, amount)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, amounts)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +- [_afterTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### IERC1155MetadataURI +#### IERC1155 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### ERC1155 +#### IERC1155MetadataURI +#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

totalSupply(uint256 id) → uint256

+
+

public

+# +
+
+
+ +Total amount of tokens in with a given id. + +
+
+ + + +
+
+

exists(uint256 id) → bool

+
+

public

+# +
+
+
+ +Indicates whether any token exist with a given id, or not. + +
+
+ + + +
+
+

_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +See [`ERC1155._beforeTokenTransfer`](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-). + +
+
+ + + +
+ +## `ERC1155URIStorage` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol"; +``` + +ERC1155 token with storage based token URI management. +Inspired by the ERC721URIStorage extension + +_Available since v4.6._ + +
+

Functions

+
+- [uri(tokenId)](#ERC1155URIStorage-uri-uint256-) +- [_setURI(tokenId, tokenURI)](#ERC1155URIStorage-_setURI-uint256-string-) +- [_setBaseURI(baseURI)](#ERC1155URIStorage-_setBaseURI-string-) +#### ERC1155 +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, amount, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, amount, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, amounts, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, amount)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, amounts)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +- [_beforeTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) +- [_afterTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### IERC1155MetadataURI +#### IERC1155 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### ERC1155 +#### IERC1155MetadataURI +#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

uri(uint256 tokenId) → string

+
+

public

+# +
+
+
+ +See [`IERC1155MetadataURI.uri`](#IERC1155MetadataURI-uri-uint256-). + +This implementation returns the concatenation of the `_baseURI` +and the token-specific uri if the latter is set + +This enables the following behaviors: + +- if `_tokenURIs[tokenId]` is set, then the result is the concatenation + of `_baseURI` and `_tokenURIs[tokenId]` (keep in mind that `_baseURI` + is empty per default); + +- if `_tokenURIs[tokenId]` is NOT set then we fallback to `super.uri()` + which in most cases will contain `ERC1155._uri`; + +- if `_tokenURIs[tokenId]` is NOT set, and if the parents do not have a + uri value set, then the result is empty. + +
+
+ + + +
+
+

_setURI(uint256 tokenId, string tokenURI)

+
+

internal

+# +
+
+
+ +Sets `tokenURI` as the tokenURI of `tokenId`. + +
+
+ + + +
+
+

_setBaseURI(string baseURI)

+
+

internal

+# +
+
+
+ +Sets `baseURI` as the `_baseURI` for all tokens + +
+
+ + + +
+ +## `IERC1155MetadataURI` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +``` + +Interface of the optional ERC1155MetadataExtension interface, as defined +in the [EIP](https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions). + +_Available since v3.1._ + +
+

Functions

+
+- [uri(id)](#IERC1155MetadataURI-uri-uint256-) +#### IERC1155 +- [balanceOf(account, id)](#IERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#IERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#IERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#IERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### IERC165 +
+
+ + + +
+
+

uri(uint256 id) → string

+
+

external

+# +
+
+
+ +Returns the URI for token type `id`. + +If the `\{id\}` substring is present in the URI, it must be replaced by +clients with the actual token type ID. + +
+
+ + + +
+ +## `ERC1155PresetMinterPauser` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol"; +``` + +[`ERC1155`](#ERC1155) token, including: + + - ability for holders to burn (destroy) their tokens + - a minter role that allows for token minting (creation) + - a pauser role that allows to stop all token transfers + +This contract uses [`AccessControl`](../access#AccessControl) to lock permissioned functions using the +different roles - head to its documentation for details. + +The account that deploys the contract will be granted the minter and pauser +roles, as well as the default admin role, which will let it grant both minter +and pauser roles to other accounts. + +_Deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/)._ + +
+

Functions

+
+- [constructor(uri)](#ERC1155PresetMinterPauser-constructor-string-) +- [mint(to, id, amount, data)](#ERC1155PresetMinterPauser-mint-address-uint256-uint256-bytes-) +- [mintBatch(to, ids, amounts, data)](#ERC1155PresetMinterPauser-mintBatch-address-uint256---uint256---bytes-) +- [pause()](#ERC1155PresetMinterPauser-pause--) +- [unpause()](#ERC1155PresetMinterPauser-unpause--) +- [supportsInterface(interfaceId)](#ERC1155PresetMinterPauser-supportsInterface-bytes4-) +- [_beforeTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155PresetMinterPauser-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) +- [MINTER_ROLE()](#ERC1155PresetMinterPauser-MINTER_ROLE-bytes32) +- [PAUSER_ROLE()](#ERC1155PresetMinterPauser-PAUSER_ROLE-bytes32) +#### ERC1155Pausable +#### Pausable +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC1155Burnable +- [burn(account, id, value)](#ERC1155Burnable-burn-address-uint256-uint256-) +- [burnBatch(account, ids, values)](#ERC1155Burnable-burnBatch-address-uint256---uint256---) +#### ERC1155 +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, amount, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, amount, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, amounts, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, amount, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, amounts, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, amount)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, amounts)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +- [_afterTokenTransfer(operator, from, to, ids, amounts, data)](#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) +#### IERC1155MetadataURI +#### IERC1155 +#### AccessControlEnumerable +- [getRoleMember(role, index)](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#AccessControlEnumerable-getRoleMemberCount-bytes32-) +- [_grantRole(role, account)](#AccessControlEnumerable-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlEnumerable-_revokeRole-bytes32-address-) +#### AccessControl +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +
+
+ +
+

Events

+
+#### ERC1155Pausable +#### Pausable +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC1155Burnable +#### ERC1155 +#### IERC1155MetadataURI +#### IERC1155 +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### AccessControlEnumerable +#### AccessControl +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

constructor(string uri)

+
+

public

+# +
+
+
+ +Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, and `PAUSER_ROLE` to the account that +deploys the contract. + +
+
+ + + +
+
+

mint(address to, uint256 id, uint256 amount, bytes data)

+
+

public

+# +
+
+
+ +Creates `amount` new tokens for `to`, of token type `id`. + +See [`ERC1155._mint`](#ERC1155-_mint-address-uint256-uint256-bytes-). + +Requirements: + +- the caller must have the `MINTER_ROLE`. + +
+
+ + + +
+
+

mintBatch(address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

public

+# +
+
+
+ +xref:ROOT:erc1155#batch-operations[Batched] variant of [`IERC4626.mint`](../interfaces#IERC4626-mint-uint256-address-). + +
+
+ + + +
+
+

pause()

+
+

public

+# +
+
+
+ +Pauses all token transfers. + +See [`ERC1155Pausable`](#ERC1155Pausable) and [`Pausable._pause`](../security#Pausable-_pause--). + +Requirements: + +- the caller must have the `PAUSER_ROLE`. + +
+
+ + + +
+
+

unpause()

+
+

public

+# +
+
+
+ +Unpauses all token transfers. + +See [`ERC1155Pausable`](#ERC1155Pausable) and [`Pausable._unpause`](../security#Pausable-_unpause--). + +Requirements: + +- the caller must have the `PAUSER_ROLE`. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

_beforeTokenTransfer(address operator, address from, address to, uint256[] ids, uint256[] amounts, bytes data)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

MINTER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

PAUSER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC1155Holder` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +``` + +_Available since v3.1._ + +
+

Functions

+
+- [onERC1155Received(, , , , )](#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +#### ERC1155Receiver +- [supportsInterface(interfaceId)](#ERC1155Receiver-supportsInterface-bytes4-) +#### IERC1155Receiver +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

onERC1155Received(address, address, uint256, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) → bytes4

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC1155Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Receiver.sol"; +``` + +_Available since v3.1._ + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC1155Receiver-supportsInterface-bytes4-) +#### IERC1155Receiver +- [onERC1155Received(operator, from, id, value, data)](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(operator, from, ids, values, data)](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
diff --git a/docs/content/contracts/4.x/api/token/ERC20.mdx b/docs/content/contracts/4.x/api/token/ERC20.mdx new file mode 100644 index 00000000..0fcfa9f4 --- /dev/null +++ b/docs/content/contracts/4.x/api/token/ERC20.mdx @@ -0,0 +1,3785 @@ +--- +title: "ERC20" +description: "Smart contract ERC20 utilities and implementations" +--- + +This set of interfaces, contracts, and utilities are all related to the [ERC20 Token Standard](https://eips.ethereum.org/EIPS/eip-20). + + +For an overview of ERC20 tokens and a walk through on how to create a token contract read our [ERC20 guide](/contracts/4.x/erc20). + + +There are a few core contracts that implement the behavior specified in the EIP: + +* [`IERC20`](#IERC20): the interface all ERC20 implementations should conform to. +* [`IERC20Metadata`](#IERC20Metadata): the extended ERC20 interface including the [`name`](#ERC20-name), [`symbol`](#ERC20-symbol) and [`decimals`](#ERC20-decimals) functions. +* [`ERC20`](#ERC20): the implementation of the ERC20 interface, including the [`name`](#ERC20-name), [`symbol`](#ERC20-symbol) and [`decimals`](#ERC20-decimals) optional standard extension to the base interface. + +Additionally there are multiple custom extensions, including: + +* [`ERC20Permit`](#ERC20Permit): gasless approval of tokens (standardized as ERC2612). +* [`ERC20Burnable`](#ERC20Burnable): destruction of own tokens. +* [`ERC20Capped`](#ERC20Capped): enforcement of a cap to the total supply when minting tokens. +* [`ERC20Pausable`](#ERC20Pausable): ability to pause token transfers. +* [`ERC20Snapshot`](#ERC20Snapshot): efficient storage of past token balances to be later queried at any point in time. +* [`ERC20FlashMint`](#ERC20FlashMint): token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC3156). +* [`ERC20Votes`](#ERC20Votes): support for voting and vote delegation. +* [`ERC20VotesComp`](#ERC20VotesComp): support for voting and vote delegation (compatible with Compound’s token, with uint96 restrictions). +* [`ERC20Wrapper`](#ERC20Wrapper): wrapper to create an ERC20 backed by another ERC20, with deposit and withdraw methods. Useful in conjunction with [`ERC20Votes`](#ERC20Votes). +* [`ERC4626`](#ERC4626): tokenized vault that manages shares (represented as ERC20) that are backed by assets (another ERC20). + +Finally, there are some utilities to interact with ERC20 contracts in various ways. + +* [`SafeERC20`](#SafeERC20): a wrapper around the interface that eliminates the need to handle boolean return values. +* [`TokenTimelock`](#TokenTimelock): hold tokens for a beneficiary until a specified time. + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC20 (such as [`_mint`](#ERC20-_mint-address-uint256-)) and expose them as external functions in the way they prefer. On the other hand, [ERC20 Presets](/contracts/4.x/erc20#preset-erc20-contract) (such as [`ERC20PresetMinterPauser`](#ERC20PresetMinterPauser)) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. + + +## Core + +[`IERC20`](#IERC20) + +[`IERC20Metadata`](#IERC20Metadata) + +[`ERC20`](#ERC20) + +## Extensions + +[`IERC20Permit`](#IERC20Permit) + +[`ERC20Permit`](#ERC20Permit) + +[`ERC20Burnable`](#ERC20Burnable) + +[`ERC20Capped`](#ERC20Capped) + +[`ERC20Pausable`](#ERC20Pausable) + +[`ERC20Snapshot`](#ERC20Snapshot) + +[`ERC20Votes`](#ERC20Votes) + +[`ERC20VotesComp`](#ERC20VotesComp) + +[`ERC20Wrapper`](#ERC20Wrapper) + +[`ERC20FlashMint`](#ERC20FlashMint) + +[`ERC4626`](#ERC4626) + +## Presets + +These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code. + +[`ERC20PresetMinterPauser`](#ERC20PresetMinterPauser) + +[`ERC20PresetFixedSupply`](#ERC20PresetFixedSupply) + +## Utilities + +[`SafeERC20`](#SafeERC20) + +[`TokenTimelock`](#TokenTimelock) + + + +
+ +## `ERC20` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +``` + +Implementation of the [`IERC20`](#IERC20) interface. + +This implementation is agnostic to the way tokens are created. This means +that a supply mechanism has to be added in a derived contract using [`ERC1155._mint`](/contracts/4.x/api/token/ERC1155#ERC1155-_mint-address-uint256-uint256-bytes-). +For a generic mechanism see [`ERC20PresetMinterPauser`](#ERC20PresetMinterPauser). + +TIP: For a detailed writeup see our guide +[How +to implement supply mechanisms](https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226). + +The default value of [`ERC20.decimals`](#ERC20-decimals--) is 18. To change this, you should override +this function so it returns a different value. + +We have followed general OpenZeppelin Contracts guidelines: functions revert +instead returning `false` on failure. This behavior is nonetheless +conventional and does not conflict with the expectations of ERC20 +applications. + +Additionally, an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event is emitted on calls to [`ERC20.transferFrom`](#ERC20-transferFrom-address-address-uint256-). +This allows applications to reconstruct the allowance for all accounts just +by listening to said events. Other implementations of the EIP may not emit +these events, as it isn't required by the specification. + +Finally, the non-standard [`ERC20.decreaseAllowance`](#ERC20-decreaseAllowance-address-uint256-) and [`ERC20.increaseAllowance`](#ERC20-increaseAllowance-address-uint256-) +functions have been added to mitigate the well-known issues around setting +allowances. See [`IERC20.approve`](#IERC20-approve-address-uint256-). + +
+

Functions

+
+- [constructor(name_, symbol_)](#ERC20-constructor-string-string-) +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

constructor(string name_, string symbol_)

+
+

public

+# +
+
+
+ +Sets the values for [`Governor.name`](../governance#Governor-name--) and [`ERC20.symbol`](#ERC20-symbol--). + +All two of these values are immutable: they can only be set once during +construction. + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

public

+# +
+
+
+ +Returns the symbol of the token, usually a shorter version of the +name. + +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +Returns the number of decimals used to get its user representation. +For example, if `decimals` equals `2`, a balance of `505` tokens should +be displayed to a user as `5.05` (`505 / 10 ** 2`). + +Tokens usually opt for a value of 18, imitating the relationship between +Ether and Wei. This is the default value returned by this function, unless +it's overridden. + +NOTE: This information is only used for _display_ purposes: it in +no way affects any of the arithmetic of the contract, including +[`IERC20.balanceOf`](#IERC20-balanceOf-address-) and [`IERC20.transfer`](#IERC20-transfer-address-uint256-). + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

public

+# +
+
+
+ +See [`IERC20.totalSupply`](#IERC20-totalSupply--). + +
+
+ + + +
+
+

balanceOf(address account) → uint256

+
+

public

+# +
+
+
+ +See [`IERC20.balanceOf`](#IERC20-balanceOf-address-). + +
+
+ + + +
+
+

transfer(address to, uint256 amount) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.transfer`](#IERC20-transfer-address-uint256-). + +Requirements: + +- `to` cannot be the zero address. +- the caller must have a balance of at least `amount`. + +
+
+ + + +
+
+

allowance(address owner, address spender) → uint256

+
+

public

+# +
+
+
+ +See [`IERC20.allowance`](#IERC20-allowance-address-address-). + +
+
+ + + +
+
+

approve(address spender, uint256 amount) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.approve`](#IERC20-approve-address-uint256-). + +NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on +`transferFrom`. This is semantically equivalent to an infinite approval. + +Requirements: + +- `spender` cannot be the zero address. + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 amount) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.transferFrom`](#IERC20-transferFrom-address-address-uint256-). + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event indicating the updated allowance. This is not +required by the EIP. See the note at the beginning of [`ERC20`](#ERC20). + +NOTE: Does not update the allowance if the current allowance +is the maximum `uint256`. + +Requirements: + +- `from` and `to` cannot be the zero address. +- `from` must have a balance of at least `amount`. +- the caller must have allowance for ``from``'s tokens of at least +`amount`. + +
+
+ + + +
+
+

increaseAllowance(address spender, uint256 addedValue) → bool

+
+

public

+# +
+
+
+ +Atomically increases the allowance granted to `spender` by the caller. + +This is an alternative to [`ERC20.approve`](#ERC20-approve-address-uint256-) that can be used as a mitigation for +problems described in [`IERC20.approve`](#IERC20-approve-address-uint256-). + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event indicating the updated allowance. + +Requirements: + +- `spender` cannot be the zero address. + +
+
+ + + +
+
+

decreaseAllowance(address spender, uint256 subtractedValue) → bool

+
+

public

+# +
+
+
+ +Atomically decreases the allowance granted to `spender` by the caller. + +This is an alternative to [`ERC20.approve`](#ERC20-approve-address-uint256-) that can be used as a mitigation for +problems described in [`IERC20.approve`](#IERC20-approve-address-uint256-). + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event indicating the updated allowance. + +Requirements: + +- `spender` cannot be the zero address. +- `spender` must have allowance for the caller of at least +`subtractedValue`. + +
+
+ + + +
+
+

_transfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Moves `amount` of tokens from `from` to `to`. + +This internal function is equivalent to [`ERC20.transfer`](#ERC20-transfer-address-uint256-), and can be used to +e.g. implement automatic token fees, slashing mechanisms, etc. + +Emits a [`IERC20.Transfer`](#IERC20-Transfer-address-address-uint256-) event. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `from` must have a balance of at least `amount`. + +
+
+ + + +
+
+

_mint(address account, uint256 amount)

+
+

internal

+# +
+
+
+ +Creates `amount` tokens and assigns them to `account`, increasing +the total supply. + +Emits a [`IERC20.Transfer`](#IERC20-Transfer-address-address-uint256-) event with `from` set to the zero address. + +Requirements: + +- `account` cannot be the zero address. + +
+
+ + + +
+
+

_burn(address account, uint256 amount)

+
+

internal

+# +
+
+
+ +Destroys `amount` tokens from `account`, reducing the +total supply. + +Emits a [`IERC20.Transfer`](#IERC20-Transfer-address-address-uint256-) event with `to` set to the zero address. + +Requirements: + +- `account` cannot be the zero address. +- `account` must have at least `amount` tokens. + +
+
+ + + +
+
+

_approve(address owner, address spender, uint256 amount)

+
+

internal

+# +
+
+
+ +Sets `amount` as the allowance of `spender` over the `owner` s tokens. + +This internal function is equivalent to `approve`, and can be used to +e.g. set automatic allowances for certain subsystems, etc. + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + +
+
+ + + +
+
+

_spendAllowance(address owner, address spender, uint256 amount)

+
+

internal

+# +
+
+
+ +Updates `owner` s allowance for `spender` based on spent `amount`. + +Does not update the allowance amount in case of infinite allowance. +Revert if not enough allowance is available. + +Might emit an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event. + +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Hook that is called before any transfer of tokens. This includes +minting and burning. + +Calling conditions: + +- when `from` and `to` are both non-zero, `amount` of ``from``'s tokens +will be transferred to `to`. +- when `from` is zero, `amount` tokens will be minted for `to`. +- when `to` is zero, `amount` of ``from``'s tokens will be burned. +- `from` and `to` are never both zero. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+
+

_afterTokenTransfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Hook that is called after any transfer of tokens. This includes +minting and burning. + +Calling conditions: + +- when `from` and `to` are both non-zero, `amount` of ``from``'s tokens +has been transferred to `to`. +- when `from` is zero, `amount` tokens have been minted for `to`. +- when `to` is zero, `amount` of ``from``'s tokens have been burned. +- `from` and `to` are never both zero. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+ +## `IERC20` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +``` + +Interface of the ERC20 standard as defined in the EIP. + +
+

Functions

+
+- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, amount)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, amount)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

totalSupply() → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens in existence. + +
+
+ + + +
+
+

balanceOf(address account) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens owned by `account`. + +
+
+ + + +
+
+

transfer(address to, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Moves `amount` tokens from the caller's account to `to`. + +Returns a boolean value indicating whether the operation succeeded. + +Emits a [`IERC20.Transfer`](#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

allowance(address owner, address spender) → uint256

+
+

external

+# +
+
+
+ +Returns the remaining number of tokens that `spender` will be +allowed to spend on behalf of `owner` through [`ERC20.transferFrom`](#ERC20-transferFrom-address-address-uint256-). This is +zero by default. + +This value changes when [`ERC20.approve`](#ERC20-approve-address-uint256-) or [`ERC20.transferFrom`](#ERC20-transferFrom-address-address-uint256-) are called. + +
+
+ + + +
+
+

approve(address spender, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Sets `amount` as the allowance of `spender` over the caller's tokens. + +Returns a boolean value indicating whether the operation succeeded. + + +Beware that changing an allowance with this method brings the risk +that someone may use both the old and the new allowance by unfortunate +transaction ordering. One possible solution to mitigate this race +condition is to first reduce the spender's allowance to 0 and set the +desired value afterwards: +https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event. + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Moves `amount` tokens from `from` to `to` using the +allowance mechanism. `amount` is then deducted from the caller's +allowance. + +Returns a boolean value indicating whether the operation succeeded. + +Emits a [`IERC20.Transfer`](#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

Transfer(address indexed from, address indexed to, uint256 value)

+
+

event

+# +
+
+ +
+ +Emitted when `value` tokens are moved from one account (`from`) to +another (`to`). + +Note that `value` may be zero. + +
+
+ + +
+
+

Approval(address indexed owner, address indexed spender, uint256 value)

+
+

event

+# +
+
+ +
+ +Emitted when the allowance of a `spender` for an `owner` is set by +a call to [`ERC20.approve`](#ERC20-approve-address-uint256-). `value` is the new allowance. + +
+
+ + + +
+ +## `ERC20Burnable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +``` + +Extension of [`ERC20`](#ERC20) that allows token holders to destroy both their own +tokens and those that they have an allowance for, in a way that can be +recognized off-chain (via event analysis). + +
+

Functions

+
+- [burn(amount)](#ERC20Burnable-burn-uint256-) +- [burnFrom(account, amount)](#ERC20Burnable-burnFrom-address-uint256-) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

burn(uint256 amount)

+
+

public

+# +
+
+
+ +Destroys `amount` tokens from the caller. + +See [`ERC20._burn`](#ERC20-_burn-address-uint256-). + +
+
+ + + +
+
+

burnFrom(address account, uint256 amount)

+
+

public

+# +
+
+
+ +Destroys `amount` tokens from `account`, deducting from the caller's +allowance. + +See [`ERC20._burn`](#ERC20-_burn-address-uint256-) and [`ERC20.allowance`](#ERC20-allowance-address-address-). + +Requirements: + +- the caller must have allowance for ``accounts``'s tokens of at least +`amount`. + +
+
+ + + +
+ +## `ERC20Capped` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; +``` + +Extension of [`ERC20`](#ERC20) that adds a cap to the supply of tokens. + +
+

Functions

+
+- [constructor(cap_)](#ERC20Capped-constructor-uint256-) +- [cap()](#ERC20Capped-cap--) +- [_mint(account, amount)](#ERC20Capped-_mint-address-uint256-) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

constructor(uint256 cap_)

+
+

internal

+# +
+
+
+ +Sets the value of the `cap`. This value is immutable, it can only be +set once during construction. + +
+
+ + + +
+
+

cap() → uint256

+
+

public

+# +
+
+
+ +Returns the cap on the token's total supply. + +
+
+ + + +
+
+

_mint(address account, uint256 amount)

+
+

internal

+# +
+
+
+ +See [`ERC20._mint`](#ERC20-_mint-address-uint256-). + +
+
+ + + +
+ +## `ERC20FlashMint` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol"; +``` + +Implementation of the ERC3156 Flash loans extension, as defined in +[ERC-3156](https://eips.ethereum.org/EIPS/eip-3156). + +Adds the [`IERC3156FlashLender.flashLoan`](../interfaces#IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-) method, which provides flash loan support at the token +level. By default there is no fee, but this can be changed by overriding [`IERC3156FlashLender.flashFee`](../interfaces#IERC3156FlashLender-flashFee-address-uint256-). + +_Available since v4.1._ + +
+

Functions

+
+- [maxFlashLoan(token)](#ERC20FlashMint-maxFlashLoan-address-) +- [flashFee(token, amount)](#ERC20FlashMint-flashFee-address-uint256-) +- [_flashFee(token, amount)](#ERC20FlashMint-_flashFee-address-uint256-) +- [_flashFeeReceiver()](#ERC20FlashMint-_flashFeeReceiver--) +- [flashLoan(receiver, token, amount, data)](#ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-) +#### IERC3156FlashLender +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### IERC3156FlashLender +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

maxFlashLoan(address token) → uint256

+
+

public

+# +
+
+
+ +Returns the maximum amount of tokens available for loan. + +
+
+ + + +
+
+

flashFee(address token, uint256 amount) → uint256

+
+

public

+# +
+
+
+ +Returns the fee applied when doing flash loans. This function calls +the [`ERC20FlashMint._flashFee`](#ERC20FlashMint-_flashFee-address-uint256-) function which returns the fee applied when doing flash +loans. + +
+
+ + + +
+
+

_flashFee(address token, uint256 amount) → uint256

+
+

internal

+# +
+
+
+ +Returns the fee applied when doing flash loans. By default this +implementation has 0 fees. This function can be overloaded to make +the flash loan mechanism deflationary. + +
+
+ + + +
+
+

_flashFeeReceiver() → address

+
+

internal

+# +
+
+
+ +Returns the receiver address of the flash fee. By default this +implementation returns the address(0) which means the fee amount will be burnt. +This function can be overloaded to change the fee receiver. + +
+
+ + + +
+
+

flashLoan(contract IERC3156FlashBorrower receiver, address token, uint256 amount, bytes data) → bool

+
+

public

+# +
+
+
+ +Performs a flash loan. New tokens are minted and sent to the +`receiver`, who is required to implement the [`IERC3156FlashBorrower`](../interfaces#IERC3156FlashBorrower) +interface. By the end of the flash loan, the receiver is expected to own +amount + fee tokens and have them approved back to the token contract itself so +they can be burned. + +
+
+ + + +
+ +## `ERC20Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; +``` + +ERC20 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + + +This contract does not include public pause and unpause functions. In +addition to inheriting this contract, you must define both functions, invoking the +[`Pausable._pause`](../security#Pausable-_pause--) and [`Pausable._unpause`](../security#Pausable-_unpause--) internal functions, with appropriate +access control, e.g. using [`AccessControl`](../access#AccessControl) or [`Ownable`](../access#Ownable). Not doing so will +make the contract unpausable. + + +
+

Functions

+
+- [_beforeTokenTransfer(from, to, amount)](#ERC20Pausable-_beforeTokenTransfer-address-address-uint256-) +#### Pausable +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### Pausable +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +See [`ERC20._beforeTokenTransfer`](#ERC20-_beforeTokenTransfer-address-address-uint256-). + +Requirements: + +- the contract must not be paused. + +
+
+ + + +
+ +## `ERC20Permit` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +``` + +Implementation of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in +[EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Adds the [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) method, which can be used to change an account's ERC20 allowance (see [`IERC20.allowance`](#IERC20-allowance-address-address-)) by +presenting a message signed by the account. By not relying on `[`IERC20.approve`](#IERC20-approve-address-uint256-)`, the token holder account doesn't +need to send a transaction, and thus is not required to hold Ether at all. + +_Available since v3.4._ + +
+

Functions

+
+- [constructor(name)](#ERC20Permit-constructor-string-) +- [permit(owner, spender, value, deadline, v, r, s)](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#ERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#ERC20Permit-DOMAIN_SEPARATOR--) +- [_useNonce(owner)](#ERC20Permit-_useNonce-address-) +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### IERC20Permit +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### IERC20Permit +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

constructor(string name)

+
+

internal

+# +
+
+
+ +Initializes the [`EIP712`](../utils#EIP712) domain separator using the `name` parameter, and setting `version` to `"1"`. + +It's a good idea to use the same `name` that is defined as the ERC20 token name. + +
+
+ + + +
+
+

permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)

+
+

public

+# +
+
+
+ +Sets `value` as the allowance of `spender` over ``owner``'s tokens, +given ``owner``'s signed approval. + + +The same issues [`IERC20.approve`](#IERC20-approve-address-uint256-) has related to transaction +ordering also apply here. + + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event. + +Requirements: + +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use ``owner``'s current nonce (see [`Votes.nonces`](../governance#Votes-nonces-address-)). + +For more information on the signature format, see the +[relevant EIP +section](https://eips.ethereum.org/EIPS/eip-2612#specification). + +CAUTION: See Security Considerations above. + +
+
+ + + +
+
+

nonces(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-). + +Every successful call to [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) increases ``owner``'s nonce by one. This +prevents a signature from being used multiple times. + +
+
+ + + +
+
+

DOMAIN_SEPARATOR() → bytes32

+
+

external

+# +
+
+
+ +Returns the domain separator used in the encoding of the signature for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-), as defined by [`EIP712`](../utils#EIP712). + +
+
+ + + +
+
+

_useNonce(address owner) → uint256 current

+
+

internal

+# +
+
+
+ +"Consume a nonce": return the current value and increment. + +_Available since v4.1._ + +
+
+ + + +
+ +## `ERC20Snapshot` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol"; +``` + +This contract extends an ERC20 token with a snapshot mechanism. When a snapshot is created, the balances and +total supply at the time are recorded for later access. + +This can be used to safely create mechanisms based on token balances such as trustless dividends or weighted voting. +In naive implementations it's possible to perform a "double spend" attack by reusing the same balance from different +accounts. By using snapshots to calculate dividends or voting power, those attacks no longer apply. It can also be +used to create an efficient ERC20 forking mechanism. + +Snapshots are created by the internal [`ERC20Snapshot._snapshot`](#ERC20Snapshot-_snapshot--) function, which will emit the [`ERC20Snapshot.Snapshot`](#ERC20Snapshot-Snapshot-uint256-) event and return a +snapshot id. To get the total supply at the time of a snapshot, call the function [`ERC20Snapshot.totalSupplyAt`](#ERC20Snapshot-totalSupplyAt-uint256-) with the snapshot +id. To get the balance of an account at the time of a snapshot, call the [`ERC20Snapshot.balanceOfAt`](#ERC20Snapshot-balanceOfAt-address-uint256-) function with the snapshot id +and the account address. + +NOTE: Snapshot policy can be customized by overriding the [`ERC20Snapshot._getCurrentSnapshotId`](#ERC20Snapshot-_getCurrentSnapshotId--) method. For example, having it +return `block.number` will trigger the creation of snapshot at the beginning of each new block. When overriding this +function, be careful about the monotonicity of its result. Non-monotonic snapshot ids will break the contract. + +Implementing snapshots for every block using this method will incur significant gas costs. For a gas-efficient +alternative consider [`ERC20Votes`](#ERC20Votes). + +==== Gas Costs + +Snapshots are efficient. Snapshot creation is _O(1)_. Retrieval of balances or total supply from a snapshot is _O(log +n)_ in the number of snapshots that have been created, although _n_ for a specific account will generally be much +smaller since identical balances in subsequent snapshots are stored as a single entry. + +There is a constant overhead for normal ERC20 transfers due to the additional snapshot bookkeeping. This overhead is +only significant for the first transfer that immediately follows a snapshot for a particular account. Subsequent +transfers will have normal cost until the next snapshot, and so on. + +
+

Functions

+
+- [_snapshot()](#ERC20Snapshot-_snapshot--) +- [_getCurrentSnapshotId()](#ERC20Snapshot-_getCurrentSnapshotId--) +- [balanceOfAt(account, snapshotId)](#ERC20Snapshot-balanceOfAt-address-uint256-) +- [totalSupplyAt(snapshotId)](#ERC20Snapshot-totalSupplyAt-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20Snapshot-_beforeTokenTransfer-address-address-uint256-) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+- [Snapshot(id)](#ERC20Snapshot-Snapshot-uint256-) +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

_snapshot() → uint256

+
+

internal

+# +
+
+
+ +Creates a new snapshot and returns its snapshot id. + +Emits a [`ERC20Snapshot.Snapshot`](#ERC20Snapshot-Snapshot-uint256-) event that contains the same id. + +[`ERC20Snapshot._snapshot`](#ERC20Snapshot-_snapshot--) is `internal` and you have to decide how to expose it externally. Its usage may be restricted to a +set of accounts, for example using [`AccessControl`](../access#AccessControl), or it may be open to the public. + +[WARNING] +==== +While an open way of calling [`ERC20Snapshot._snapshot`](#ERC20Snapshot-_snapshot--) is required for certain trust minimization mechanisms such as forking, +you must consider that it can potentially be used by attackers in two ways. + +First, it can be used to increase the cost of retrieval of values from snapshots, although it will grow +logarithmically thus rendering this attack ineffective in the long term. Second, it can be used to target +specific accounts and increase the cost of ERC20 transfers for them, in the ways specified in the Gas Costs +section above. + +We haven't measured the actual numbers; if this is something you're interested in please reach out to us. +==== + +
+
+ + + +
+
+

_getCurrentSnapshotId() → uint256

+
+

internal

+# +
+
+
+ +Get the current snapshotId + +
+
+ + + +
+
+

balanceOfAt(address account, uint256 snapshotId) → uint256

+
+

public

+# +
+
+
+ +Retrieves the balance of `account` at the time `snapshotId` was created. + +
+
+ + + +
+
+

totalSupplyAt(uint256 snapshotId) → uint256

+
+

public

+# +
+
+
+ +Retrieves the total supply at the time `snapshotId` was created. + +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Hook that is called before any transfer of tokens. This includes +minting and burning. + +Calling conditions: + +- when `from` and `to` are both non-zero, `amount` of ``from``'s tokens +will be transferred to `to`. +- when `from` is zero, `amount` tokens will be minted for `to`. +- when `to` is zero, `amount` of ``from``'s tokens will be burned. +- `from` and `to` are never both zero. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+
+

Snapshot(uint256 id)

+
+

event

+# +
+
+ +
+ +Emitted by [`ERC20Snapshot._snapshot`](#ERC20Snapshot-_snapshot--) when a snapshot identified by `id` is created. + +
+
+ + + +
+ +## `ERC20Votes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +``` + +Extension of ERC20 to support Compound-like voting and delegation. This version is more generic than Compound's, +and supports token supply up to 2^224^ - 1, while COMP is limited to 2^96^ - 1. + +NOTE: If exact COMP compatibility is required, use the [`ERC20VotesComp`](#ERC20VotesComp) variant of this module. + +This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either +by calling the [`IVotes.delegate`](../governance#IVotes-delegate-address-) function directly, or by providing a signature to be used with [`IVotes.delegateBySig`](../governance#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-). Voting +power can be queried through the public accessors [`Governor.getVotes`](../governance#Governor-getVotes-address-uint256-) and [`IVotes.getPastVotes`](../governance#IVotes-getPastVotes-address-uint256-). + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it +requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + +_Available since v4.2._ + +
+

Functions

+
+- [clock()](#ERC20Votes-clock--) +- [CLOCK_MODE()](#ERC20Votes-CLOCK_MODE--) +- [checkpoints(account, pos)](#ERC20Votes-checkpoints-address-uint32-) +- [numCheckpoints(account)](#ERC20Votes-numCheckpoints-address-) +- [delegates(account)](#ERC20Votes-delegates-address-) +- [getVotes(account)](#ERC20Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#ERC20Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#ERC20Votes-getPastTotalSupply-uint256-) +- [delegate(delegatee)](#ERC20Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#ERC20Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_maxSupply()](#ERC20Votes-_maxSupply--) +- [_mint(account, amount)](#ERC20Votes-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20Votes-_burn-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20Votes-_afterTokenTransfer-address-address-uint256-) +- [_delegate(delegator, delegatee)](#ERC20Votes-_delegate-address-address-) +#### IERC5805 +#### IVotes +#### IERC6372 +#### ERC20Permit +- [permit(owner, spender, value, deadline, v, r, s)](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#ERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#ERC20Permit-DOMAIN_SEPARATOR--) +- [_useNonce(owner)](#ERC20Permit-_useNonce-address-) +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### IERC20Permit +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### IERC5805 +#### IVotes +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousBalance, newBalance)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 +#### ERC20Permit +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### IERC20Permit +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Description of the clock + +
+
+ + + +
+
+

checkpoints(address account, uint32 pos) → struct ERC20Votes.Checkpoint

+
+

public

+# +
+
+
+ +Get the `pos`-th checkpoint for `account`. + +
+
+ + + +
+
+

numCheckpoints(address account) → uint32

+
+

public

+# +
+
+
+ +Get number of checkpoints for `account`. + +
+
+ + + +
+
+

delegates(address account) → address

+
+

public

+# +
+
+
+ +Get the address `account` is currently delegating to. + +
+
+ + + +
+
+

getVotes(address account) → uint256

+
+

public

+# +
+
+
+ +Gets the current votes balance for `account` + +
+
+ + + +
+
+

getPastVotes(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Retrieve the number of votes for `account` at the end of `timepoint`. + +Requirements: + +- `timepoint` must be in the past + +
+
+ + + +
+
+

getPastTotalSupply(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Retrieve the `totalSupply` at the end of `timepoint`. Note, this value is the sum of all balances. +It is NOT the sum of all the delegated votes! + +Requirements: + +- `timepoint` must be in the past + +
+
+ + + +
+
+

delegate(address delegatee)

+
+

public

+# +
+
+
+ +Delegate votes from the sender to `delegatee`. + +
+
+ + + +
+
+

delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

+
+

public

+# +
+
+
+ +Delegates votes from signer to `delegatee` + +
+
+ + + +
+
+

_maxSupply() → uint224

+
+

internal

+# +
+
+
+ +Maximum token supply. Defaults to `type(uint224).max` (2^224^ - 1). + +
+
+ + + +
+
+

_mint(address account, uint256 amount)

+
+

internal

+# +
+
+
+ +Snapshots the totalSupply after it has been increased. + +
+
+ + + +
+
+

_burn(address account, uint256 amount)

+
+

internal

+# +
+
+
+ +Snapshots the totalSupply after it has been decreased. + +
+
+ + + +
+
+

_afterTokenTransfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Move voting power when tokens are transferred. + +Emits a [`IVotes.DelegateVotesChanged`](../governance#IVotes-DelegateVotesChanged-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_delegate(address delegator, address delegatee)

+
+

internal

+# +
+
+
+ +Change delegation for `delegator` to `delegatee`. + +Emits events [`IVotes.DelegateChanged`](../governance#IVotes-DelegateChanged-address-address-address-) and [`IVotes.DelegateVotesChanged`](../governance#IVotes-DelegateVotesChanged-address-uint256-uint256-). + +
+
+ + + +
+ +## `ERC20VotesComp` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesComp.sol"; +``` + +Extension of ERC20 to support Compound's voting and delegation. This version exactly matches Compound's +interface, with the drawback of only supporting supply up to (2^96^ - 1). + +NOTE: You should use this contract if you need exact compatibility with COMP (for example in order to use your token +with Governor Alpha or Bravo) and if you are sure the supply cap of 2^96^ is enough for you. Otherwise, use the +[`ERC20Votes`](#ERC20Votes) variant of this module. + +This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either +by calling the [`IVotes.delegate`](../governance#IVotes-delegate-address-) function directly, or by providing a signature to be used with [`IVotes.delegateBySig`](../governance#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-). Voting +power can be queried through the public accessors [`ERC20VotesComp.getCurrentVotes`](#ERC20VotesComp-getCurrentVotes-address-) and [`ERC20VotesComp.getPriorVotes`](#ERC20VotesComp-getPriorVotes-address-uint256-). + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it +requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + +_Available since v4.2._ + +
+

Functions

+
+- [getCurrentVotes(account)](#ERC20VotesComp-getCurrentVotes-address-) +- [getPriorVotes(account, blockNumber)](#ERC20VotesComp-getPriorVotes-address-uint256-) +- [_maxSupply()](#ERC20VotesComp-_maxSupply--) +#### ERC20Votes +- [clock()](#ERC20Votes-clock--) +- [CLOCK_MODE()](#ERC20Votes-CLOCK_MODE--) +- [checkpoints(account, pos)](#ERC20Votes-checkpoints-address-uint32-) +- [numCheckpoints(account)](#ERC20Votes-numCheckpoints-address-) +- [delegates(account)](#ERC20Votes-delegates-address-) +- [getVotes(account)](#ERC20Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#ERC20Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#ERC20Votes-getPastTotalSupply-uint256-) +- [delegate(delegatee)](#ERC20Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#ERC20Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_mint(account, amount)](#ERC20Votes-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20Votes-_burn-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20Votes-_afterTokenTransfer-address-address-uint256-) +- [_delegate(delegator, delegatee)](#ERC20Votes-_delegate-address-address-) +#### IERC5805 +#### IVotes +#### IERC6372 +#### ERC20Permit +- [permit(owner, spender, value, deadline, v, r, s)](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#ERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#ERC20Permit-DOMAIN_SEPARATOR--) +- [_useNonce(owner)](#ERC20Permit-_useNonce-address-) +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### IERC20Permit +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### ERC20Votes +#### IERC5805 +#### IVotes +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousBalance, newBalance)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 +#### ERC20Permit +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### IERC20Permit +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

getCurrentVotes(address account) → uint96

+
+

external

+# +
+
+
+ +Comp version of the [`Governor.getVotes`](../governance#Governor-getVotes-address-uint256-) accessor, with `uint96` return type. + +
+
+ + + +
+
+

getPriorVotes(address account, uint256 blockNumber) → uint96

+
+

external

+# +
+
+
+ +Comp version of the [`IVotes.getPastVotes`](../governance#IVotes-getPastVotes-address-uint256-) accessor, with `uint96` return type. + +
+
+ + + +
+
+

_maxSupply() → uint224

+
+

internal

+# +
+
+
+ +Maximum token supply. Reduced to `type(uint96).max` (2^96^ - 1) to fit COMP interface. + +
+
+ + + +
+ +## `ERC20Wrapper` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +``` + +Extension of the ERC20 token contract to support token wrapping. + +Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". This is useful +in conjunction with other modules. For example, combining this wrapping mechanism with [`ERC20Votes`](#ERC20Votes) will allow the +wrapping of an existing "basic" ERC20 into a governance token. + +_Available since v4.2._ + +
+

Functions

+
+- [constructor(underlyingToken)](#ERC20Wrapper-constructor-contract-IERC20-) +- [decimals()](#ERC20Wrapper-decimals--) +- [underlying()](#ERC20Wrapper-underlying--) +- [depositFor(account, amount)](#ERC20Wrapper-depositFor-address-uint256-) +- [withdrawTo(account, amount)](#ERC20Wrapper-withdrawTo-address-uint256-) +- [_recover(account)](#ERC20Wrapper-_recover-address-) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

constructor(contract IERC20 underlyingToken)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +See [`ERC20.decimals`](#ERC20-decimals--). + +
+
+ + + +
+
+

underlying() → contract IERC20

+
+

public

+# +
+
+
+ +Returns the address of the underlying ERC-20 token that is being wrapped. + +
+
+ + + +
+
+

depositFor(address account, uint256 amount) → bool

+
+

public

+# +
+
+
+ +Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens. + +
+
+ + + +
+
+

withdrawTo(address account, uint256 amount) → bool

+
+

public

+# +
+
+
+ +Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. + +
+
+ + + +
+
+

_recover(address account) → uint256

+
+

internal

+# +
+
+
+ +Mint wrapped token to cover any underlyingTokens that would have been transferred by mistake. Internal +function that can be exposed with access control if desired. + +
+
+ + + +
+ +## `ERC4626` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +``` + +Implementation of the ERC4626 "Tokenized Vault Standard" as defined in +[EIP-4626](https://eips.ethereum.org/EIPS/eip-4626). + +This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for +underlying "assets" through standardized [`IERC4626.deposit`](../interfaces#IERC4626-deposit-uint256-address-), [`IERC4626.mint`](../interfaces#IERC4626-mint-uint256-address-), [`IERC4626.redeem`](../interfaces#IERC4626-redeem-uint256-address-address-) and [`ERC1155Burnable.burn`](/contracts/4.x/api/token/ERC1155#ERC1155Burnable-burn-address-uint256-uint256-) workflows. This contract extends +the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this +contract and not the "assets" token which is an independent contract. + +[CAUTION] +==== +In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning +with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation +attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial +deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may +similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by +verifying the amount received is as expected, using a wrapper that performs these checks such as +[ERC4626Router](https://github.com/fei-protocol/ERC4626#erc4626router-and-base). + +Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()` +corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault +decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself +determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset +(0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's +donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more +expensive than it is profitable. More details about the underlying math can be found +xref:erc4626#inflation-attack[here]. + +The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued +to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets +will cause the first user to exit to experience reduced losses in detriment to the last users that will experience +bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the +`_convertToShares` and `_convertToAssets` functions. + +To learn more, check out our xref:ROOT:erc4626[ERC-4626 guide]. +==== + +_Available since v4.7._ + +
+

Functions

+
+- [constructor(asset_)](#ERC4626-constructor-contract-IERC20-) +- [decimals()](#ERC4626-decimals--) +- [asset()](#ERC4626-asset--) +- [totalAssets()](#ERC4626-totalAssets--) +- [convertToShares(assets)](#ERC4626-convertToShares-uint256-) +- [convertToAssets(shares)](#ERC4626-convertToAssets-uint256-) +- [maxDeposit()](#ERC4626-maxDeposit-address-) +- [maxMint()](#ERC4626-maxMint-address-) +- [maxWithdraw(owner)](#ERC4626-maxWithdraw-address-) +- [maxRedeem(owner)](#ERC4626-maxRedeem-address-) +- [previewDeposit(assets)](#ERC4626-previewDeposit-uint256-) +- [previewMint(shares)](#ERC4626-previewMint-uint256-) +- [previewWithdraw(assets)](#ERC4626-previewWithdraw-uint256-) +- [previewRedeem(shares)](#ERC4626-previewRedeem-uint256-) +- [deposit(assets, receiver)](#ERC4626-deposit-uint256-address-) +- [mint(shares, receiver)](#ERC4626-mint-uint256-address-) +- [withdraw(assets, receiver, owner)](#ERC4626-withdraw-uint256-address-address-) +- [redeem(shares, receiver, owner)](#ERC4626-redeem-uint256-address-address-) +- [_convertToShares(assets, rounding)](#ERC4626-_convertToShares-uint256-enum-Math-Rounding-) +- [_convertToAssets(shares, rounding)](#ERC4626-_convertToAssets-uint256-enum-Math-Rounding-) +- [_deposit(caller, receiver, assets, shares)](#ERC4626-_deposit-address-address-uint256-uint256-) +- [_withdraw(caller, receiver, owner, assets, shares)](#ERC4626-_withdraw-address-address-address-uint256-uint256-) +- [_decimalsOffset()](#ERC4626-_decimalsOffset--) +#### IERC4626 +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### IERC4626 +- [Deposit(sender, owner, assets, shares)](#IERC4626-Deposit-address-address-uint256-uint256-) +- [Withdraw(sender, receiver, owner, assets, shares)](#IERC4626-Withdraw-address-address-address-uint256-uint256-) +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

constructor(contract IERC20 asset_)

+
+

internal

+# +
+
+
+ +Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). + +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This +"original" value is cached during construction of the vault contract. If this read operation fails (e.g., the +asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + +See [`IERC20Metadata.decimals`](#IERC20Metadata-decimals--). + +
+
+ + + +
+
+

asset() → address

+
+

public

+# +
+
+
+ +See [`IERC4626.asset`](../interfaces#IERC4626-asset--). + +
+
+ + + +
+
+

totalAssets() → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.totalAssets`](../interfaces#IERC4626-totalAssets--). + +
+
+ + + +
+
+

convertToShares(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.convertToShares`](../interfaces#IERC4626-convertToShares-uint256-). + +
+
+ + + +
+
+

convertToAssets(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.convertToAssets`](../interfaces#IERC4626-convertToAssets-uint256-). + +
+
+ + + +
+
+

maxDeposit(address) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.maxDeposit`](../interfaces#IERC4626-maxDeposit-address-). + +
+
+ + + +
+
+

maxMint(address) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.maxMint`](../interfaces#IERC4626-maxMint-address-). + +
+
+ + + +
+
+

maxWithdraw(address owner) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.maxWithdraw`](../interfaces#IERC4626-maxWithdraw-address-). + +
+
+ + + +
+
+

maxRedeem(address owner) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.maxRedeem`](../interfaces#IERC4626-maxRedeem-address-). + +
+
+ + + +
+
+

previewDeposit(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.previewDeposit`](../interfaces#IERC4626-previewDeposit-uint256-). + +
+
+ + + +
+
+

previewMint(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.previewMint`](../interfaces#IERC4626-previewMint-uint256-). + +
+
+ + + +
+
+

previewWithdraw(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.previewWithdraw`](../interfaces#IERC4626-previewWithdraw-uint256-). + +
+
+ + + +
+
+

previewRedeem(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.previewRedeem`](../interfaces#IERC4626-previewRedeem-uint256-). + +
+
+ + + +
+
+

deposit(uint256 assets, address receiver) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.deposit`](../interfaces#IERC4626-deposit-uint256-address-). + +
+
+ + + +
+
+

mint(uint256 shares, address receiver) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.mint`](../interfaces#IERC4626-mint-uint256-address-). + +As opposed to [`IERC4626.deposit`](../interfaces#IERC4626-deposit-uint256-address-), minting is allowed even if the vault is in a state where the price of a share is zero. +In this case, the shares will be minted without requiring any assets to be deposited. + +
+
+ + + +
+
+

withdraw(uint256 assets, address receiver, address owner) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.withdraw`](../interfaces#IERC4626-withdraw-uint256-address-address-). + +
+
+ + + +
+
+

redeem(uint256 shares, address receiver, address owner) → uint256

+
+

public

+# +
+
+
+ +See [`IERC4626.redeem`](../interfaces#IERC4626-redeem-uint256-address-address-). + +
+
+ + + +
+
+

_convertToShares(uint256 assets, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Internal conversion function (from assets to shares) with support for rounding direction. + +
+
+ + + +
+
+

_convertToAssets(uint256 shares, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Internal conversion function (from shares to assets) with support for rounding direction. + +
+
+ + + +
+
+

_deposit(address caller, address receiver, uint256 assets, uint256 shares)

+
+

internal

+# +
+
+
+ +Deposit/mint common workflow. + +
+
+ + + +
+
+

_withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)

+
+

internal

+# +
+
+
+ +Withdraw/redeem common workflow. + +
+
+ + + +
+
+

_decimalsOffset() → uint8

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `IERC20Metadata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +``` + +Interface for the optional metadata functions from the ERC20 standard. + +_Available since v4.1._ + +
+

Functions

+
+- [name()](#IERC20Metadata-name--) +- [symbol()](#IERC20Metadata-symbol--) +- [decimals()](#IERC20Metadata-decimals--) +#### IERC20 +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, amount)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, amount)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the symbol of the token. + +
+
+ + + +
+
+

decimals() → uint8

+
+

external

+# +
+
+
+ +Returns the decimals places of the token. + +
+
+ + + +
+ +## `IERC20Permit` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +``` + +Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in +[EIP-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Adds the [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) method, which can be used to change an account's ERC20 allowance (see [`IERC20.allowance`](#IERC20-allowance-address-address-)) by +presenting a message signed by the account. By not relying on [`IERC20.approve`](#IERC20-approve-address-uint256-), the token holder account doesn't +need to send a transaction, and thus is not required to hold Ether at all. + +==== Security Considerations + +There are two important considerations concerning the use of `permit`. The first is that a valid permit signature +expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be +considered as an intention to spend the allowance in any specific way. The second is that because permits have +built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should +take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be +generally recommended is: + +```solidity +function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + doThing(..., value); +} + +function doThing(..., uint256 value) public { + token.safeTransferFrom(msg.sender, address(this), value); + ... +} +``` + +Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of +`try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also +[`SafeERC20.safeTransferFrom`](#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-)). + +Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so +contracts should have entry points that don't rely on permit. + +
+

Functions

+
+- [permit(owner, spender, value, deadline, v, r, s)](#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#IERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#IERC20Permit-DOMAIN_SEPARATOR--) +
+
+ + + +
+
+

permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)

+
+

external

+# +
+
+
+ +Sets `value` as the allowance of `spender` over ``owner``'s tokens, +given ``owner``'s signed approval. + + +The same issues [`IERC20.approve`](#IERC20-approve-address-uint256-) has related to transaction +ordering also apply here. + + +Emits an [`IERC20.Approval`](#IERC20-Approval-address-address-uint256-) event. + +Requirements: + +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use ``owner``'s current nonce (see [`Votes.nonces`](../governance#Votes-nonces-address-)). + +For more information on the signature format, see the +[relevant EIP +section](https://eips.ethereum.org/EIPS/eip-2612#specification). + +CAUTION: See Security Considerations above. + +
+
+ + + +
+
+

nonces(address owner) → uint256

+
+

external

+# +
+
+
+ +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-). + +Every successful call to [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) increases ``owner``'s nonce by one. This +prevents a signature from being used multiple times. + +
+
+ + + +
+
+

DOMAIN_SEPARATOR() → bytes32

+
+

external

+# +
+
+
+ +Returns the domain separator used in the encoding of the signature for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-), as defined by [`EIP712`](../utils#EIP712). + +
+
+ + + +
+ +## `ERC20PresetFixedSupply` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; +``` + +[`ERC20`](#ERC20) token, including: + + - Preminted initial supply + - Ability for holders to burn (destroy) their tokens + - No access control mechanism (for minting/pausing) and hence no governance + +This contract uses [`ERC20Burnable`](#ERC20Burnable) to include burn capabilities - head to +its documentation for details. + +_Available since v3.4._ + +_Deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/)._ + +
+

Functions

+
+- [constructor(name, symbol, initialSupply, owner)](#ERC20PresetFixedSupply-constructor-string-string-uint256-address-) +#### ERC20Burnable +- [burn(amount)](#ERC20Burnable-burn-uint256-) +- [burnFrom(account, amount)](#ERC20Burnable-burnFrom-address-uint256-) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(from, to, amount)](#ERC20-_beforeTokenTransfer-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +
+
+ +
+

Events

+
+#### ERC20Burnable +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

constructor(string name, string symbol, uint256 initialSupply, address owner)

+
+

public

+# +
+
+
+ +Mints `initialSupply` amount of token and transfers them to `owner`. + +See [`ERC20.constructor`](#ERC20-constructor-string-string-). + +
+
+ + + +
+ +## `ERC20PresetMinterPauser` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; +``` + +[`ERC20`](#ERC20) token, including: + + - ability for holders to burn (destroy) their tokens + - a minter role that allows for token minting (creation) + - a pauser role that allows to stop all token transfers + +This contract uses [`AccessControl`](../access#AccessControl) to lock permissioned functions using the +different roles - head to its documentation for details. + +The account that deploys the contract will be granted the minter and pauser +roles, as well as the default admin role, which will let it grant both minter +and pauser roles to other accounts. + +_Deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/)._ + +
+

Functions

+
+- [constructor(name, symbol)](#ERC20PresetMinterPauser-constructor-string-string-) +- [mint(to, amount)](#ERC20PresetMinterPauser-mint-address-uint256-) +- [pause()](#ERC20PresetMinterPauser-pause--) +- [unpause()](#ERC20PresetMinterPauser-unpause--) +- [_beforeTokenTransfer(from, to, amount)](#ERC20PresetMinterPauser-_beforeTokenTransfer-address-address-uint256-) +- [MINTER_ROLE()](#ERC20PresetMinterPauser-MINTER_ROLE-bytes32) +- [PAUSER_ROLE()](#ERC20PresetMinterPauser-PAUSER_ROLE-bytes32) +#### ERC20Pausable +#### Pausable +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC20Burnable +- [burn(amount)](#ERC20Burnable-burn-uint256-) +- [burnFrom(account, amount)](#ERC20Burnable-burnFrom-address-uint256-) +#### ERC20 +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, amount)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, amount)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, amount)](#ERC20-transferFrom-address-address-uint256-) +- [increaseAllowance(spender, addedValue)](#ERC20-increaseAllowance-address-uint256-) +- [decreaseAllowance(spender, subtractedValue)](#ERC20-decreaseAllowance-address-uint256-) +- [_transfer(from, to, amount)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, amount)](#ERC20-_mint-address-uint256-) +- [_burn(account, amount)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, amount)](#ERC20-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC20-_spendAllowance-address-address-uint256-) +- [_afterTokenTransfer(from, to, amount)](#ERC20-_afterTokenTransfer-address-address-uint256-) +#### IERC20Metadata +#### IERC20 +#### AccessControlEnumerable +- [supportsInterface(interfaceId)](#AccessControlEnumerable-supportsInterface-bytes4-) +- [getRoleMember(role, index)](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#AccessControlEnumerable-getRoleMemberCount-bytes32-) +- [_grantRole(role, account)](#AccessControlEnumerable-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlEnumerable-_revokeRole-bytes32-address-) +#### AccessControl +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +
+
+ +
+

Events

+
+#### ERC20Pausable +#### Pausable +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC20Burnable +#### ERC20 +#### IERC20Metadata +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +#### AccessControlEnumerable +#### AccessControl +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

constructor(string name, string symbol)

+
+

public

+# +
+
+
+ +Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the +account that deploys the contract. + +See [`ERC20.constructor`](#ERC20-constructor-string-string-). + +
+
+ + + +
+
+

mint(address to, uint256 amount)

+
+

public

+# +
+
+
+ +Creates `amount` new tokens for `to`. + +See [`ERC20._mint`](#ERC20-_mint-address-uint256-). + +Requirements: + +- the caller must have the `MINTER_ROLE`. + +
+
+ + + +
+
+

pause()

+
+

public

+# +
+
+
+ +Pauses all token transfers. + +See [`ERC20Pausable`](#ERC20Pausable) and [`Pausable._pause`](../security#Pausable-_pause--). + +Requirements: + +- the caller must have the `PAUSER_ROLE`. + +
+
+ + + +
+
+

unpause()

+
+

public

+# +
+
+
+ +Unpauses all token transfers. + +See [`ERC20Pausable`](#ERC20Pausable) and [`Pausable._unpause`](../security#Pausable-_unpause--). + +Requirements: + +- the caller must have the `PAUSER_ROLE`. + +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

MINTER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

PAUSER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `SafeERC20` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +``` + +Wrappers around ERC20 operations that throw on failure (when the token +contract returns false). Tokens that return no value (and instead revert or +throw on failure) are also supported, non-reverting calls are assumed to be +successful. +To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, +which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + +
+

Functions

+
+- [safeTransfer(token, to, value)](#SafeERC20-safeTransfer-contract-IERC20-address-uint256-) +- [safeTransferFrom(token, from, to, value)](#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-) +- [safeApprove(token, spender, value)](#SafeERC20-safeApprove-contract-IERC20-address-uint256-) +- [safeIncreaseAllowance(token, spender, value)](#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-) +- [safeDecreaseAllowance(token, spender, value)](#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-) +- [forceApprove(token, spender, value)](#SafeERC20-forceApprove-contract-IERC20-address-uint256-) +- [safePermit(token, owner, spender, value, deadline, v, r, s)](#SafeERC20-safePermit-contract-IERC20Permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +
+
+ + + +
+
+

safeTransfer(contract IERC20 token, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, +non-reverting calls are assumed to be successful. + +
+
+ + + +
+
+

safeTransferFrom(contract IERC20 token, address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the +calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + +
+
+ + + +
+
+

safeApprove(contract IERC20 token, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Deprecated. This function has issues similar to the ones found in +[`IERC20.approve`](#IERC20-approve-address-uint256-), and its usage is discouraged. + +Whenever possible, use [`SafeERC20.safeIncreaseAllowance`](#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-) and +[`SafeERC20.safeDecreaseAllowance`](#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-) instead. + +
+
+ + + +
+
+

safeIncreaseAllowance(contract IERC20 token, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, +non-reverting calls are assumed to be successful. + +
+
+ + + +
+
+

safeDecreaseAllowance(contract IERC20 token, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, +non-reverting calls are assumed to be successful. + +
+
+ + + +
+
+

forceApprove(contract IERC20 token, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, +non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval +to be set to zero before setting it to a non-zero value, such as USDT. + +
+
+ + + +
+
+

safePermit(contract IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)

+
+

internal

+# +
+
+
+ +Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. +Revert on invalid signature. + +
+
+ + + +
+ +## `TokenTimelock` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/utils/TokenTimelock.sol"; +``` + +A token holder contract that will allow a beneficiary to extract the +tokens after a given release time. + +Useful for simple vesting schedules like "advisors get all of their tokens +after 1 year". + +
+

Functions

+
+- [constructor(token_, beneficiary_, releaseTime_)](#TokenTimelock-constructor-contract-IERC20-address-uint256-) +- [token()](#TokenTimelock-token--) +- [beneficiary()](#TokenTimelock-beneficiary--) +- [releaseTime()](#TokenTimelock-releaseTime--) +- [release()](#TokenTimelock-release--) +
+
+ + + +
+
+

constructor(contract IERC20 token_, address beneficiary_, uint256 releaseTime_)

+
+

public

+# +
+
+
+ +Deploys a timelock instance that is able to hold the token specified, and will only release it to +`beneficiary_` when [`PaymentSplitter.release`](../finance#PaymentSplitter-release-contract-IERC20-address-) is invoked after `releaseTime_`. The release time is specified as a Unix timestamp +(in seconds). + +
+
+ + + +
+
+

token() → contract IERC20

+
+

public

+# +
+
+
+ +Returns the token being held. + +
+
+ + + +
+
+

beneficiary() → address

+
+

public

+# +
+
+
+ +Returns the beneficiary that will receive the tokens. + +
+
+ + + +
+
+

releaseTime() → uint256

+
+

public

+# +
+
+
+ +Returns the time when the tokens are released in seconds since Unix epoch (i.e. Unix timestamp). + +
+
+ + + +
+
+

release()

+
+

public

+# +
+
+
+ +Transfers tokens held by the timelock to the beneficiary. Will only succeed if invoked after the release +time. + +
+
diff --git a/docs/content/contracts/4.x/api/token/ERC721.mdx b/docs/content/contracts/4.x/api/token/ERC721.mdx new file mode 100644 index 00000000..f520501e --- /dev/null +++ b/docs/content/contracts/4.x/api/token/ERC721.mdx @@ -0,0 +1,2885 @@ +--- +title: "ERC721" +description: "Smart contract ERC721 utilities and implementations" +--- + +This set of interfaces, contracts, and utilities are all related to the [ERC721 Non-Fungible Token Standard](https://eips.ethereum.org/EIPS/eip-721). + + +For a walk through on how to create an ERC721 token read our [ERC721 guide](/contracts/4.x/erc721). + + +The EIP specifies four interfaces: + +* [`IERC721`](#IERC721): Core functionality required in all compliant implementation. +* [`IERC721Metadata`](#IERC721Metadata): Optional extension that adds name, symbol, and token URI, almost always included. +* [`IERC721Enumerable`](#IERC721Enumerable): Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* [`IERC721Receiver`](#IERC721Receiver): An interface that must be implemented by contracts if they want to accept tokens through `safeTransferFrom`. + +OpenZeppelin Contracts provides implementations of all four interfaces: + +* [`ERC721`](#ERC721): The core and metadata extensions, with a base URI mechanism. +* [`ERC721Enumerable`](#ERC721Enumerable): The enumerable extension. +* [`ERC721Holder`](#ERC721Holder): A bare bones implementation of the receiver interface. + +Additionally there are a few of other extensions: + +* [`ERC721Consecutive`](#ERC721Consecutive): An implementation of [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) for minting batchs of tokens during construction, in accordance with ERC721. +* [`ERC721URIStorage`](#ERC721URIStorage): A more flexible but more expensive way of storing metadata. +* [`ERC721Votes`](#ERC721Votes): Support for voting and vote delegation. +* [`ERC721Royalty`](#ERC721Royalty): A way to signal royalty information following ERC2981. +* [`ERC721Pausable`](#ERC721Pausable): A primitive to pause contract operation. +* [`ERC721Burnable`](#ERC721Burnable): A way for token holders to burn their own tokens. +* [`ERC721Wrapper`](#ERC721Wrapper): Wrapper to create an ERC721 backed by another ERC721, with deposit and withdraw methods. Useful in conjunction with [`ERC721Votes`](#ERC721Votes). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC721 (such as [`_mint`](#ERC721-_mint-address-uint256-)) and expose them as external functions in the way they prefer. On the other hand, [ERC721 Presets](/contracts/4.x/erc721#preset-erc721-contract) (such as [`ERC721PresetMinterPauserAutoId`](#ERC721PresetMinterPauserAutoId)) are designed using opinionated patterns to provide developers with ready to use, deployable contracts. + + +## Core + +[`IERC721`](#IERC721) + +[`IERC721Metadata`](#IERC721Metadata) + +[`IERC721Enumerable`](#IERC721Enumerable) + +[`ERC721`](#ERC721) + +[`ERC721Enumerable`](#ERC721Enumerable) + +[`IERC721Receiver`](#IERC721Receiver) + +## Extensions + +[`ERC721Pausable`](#ERC721Pausable) + +[`ERC721Burnable`](#ERC721Burnable) + +[`ERC721Consecutive`](#ERC721Consecutive) + +[`ERC721URIStorage`](#ERC721URIStorage) + +[`ERC721Votes`](#ERC721Votes) + +[`ERC721Royalty`](#ERC721Royalty) + +[`ERC721Wrapper`](#ERC721Wrapper) + +## Presets + +These contracts are preconfigured combinations of the above features. They can be used through inheritance or as models to copy and paste their source code. + +[`ERC721PresetMinterPauserAutoId`](#ERC721PresetMinterPauserAutoId) + +## Utilities + +[`ERC721Holder`](#ERC721Holder) + + + +
+ +## `ERC721` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +``` + +Implementation of [ERC721](https://eips.ethereum.org/EIPS/eip-721) Non-Fungible Token Standard, including +the Metadata extension, but not including the Enumerable extension, which is available separately as +[`ERC721Enumerable`](#ERC721Enumerable). + +
+

Functions

+
+- [constructor(name_, symbol_)](#ERC721-constructor-string-string-) +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(string name_, string symbol_)

+
+

public

+# +
+
+
+ +Initializes the contract by setting a `name` and a `symbol` to the token collection. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

balanceOf(address owner) → uint256

+
+

public

+# +
+
+
+ +See [`IERC721.balanceOf`](#IERC721-balanceOf-address-). + +
+
+ + + +
+
+

ownerOf(uint256 tokenId) → address

+
+

public

+# +
+
+
+ +See [`IERC721.ownerOf`](#IERC721-ownerOf-uint256-). + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +See [`IERC721Metadata.name`](#IERC721Metadata-name--). + +
+
+ + + +
+
+

symbol() → string

+
+

public

+# +
+
+
+ +See [`IERC721Metadata.symbol`](#IERC721Metadata-symbol--). + +
+
+ + + +
+
+

tokenURI(uint256 tokenId) → string

+
+

public

+# +
+
+
+ +See [`IERC721Metadata.tokenURI`](#IERC721Metadata-tokenURI-uint256-). + +
+
+ + + +
+
+

_baseURI() → string

+
+

internal

+# +
+
+
+ +Base URI for computing [`ERC721.tokenURI`](#ERC721-tokenURI-uint256-). If set, the resulting URI for each +token will be the concatenation of the `baseURI` and the `tokenId`. Empty +by default, can be overridden in child contracts. + +
+
+ + + +
+
+

approve(address to, uint256 tokenId)

+
+

public

+# +
+
+
+ +See [`IERC721.approve`](#IERC721-approve-address-uint256-). + +
+
+ + + +
+
+

getApproved(uint256 tokenId) → address

+
+

public

+# +
+
+
+ +See [`IERC721.getApproved`](#IERC721-getApproved-uint256-). + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

public

+# +
+
+
+ +See [`IERC721.setApprovalForAll`](#IERC721-setApprovalForAll-address-bool-). + +
+
+ + + +
+
+

isApprovedForAll(address owner, address operator) → bool

+
+

public

+# +
+
+
+ +See [`IERC721.isApprovedForAll`](#IERC721-isApprovedForAll-address-address-). + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 tokenId)

+
+

public

+# +
+
+
+ +See [`IERC721.transferFrom`](#IERC721-transferFrom-address-address-uint256-). + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId)

+
+

public

+# +
+
+
+ +See [`IERC721.safeTransferFrom`](#IERC721-safeTransferFrom-address-address-uint256-). + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId, bytes data)

+
+

public

+# +
+
+
+ +See [`IERC721.safeTransferFrom`](#IERC721-safeTransferFrom-address-address-uint256-). + +
+
+ + + +
+
+

_safeTransfer(address from, address to, uint256 tokenId, bytes data)

+
+

internal

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC721 protocol to prevent tokens from being forever locked. + +`data` is additional data, it has no specified format and it is sent in call to `to`. + +This internal function is equivalent to [`ERC1155.safeTransferFrom`](/contracts/4.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-), and can be used to e.g. +implement alternative mechanisms to perform token transfer, such as signature-based. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon a safe transfer. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

_ownerOf(uint256 tokenId) → address

+
+

internal

+# +
+
+
+ +Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + +
+
+ + + +
+
+

_exists(uint256 tokenId) → bool

+
+

internal

+# +
+
+
+ +Returns whether `tokenId` exists. + +Tokens can be managed by their owner or approved accounts via [`ERC20.approve`](/contracts/4.x/api/token/ERC20#ERC20-approve-address-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/4.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). + +Tokens start existing when they are minted (`_mint`), +and stop existing when they are burned (`_burn`). + +
+
+ + + +
+
+

_isApprovedOrOwner(address spender, uint256 tokenId) → bool

+
+

internal

+# +
+
+
+ +Returns whether `spender` is allowed to manage `tokenId`. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

_safeMint(address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Safely mints `tokenId` and transfers it to `to`. + +Requirements: + +- `tokenId` must not exist. +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon a safe transfer. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

_safeMint(address to, uint256 tokenId, bytes data)

+
+

internal

+# +
+
+
+ +Same as [`_safeMint`](#ERC721-_safeMint-address-uint256-), with an additional `data` parameter which is +forwarded in [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) to contract recipients. + +
+
+ + + +
+
+

_mint(address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Mints `tokenId` and transfers it to `to`. + + +Usage of this method is discouraged, use [`ERC721._safeMint`](#ERC721-_safeMint-address-uint256-bytes-) whenever possible + + +Requirements: + +- `tokenId` must not exist. +- `to` cannot be the zero address. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

_burn(uint256 tokenId)

+
+

internal

+# +
+
+
+ +Destroys `tokenId`. +The approval is cleared when the token is burned. +This is an internal function that does not check if the sender is authorized to operate on the token. + +Requirements: + +- `tokenId` must exist. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

_transfer(address from, address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Transfers `tokenId` from `from` to `to`. + As opposed to [`ERC20.transferFrom`](/contracts/4.x/api/token/ERC20#ERC20-transferFrom-address-address-uint256-), this imposes no restrictions on msg.sender. + +Requirements: + +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

_approve(address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Approve `to` to operate on `tokenId` + +Emits an [`IERC20.Approval`](/contracts/4.x/api/token/ERC20#IERC20-Approval-address-address-uint256-) event. + +
+
+ + + +
+
+

_setApprovalForAll(address owner, address operator, bool approved)

+
+

internal

+# +
+
+
+ +Approve `operator` to operate on all of `owner` tokens + +Emits an [`IERC1155.ApprovalForAll`](/contracts/4.x/api/token/ERC1155#IERC1155-ApprovalForAll-address-address-bool-) event. + +
+
+ + + +
+
+

_requireMinted(uint256 tokenId)

+
+

internal

+# +
+
+
+ +Reverts if the `tokenId` has not been minted yet. + +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +Hook that is called before any token transfer. This includes minting and burning. If [`ERC721Consecutive`](#ERC721Consecutive) is +used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1. + +Calling conditions: + +- When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`. +- When `from` is zero, the tokens will be minted for `to`. +- When `to` is zero, ``from``'s tokens will be burned. +- `from` and `to` are never both zero. +- `batchSize` is non-zero. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+
+

_afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +Hook that is called after any token transfer. This includes minting and burning. If [`ERC721Consecutive`](#ERC721Consecutive) is +used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1. + +Calling conditions: + +- When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`. +- When `from` is zero, the tokens were minted for `to`. +- When `to` is zero, ``from``'s tokens were burned. +- `from` and `to` are never both zero. +- `batchSize` is non-zero. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+
+

__unsafe_increaseBalance(address account, uint256 amount)

+
+

internal

+# +
+
+
+ +Unsafe write access to the balances, used by extensions that "mint" tokens using an [`ERC721.ownerOf`](#ERC721-ownerOf-uint256-) override. + + +Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant +being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such +that `ownerOf(tokenId)` is `a`. + + +
+
+ + + +
+ +## `IERC721` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +``` + +Required interface of an ERC721 compliant contract. + +
+

Functions

+
+- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 +
+
+ + + +
+
+

balanceOf(address owner) → uint256 balance

+
+

external

+# +
+
+
+ +Returns the number of tokens in ``owner``'s account. + +
+
+ + + +
+
+

ownerOf(uint256 tokenId) → address owner

+
+

external

+# +
+
+
+ +Returns the owner of the `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId, bytes data)

+
+

external

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either [`ERC20.approve`](/contracts/4.x/api/token/ERC20#ERC20-approve-address-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/4.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon a safe transfer. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId)

+
+

external

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC721 protocol to prevent tokens from being forever locked. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must have been allowed to move this token by either [`ERC20.approve`](/contracts/4.x/api/token/ERC20#ERC20-approve-address-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/4.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon a safe transfer. + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 tokenId)

+
+

external

+# +
+
+
+ +Transfers `tokenId` token from `from` to `to`. + + +Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721 +or else they may be permanently lost. Usage of [`ERC1155.safeTransferFrom`](/contracts/4.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) prevents loss, though the caller must +understand this adds an external call which potentially creates a reentrancy vulnerability. + + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either [`ERC20.approve`](/contracts/4.x/api/token/ERC20#ERC20-approve-address-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/4.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). + +Emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event. + +
+
+ + + +
+
+

approve(address to, uint256 tokenId)

+
+

external

+# +
+
+
+ +Gives permission to `to` to transfer `tokenId` token to another account. +The approval is cleared when the token is transferred. + +Only a single account can be approved at a time, so approving the zero address clears previous approvals. + +Requirements: + +- The caller must own the token or be an approved operator. +- `tokenId` must exist. + +Emits an [`IERC20.Approval`](/contracts/4.x/api/token/ERC20#IERC20-Approval-address-address-uint256-) event. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

external

+# +
+
+
+ +Approve or remove `operator` as an operator for the caller. +Operators can call [`ERC20.transferFrom`](/contracts/4.x/api/token/ERC20#ERC20-transferFrom-address-address-uint256-) or [`ERC1155.safeTransferFrom`](/contracts/4.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) for any token owned by the caller. + +Requirements: + +- The `operator` cannot be the caller. + +Emits an [`IERC1155.ApprovalForAll`](/contracts/4.x/api/token/ERC1155#IERC1155-ApprovalForAll-address-address-bool-) event. + +
+
+ + + +
+
+

getApproved(uint256 tokenId) → address operator

+
+

external

+# +
+
+
+ +Returns the account approved for `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

isApprovedForAll(address owner, address operator) → bool

+
+

external

+# +
+
+
+ +Returns if the `operator` is allowed to manage all of the assets of `owner`. + +See [`ERC1155.setApprovalForAll`](/contracts/4.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-) + +
+
+ + + +
+
+

Transfer(address indexed from, address indexed to, uint256 indexed tokenId)

+
+

event

+# +
+
+ +
+ +Emitted when `tokenId` token is transferred from `from` to `to`. + +
+
+ + +
+
+

Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)

+
+

event

+# +
+
+ +
+ +Emitted when `owner` enables `approved` to manage the `tokenId` token. + +
+
+ + +
+
+

ApprovalForAll(address indexed owner, address indexed operator, bool approved)

+
+

event

+# +
+
+ +
+ +Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + +
+
+ + + +
+ +## `IERC721Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +``` + +Interface for any contract that wants to support safeTransfers +from ERC721 asset contracts. + +
+

Functions

+
+- [onERC721Received(operator, from, tokenId, data)](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) +
+
+ + + +
+
+

onERC721Received(address operator, address from, uint256 tokenId, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Whenever an [`IERC721`](#IERC721) `tokenId` token is transferred to this contract via [`IERC721.safeTransferFrom`](#IERC721-safeTransferFrom-address-address-uint256-) +by `operator` from `from`, this function is called. + +It must return its Solidity selector to confirm the token transfer. +If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted. + +The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + +
+
+ + + +
+ +## `ERC721Burnable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; +``` + +ERC721 Token that can be burned (destroyed). + +
+

Functions

+
+- [burn(tokenId)](#ERC721Burnable-burn-uint256-) +#### ERC721 +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

burn(uint256 tokenId)

+
+

public

+# +
+
+
+ +Burns `tokenId`. See [`ERC721._burn`](#ERC721-_burn-uint256-). + +Requirements: + +- The caller must own `tokenId` or be an approved operator. + +
+
+ + + +
+ +## `ERC721Consecutive` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Consecutive.sol"; +``` + +Implementation of the ERC2309 "Consecutive Transfer Extension" as defined in +[EIP-2309](https://eips.ethereum.org/EIPS/eip-2309). + +This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable +contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades. +These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers. + +Using this extension removes the ability to mint single tokens during contract construction. This ability is +regained after construction. During construction, only batch minting is allowed. + + +This extension bypasses the hooks [`ERC1155._beforeTokenTransfer`](/contracts/4.x/api/token/ERC1155#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) and [`ERC1155._afterTokenTransfer`](/contracts/4.x/api/token/ERC1155#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) for tokens minted in +batch. When using this extension, you should consider the `_beforeConsecutiveTokenTransfer` and +`_afterConsecutiveTokenTransfer` hooks in addition to [`ERC1155._beforeTokenTransfer`](/contracts/4.x/api/token/ERC1155#ERC1155-_beforeTokenTransfer-address-address-address-uint256---uint256---bytes-) and [`ERC1155._afterTokenTransfer`](/contracts/4.x/api/token/ERC1155#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-). + + + +When overriding [`ERC1155._afterTokenTransfer`](/contracts/4.x/api/token/ERC1155#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-), be careful about call ordering. [`ERC721.ownerOf`](#ERC721-ownerOf-uint256-) may return invalid +values during the [`ERC1155._afterTokenTransfer`](/contracts/4.x/api/token/ERC1155#ERC1155-_afterTokenTransfer-address-address-address-uint256---uint256---bytes-) execution if the super call is not called first. To be safe, execute the +super call before your custom logic. + + +_Available since v4.8._ + +
+

Functions

+
+- [_maxBatchSize()](#ERC721Consecutive-_maxBatchSize--) +- [_ownerOf(tokenId)](#ERC721Consecutive-_ownerOf-uint256-) +- [_mintConsecutive(to, batchSize)](#ERC721Consecutive-_mintConsecutive-address-uint96-) +- [_mint(to, tokenId)](#ERC721Consecutive-_mint-address-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721Consecutive-_afterTokenTransfer-address-address-uint256-uint256-) +#### ERC721 +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +#### IERC2309 +
+
+ +
+

Events

+
+#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +#### IERC2309 +- [ConsecutiveTransfer(fromTokenId, toTokenId, fromAddress, toAddress)](#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-) +
+
+ + + +
+
+

_maxBatchSize() → uint96

+
+

internal

+# +
+
+
+ +Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing +services that have to record one entry per token, and have protections against "unreasonably large" batches of +tokens. + +NOTE: Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being +correctly supported by off-chain indexing services (including marketplaces). + +
+
+ + + +
+
+

_ownerOf(uint256 tokenId) → address

+
+

internal

+# +
+
+
+ +See [`ERC721._ownerOf`](#ERC721-_ownerOf-uint256-). Override that checks the sequential ownership structure for tokens that have +been minted as part of a batch, and not yet transferred. + +
+
+ + + +
+
+

_mintConsecutive(address to, uint96 batchSize) → uint96

+
+

internal

+# +
+
+
+ +Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the +batch; if `batchSize` is 0, returns the number of consecutive ids minted so far. + +Requirements: + +- `batchSize` must not be greater than [`ERC721Consecutive._maxBatchSize`](#ERC721Consecutive-_maxBatchSize--). +- The function is called in the constructor of the contract (directly or indirectly). + +CAUTION: Does not emit a `Transfer` event. This is ERC721 compliant as long as it is done inside of the +constructor, which is enforced by this function. + +CAUTION: Does not invoke `onERC721Received` on the receiver. + +Emits a [`IERC2309.ConsecutiveTransfer`](../interfaces#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-) event. + +
+
+ + + +
+
+

_mint(address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +See [`ERC721._mint`](#ERC721-_mint-address-uint256-). Override version that restricts normal minting to after construction. + +Warning: Using [`ERC721Consecutive`](#ERC721Consecutive) prevents using [`ERC1155._mint`](/contracts/4.x/api/token/ERC1155#ERC1155-_mint-address-uint256-uint256-bytes-) during construction in favor of [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-). +After construction, [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-) is no longer available and [`ERC1155._mint`](/contracts/4.x/api/token/ERC1155#ERC1155-_mint-address-uint256-uint256-bytes-) becomes available. + +
+
+ + + +
+
+

_afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +See [`ERC721._afterTokenTransfer`](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-). Burning of tokens that have been sequentially minted must be explicit. + +
+
+ + + +
+ +## `ERC721Enumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +``` + +This implements an optional extension of [`ERC721`](#ERC721) defined in the EIP that adds +enumerability of all the token ids in the contract as well as all token ids owned by each +account. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC721Enumerable-supportsInterface-bytes4-) +- [tokenOfOwnerByIndex(owner, index)](#ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) +- [totalSupply()](#ERC721Enumerable-totalSupply--) +- [tokenByIndex(index)](#ERC721Enumerable-tokenByIndex-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721Enumerable-_beforeTokenTransfer-address-address-uint256-uint256-) +#### IERC721Enumerable +#### ERC721 +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### IERC721Enumerable +#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

tokenOfOwnerByIndex(address owner, uint256 index) → uint256

+
+

public

+# +
+
+
+ +See [`IERC721Enumerable.tokenOfOwnerByIndex`](#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-). + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

public

+# +
+
+
+ +See [`IERC721Enumerable.totalSupply`](#IERC721Enumerable-totalSupply--). + +
+
+ + + +
+
+

tokenByIndex(uint256 index) → uint256

+
+

public

+# +
+
+
+ +See [`IERC721Enumerable.tokenByIndex`](#IERC721Enumerable-tokenByIndex-uint256-). + +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +See [`ERC721._beforeTokenTransfer`](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-). + +
+
+ + + +
+ +## `ERC721Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol"; +``` + +ERC721 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + + +This contract does not include public pause and unpause functions. In +addition to inheriting this contract, you must define both functions, invoking the +[`Pausable._pause`](../security#Pausable-_pause--) and [`Pausable._unpause`](../security#Pausable-_unpause--) internal functions, with appropriate +access control, e.g. using [`AccessControl`](../access#AccessControl) or [`Ownable`](../access#Ownable). Not doing so will +make the contract unpausable. + + +
+

Functions

+
+- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721Pausable-_beforeTokenTransfer-address-address-uint256-uint256-) +#### Pausable +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC721 +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Pausable +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +See [`ERC721._beforeTokenTransfer`](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-). + +Requirements: + +- the contract must not be paused. + +
+
+ + + +
+ +## `ERC721Royalty` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol"; +``` + +Extension of ERC721 with the ERC2981 NFT Royalty Standard, a standardized way to retrieve royalty payment +information. + +Royalty information can be specified globally for all token ids via [`ERC2981._setDefaultRoyalty`](contracts/4.x/api/token/common#ERC2981-_setDefaultRoyalty-address-uint96-), and/or individually for +specific token ids via [`ERC2981._setTokenRoyalty`](contracts/4.x/api/token/common#ERC2981-_setTokenRoyalty-uint256-address-uint96-). The latter takes precedence over the first. + + +ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See +[Rationale](https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments) in the EIP. Marketplaces are expected to +voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. + + +_Available since v4.5._ + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC721Royalty-supportsInterface-bytes4-) +- [_burn(tokenId)](#ERC721Royalty-_burn-uint256-) +#### ERC721 +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC2981 +- [royaltyInfo(tokenId, salePrice)](#ERC2981-royaltyInfo-uint256-uint256-) +- [_feeDenominator()](#ERC2981-_feeDenominator--) +- [_setDefaultRoyalty(receiver, feeNumerator)](#ERC2981-_setDefaultRoyalty-address-uint96-) +- [_deleteDefaultRoyalty()](#ERC2981-_deleteDefaultRoyalty--) +- [_setTokenRoyalty(tokenId, receiver, feeNumerator)](#ERC2981-_setTokenRoyalty-uint256-address-uint96-) +- [_resetTokenRoyalty(tokenId)](#ERC2981-_resetTokenRoyalty-uint256-) +#### ERC165 +#### IERC2981 +#### IERC165 +
+
+ +
+

Events

+
+#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC2981 +#### ERC165 +#### IERC2981 +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

_burn(uint256 tokenId)

+
+

internal

+# +
+
+
+ +See [`ERC721._burn`](#ERC721-_burn-uint256-). This override additionally clears the royalty information for the token. + +
+
+ + + +
+ +## `ERC721URIStorage` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +``` + +ERC721 token with storage based token URI management. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC721URIStorage-supportsInterface-bytes4-) +- [tokenURI(tokenId)](#ERC721URIStorage-tokenURI-uint256-) +- [_setTokenURI(tokenId, _tokenURI)](#ERC721URIStorage-_setTokenURI-uint256-string-) +- [_burn(tokenId)](#ERC721URIStorage-_burn-uint256-) +#### ERC721 +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC4906 +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### ERC721 +#### IERC721Metadata +#### IERC4906 +- [MetadataUpdate(_tokenId)](#IERC4906-MetadataUpdate-uint256-) +- [BatchMetadataUpdate(_fromTokenId, _toTokenId)](#IERC4906-BatchMetadataUpdate-uint256-uint256-) +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-) + +
+
+ + + +
+
+

tokenURI(uint256 tokenId) → string

+
+

public

+# +
+
+
+ +See [`IERC721Metadata.tokenURI`](#IERC721Metadata-tokenURI-uint256-). + +
+
+ + + +
+
+

_setTokenURI(uint256 tokenId, string _tokenURI)

+
+

internal

+# +
+
+
+ +Sets `_tokenURI` as the tokenURI of `tokenId`. + +Emits [`IERC4906.MetadataUpdate`](../interfaces#IERC4906-MetadataUpdate-uint256-). + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

_burn(uint256 tokenId)

+
+

internal

+# +
+
+
+ +See [`ERC721._burn`](#ERC721-_burn-uint256-). This override additionally checks to see if a +token-specific URI was set for the token, and if so, it deletes the token URI from +the storage mapping. + +
+
+ + + +
+ +## `ERC721Votes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol"; +``` + +Extension of ERC721 to support voting and delegation as implemented by [`Votes`](../governance#Votes), where each individual NFT counts +as 1 vote unit. + +Tokens do not count as votes until they are delegated, because votes must be tracked which incurs an additional cost +on every transfer. Token holders can either delegate to a trusted representative who will decide how to make use of +the votes in governance decisions, or they can delegate to themselves to be their own representative. + +_Available since v4.5._ + +
+

Functions

+
+- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721Votes-_afterTokenTransfer-address-address-uint256-uint256-) +- [_getVotingUnits(account)](#ERC721Votes-_getVotingUnits-address-) +#### Votes +- [clock()](#Votes-clock--) +- [CLOCK_MODE()](#Votes-CLOCK_MODE--) +- [getVotes(account)](#Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#Votes-getPastTotalSupply-uint256-) +- [_getTotalSupply()](#Votes-_getTotalSupply--) +- [delegates(account)](#Votes-delegates-address-) +- [delegate(delegatee)](#Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#Votes-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#Votes-_transferVotingUnits-address-address-uint256-) +- [_useNonce(owner)](#Votes-_useNonce-address-) +- [nonces(owner)](#Votes-nonces-address-) +- [DOMAIN_SEPARATOR()](#Votes-DOMAIN_SEPARATOR--) +#### IERC5805 +#### IVotes +#### IERC6372 +#### EIP712 +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +#### ERC721 +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### Votes +#### IERC5805 +#### IVotes +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousBalance, newBalance)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 +#### EIP712 +#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

_afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +See [`ERC721._afterTokenTransfer`](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-). Adjusts votes when tokens are transferred. + +Emits a [`IVotes.DelegateVotesChanged`](../governance#IVotes-DelegateVotesChanged-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_getVotingUnits(address account) → uint256

+
+

internal

+# +
+
+
+ +Returns the balance of `account`. + + +Overriding this function will likely result in incorrect vote tracking. + + +
+
+ + + +
+ +## `ERC721Wrapper` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Wrapper.sol"; +``` + +Extension of the ERC721 token contract to support token wrapping. + +Users can deposit and withdraw an "underlying token" and receive a "wrapped token" with a matching tokenId. This is useful +in conjunction with other modules. For example, combining this wrapping mechanism with [`ERC721Votes`](#ERC721Votes) will allow the +wrapping of an existing "basic" ERC721 into a governance token. + +_Available since v4.9.0_ + +
+

Functions

+
+- [constructor(underlyingToken)](#ERC721Wrapper-constructor-contract-IERC721-) +- [depositFor(account, tokenIds)](#ERC721Wrapper-depositFor-address-uint256---) +- [withdrawTo(account, tokenIds)](#ERC721Wrapper-withdrawTo-address-uint256---) +- [onERC721Received(, from, tokenId, )](#ERC721Wrapper-onERC721Received-address-address-uint256-bytes-) +- [_recover(account, tokenId)](#ERC721Wrapper-_recover-address-uint256-) +- [underlying()](#ERC721Wrapper-underlying--) +#### IERC721Receiver +#### ERC721 +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_beforeTokenTransfer-address-address-uint256-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### ERC165 +#### IERC165 +
+
+ +
+

Events

+
+#### IERC721Receiver +#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

constructor(contract IERC721 underlyingToken)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

depositFor(address account, uint256[] tokenIds) → bool

+
+

public

+# +
+
+
+ +Allow a user to deposit underlying tokens and mint the corresponding tokenIds. + +
+
+ + + +
+
+

withdrawTo(address account, uint256[] tokenIds) → bool

+
+

public

+# +
+
+
+ +Allow a user to burn wrapped tokens and withdraw the corresponding tokenIds of the underlying tokens. + +
+
+ + + +
+
+

onERC721Received(address, address from, uint256 tokenId, bytes) → bytes4

+
+

public

+# +
+
+
+ +Overrides [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) to allow minting on direct ERC721 transfers to +this contract. + +In case there's data attached, it validates that the operator is this contract, so only trusted data +is accepted from [`ERC20Wrapper.depositFor`](/contracts/4.x/api/token/ERC20#ERC20Wrapper-depositFor-address-uint256-). + + +Doesn't work with unsafe transfers (eg. [`IERC721.transferFrom`](#IERC721-transferFrom-address-address-uint256-)). Use [`ERC721Wrapper._recover`](#ERC721Wrapper-_recover-address-uint256-) +for recovering in that scenario. + + +
+
+ + + +
+
+

_recover(address account, uint256 tokenId) → uint256

+
+

internal

+# +
+
+
+ +Mint a wrapped token to cover any underlyingToken that would have been transferred by mistake. Internal +function that can be exposed with access control if desired. + +
+
+ + + +
+
+

underlying() → contract IERC721

+
+

public

+# +
+
+
+ +Returns the underlying token. + +
+
+ + + +
+ +## `IERC721Enumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; +``` + +See https://eips.ethereum.org/EIPS/eip-721 + +
+

Functions

+
+- [totalSupply()](#IERC721Enumerable-totalSupply--) +- [tokenOfOwnerByIndex(owner, index)](#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) +- [tokenByIndex(index)](#IERC721Enumerable-tokenByIndex-uint256-) +#### IERC721 +- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 +
+
+ + + +
+
+

totalSupply() → uint256

+
+

external

+# +
+
+
+ +Returns the total amount of tokens stored by the contract. + +
+
+ + + +
+
+

tokenOfOwnerByIndex(address owner, uint256 index) → uint256

+
+

external

+# +
+
+
+ +Returns a token ID owned by `owner` at a given `index` of its token list. +Use along with [`ERC1155.balanceOf`](/contracts/4.x/api/token/ERC1155#ERC1155-balanceOf-address-uint256-) to enumerate all of ``owner``'s tokens. + +
+
+ + + +
+
+

tokenByIndex(uint256 index) → uint256

+
+

external

+# +
+
+
+ +Returns a token ID at a given `index` of all the tokens stored by the contract. +Use along with [`ERC1155Supply.totalSupply`](/contracts/4.x/api/token/ERC1155#ERC1155Supply-totalSupply-uint256-) to enumerate all tokens. + +
+
+ + + +
+ +## `IERC721Metadata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +``` + +See https://eips.ethereum.org/EIPS/eip-721 + +
+

Functions

+
+- [name()](#IERC721Metadata-name--) +- [symbol()](#IERC721Metadata-symbol--) +- [tokenURI(tokenId)](#IERC721Metadata-tokenURI-uint256-) +#### IERC721 +- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the token collection name. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the token collection symbol. + +
+
+ + + +
+
+

tokenURI(uint256 tokenId) → string

+
+

external

+# +
+
+
+ +Returns the Uniform Resource Identifier (URI) for `tokenId` token. + +
+
+ + + +
+ +## `ERC721PresetMinterPauserAutoId` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol"; +``` + +[`ERC721`](#ERC721) token, including: + + - ability for holders to burn (destroy) their tokens + - a minter role that allows for token minting (creation) + - a pauser role that allows to stop all token transfers + - token ID and URI autogeneration + +This contract uses [`AccessControl`](../access#AccessControl) to lock permissioned functions using the +different roles - head to its documentation for details. + +The account that deploys the contract will be granted the minter and pauser +roles, as well as the default admin role, which will let it grant both minter +and pauser roles to other accounts. + +_Deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com/)._ + +
+

Functions

+
+- [constructor(name, symbol, baseTokenURI)](#ERC721PresetMinterPauserAutoId-constructor-string-string-string-) +- [_baseURI()](#ERC721PresetMinterPauserAutoId-_baseURI--) +- [mint(to)](#ERC721PresetMinterPauserAutoId-mint-address-) +- [pause()](#ERC721PresetMinterPauserAutoId-pause--) +- [unpause()](#ERC721PresetMinterPauserAutoId-unpause--) +- [_beforeTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721PresetMinterPauserAutoId-_beforeTokenTransfer-address-address-uint256-uint256-) +- [supportsInterface(interfaceId)](#ERC721PresetMinterPauserAutoId-supportsInterface-bytes4-) +- [MINTER_ROLE()](#ERC721PresetMinterPauserAutoId-MINTER_ROLE-bytes32) +- [PAUSER_ROLE()](#ERC721PresetMinterPauserAutoId-PAUSER_ROLE-bytes32) +#### ERC721Pausable +#### Pausable +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC721Burnable +- [burn(tokenId)](#ERC721Burnable-burn-uint256-) +#### ERC721Enumerable +- [tokenOfOwnerByIndex(owner, index)](#ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) +- [totalSupply()](#ERC721Enumerable-totalSupply--) +- [tokenByIndex(index)](#ERC721Enumerable-tokenByIndex-uint256-) +#### IERC721Enumerable +#### ERC721 +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_exists(tokenId)](#ERC721-_exists-uint256-) +- [_isApprovedOrOwner(spender, tokenId)](#ERC721-_isApprovedOrOwner-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_approve(to, tokenId)](#ERC721-_approve-address-uint256-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireMinted(tokenId)](#ERC721-_requireMinted-uint256-) +- [_afterTokenTransfer(from, to, firstTokenId, batchSize)](#ERC721-_afterTokenTransfer-address-address-uint256-uint256-) +- [__unsafe_increaseBalance(account, amount)](#ERC721-__unsafe_increaseBalance-address-uint256-) +#### IERC721Metadata +#### IERC721 +#### AccessControlEnumerable +- [getRoleMember(role, index)](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#AccessControlEnumerable-getRoleMemberCount-bytes32-) +- [_grantRole(role, account)](#AccessControlEnumerable-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlEnumerable-_revokeRole-bytes32-address-) +#### AccessControl +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControl-renounceRole-bytes32-address-) +- [_setupRole(role, account)](#AccessControl-_setupRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +
+
+ +
+

Events

+
+#### ERC721Pausable +#### Pausable +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC721Burnable +#### ERC721Enumerable +#### IERC721Enumerable +#### ERC721 +#### IERC721Metadata +#### IERC721 +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### AccessControlEnumerable +#### AccessControl +#### ERC165 +#### IERC165 +#### IAccessControlEnumerable +#### IAccessControl +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ + + +
+
+

constructor(string name, string symbol, string baseTokenURI)

+
+

public

+# +
+
+
+ +Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE` and `PAUSER_ROLE` to the +account that deploys the contract. + +Token URIs will be autogenerated based on `baseURI` and their token IDs. +See [`ERC721.tokenURI`](#ERC721-tokenURI-uint256-). + +
+
+ + + +
+
+

_baseURI() → string

+
+

internal

+# +
+
+
+ +Base URI for computing [`ERC721.tokenURI`](#ERC721-tokenURI-uint256-). If set, the resulting URI for each +token will be the concatenation of the `baseURI` and the `tokenId`. Empty +by default, can be overridden in child contracts. + +
+
+ + + +
+
+

mint(address to)

+
+

public

+# +
+
+
+ +Creates a new token for `to`. Its token ID will be automatically +assigned (and available on the emitted [`IERC721.Transfer`](#IERC721-Transfer-address-address-uint256-) event), and the token +URI autogenerated based on the base URI passed at construction. + +See [`ERC721._mint`](#ERC721-_mint-address-uint256-). + +Requirements: + +- the caller must have the `MINTER_ROLE`. + +
+
+ + + +
+
+

pause()

+
+

public

+# +
+
+
+ +Pauses all token transfers. + +See [`ERC721Pausable`](#ERC721Pausable) and [`Pausable._pause`](../security#Pausable-_pause--). + +Requirements: + +- the caller must have the `PAUSER_ROLE`. + +
+
+ + + +
+
+

unpause()

+
+

public

+# +
+
+
+ +Unpauses all token transfers. + +See [`ERC721Pausable`](#ERC721Pausable) and [`Pausable._unpause`](../security#Pausable-_unpause--). + +Requirements: + +- the caller must have the `PAUSER_ROLE`. + +
+
+ + + +
+
+

_beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

MINTER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

PAUSER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC721Holder` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +``` + +Implementation of the [`IERC721Receiver`](#IERC721Receiver) interface. + +Accepts all token transfers. +Make sure the contract is able to use its token with [`IERC721.safeTransferFrom`](#IERC721-safeTransferFrom-address-address-uint256-), [`IERC721.approve`](#IERC721-approve-address-uint256-) or [`IERC721.setApprovalForAll`](#IERC721-setApprovalForAll-address-bool-). + +
+

Functions

+
+- [onERC721Received(, , , )](#ERC721Holder-onERC721Received-address-address-uint256-bytes-) +#### IERC721Receiver +
+
+ + + +
+
+

onERC721Received(address, address, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-). + +Always returns `IERC721Receiver.onERC721Received.selector`. + +
+
diff --git a/docs/content/contracts/4.x/api/token/ERC777.mdx b/docs/content/contracts/4.x/api/token/ERC777.mdx new file mode 100644 index 00000000..741da0bb --- /dev/null +++ b/docs/content/contracts/4.x/api/token/ERC777.mdx @@ -0,0 +1,1291 @@ +--- +title: "ERC777" +description: "Smart contract ERC777 utilities and implementations" +--- + +**🔥 CAUTION**\ +As of v4.9, OpenZeppelin’s implementation of ERC-777 is deprecated and will be removed in the next major release. + +This set of interfaces and contracts are all related to the [ERC777 token standard](https://eips.ethereum.org/EIPS/eip-777). + + +For an overview of ERC777 tokens and a walk through on how to create a token contract read our [ERC777 guide](/contracts/4.x/erc777). + + +The token behavior itself is implemented in the core contracts: [`IERC777`](#IERC777), [`ERC777`](#ERC777). + +Additionally there are interfaces used to develop contracts that react to token movements: [`IERC777Sender`](#IERC777Sender), [`IERC777Recipient`](#IERC777Recipient). + +## Core + +[`IERC777`](#IERC777) + +[`ERC777`](#ERC777) + +## Hooks + +[`IERC777Sender`](#IERC777Sender) + +[`IERC777Recipient`](#IERC777Recipient) + +## Presets + +These contracts are preconfigured combinations of features. They can be used through inheritance or as models to copy and paste their source code. + +[`ERC777PresetFixedSupply`](#ERC777PresetFixedSupply) + + + +
+ +## `ERC777` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC777/ERC777.sol"; +``` + +Implementation of the [`IERC777`](#IERC777) interface. + +This implementation is agnostic to the way tokens are created. This means +that a supply mechanism has to be added in a derived contract using [`ERC1155._mint`](/contracts/4.x/api/token/ERC1155#ERC1155-_mint-address-uint256-uint256-bytes-). + +Support for ERC20 is included in this contract, as specified by the EIP: both +the ERC777 and ERC20 interfaces can be safely used when interacting with it. +Both [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) and [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) events are emitted on token +movements. + +Additionally, the [`IERC777.granularity`](#IERC777-granularity--) value is hard-coded to `1`, meaning that there +are no special restrictions in the amount of tokens that created, moved, or +destroyed. This makes integration with ERC20 applications seamless. + +CAUTION: This file is deprecated as of v4.9 and will be removed in the next major release. + +
+

Functions

+
+- [constructor(name_, symbol_, defaultOperators_)](#ERC777-constructor-string-string-address---) +- [name()](#ERC777-name--) +- [symbol()](#ERC777-symbol--) +- [decimals()](#ERC777-decimals--) +- [granularity()](#ERC777-granularity--) +- [totalSupply()](#ERC777-totalSupply--) +- [balanceOf(tokenHolder)](#ERC777-balanceOf-address-) +- [send(recipient, amount, data)](#ERC777-send-address-uint256-bytes-) +- [transfer(recipient, amount)](#ERC777-transfer-address-uint256-) +- [burn(amount, data)](#ERC777-burn-uint256-bytes-) +- [isOperatorFor(operator, tokenHolder)](#ERC777-isOperatorFor-address-address-) +- [authorizeOperator(operator)](#ERC777-authorizeOperator-address-) +- [revokeOperator(operator)](#ERC777-revokeOperator-address-) +- [defaultOperators()](#ERC777-defaultOperators--) +- [operatorSend(sender, recipient, amount, data, operatorData)](#ERC777-operatorSend-address-address-uint256-bytes-bytes-) +- [operatorBurn(account, amount, data, operatorData)](#ERC777-operatorBurn-address-uint256-bytes-bytes-) +- [allowance(holder, spender)](#ERC777-allowance-address-address-) +- [approve(spender, value)](#ERC777-approve-address-uint256-) +- [transferFrom(holder, recipient, amount)](#ERC777-transferFrom-address-address-uint256-) +- [_mint(account, amount, userData, operatorData)](#ERC777-_mint-address-uint256-bytes-bytes-) +- [_mint(account, amount, userData, operatorData, requireReceptionAck)](#ERC777-_mint-address-uint256-bytes-bytes-bool-) +- [_send(from, to, amount, userData, operatorData, requireReceptionAck)](#ERC777-_send-address-address-uint256-bytes-bytes-bool-) +- [_burn(from, amount, data, operatorData)](#ERC777-_burn-address-uint256-bytes-bytes-) +- [_approve(holder, spender, value)](#ERC777-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC777-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(operator, from, to, amount)](#ERC777-_beforeTokenTransfer-address-address-address-uint256-) +#### IERC20 +#### IERC777 +
+
+ +
+

Events

+
+#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +#### IERC777 +- [Minted(operator, to, amount, data, operatorData)](#IERC777-Minted-address-address-uint256-bytes-bytes-) +- [Burned(operator, from, amount, data, operatorData)](#IERC777-Burned-address-address-uint256-bytes-bytes-) +- [AuthorizedOperator(operator, tokenHolder)](#IERC777-AuthorizedOperator-address-address-) +- [RevokedOperator(operator, tokenHolder)](#IERC777-RevokedOperator-address-address-) +- [Sent(operator, from, to, amount, data, operatorData)](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

constructor(string name_, string symbol_, address[] defaultOperators_)

+
+

public

+# +
+
+
+ +`defaultOperators` may be an empty array. + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +See [`IERC777.name`](#IERC777-name--). + +
+
+ + + +
+
+

symbol() → string

+
+

public

+# +
+
+
+ +See [`IERC777.symbol`](#IERC777-symbol--). + +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +See [`ERC20.decimals`](/contracts/4.x/api/token/ERC20#ERC20-decimals--). + +Always returns 18, as per the +[ERC777 EIP](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility). + +
+
+ + + +
+
+

granularity() → uint256

+
+

public

+# +
+
+
+ +See [`IERC777.granularity`](#IERC777-granularity--). + +This implementation always returns `1`. + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

public

+# +
+
+
+ +See [`IERC777.totalSupply`](#IERC777-totalSupply--). + +
+
+ + + +
+
+

balanceOf(address tokenHolder) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of tokens owned by an account (`tokenHolder`). + +
+
+ + + +
+
+

send(address recipient, uint256 amount, bytes data)

+
+

public

+# +
+
+
+ +See [`IERC777.send`](#IERC777-send-address-uint256-bytes-). + +Also emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event for ERC20 compatibility. + +
+
+ + + +
+
+

transfer(address recipient, uint256 amount) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.transfer`](/contracts/4.x/api/token/ERC20#IERC20-transfer-address-uint256-). + +Unlike `send`, `recipient` is _not_ required to implement the [`IERC777Recipient`](#IERC777Recipient) +interface if it is a contract. + +Also emits a [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) event. + +
+
+ + + +
+
+

burn(uint256 amount, bytes data)

+
+

public

+# +
+
+
+ +See [`IERC777.burn`](#IERC777-burn-uint256-bytes-). + +Also emits a [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) event for ERC20 compatibility. + +
+
+ + + +
+
+

isOperatorFor(address operator, address tokenHolder) → bool

+
+

public

+# +
+
+
+ +See [`IERC777.isOperatorFor`](#IERC777-isOperatorFor-address-address-). + +
+
+ + + +
+
+

authorizeOperator(address operator)

+
+

public

+# +
+
+
+ +See [`IERC777.authorizeOperator`](#IERC777-authorizeOperator-address-). + +
+
+ + + +
+
+

revokeOperator(address operator)

+
+

public

+# +
+
+
+ +See [`IERC777.revokeOperator`](#IERC777-revokeOperator-address-). + +
+
+ + + +
+
+

defaultOperators() → address[]

+
+

public

+# +
+
+
+ +See [`IERC777.defaultOperators`](#IERC777-defaultOperators--). + +
+
+ + + +
+
+

operatorSend(address sender, address recipient, uint256 amount, bytes data, bytes operatorData)

+
+

public

+# +
+
+
+ +See [`IERC777.operatorSend`](#IERC777-operatorSend-address-address-uint256-bytes-bytes-). + +Emits [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) and [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) events. + +
+
+ + + +
+
+

operatorBurn(address account, uint256 amount, bytes data, bytes operatorData)

+
+

public

+# +
+
+
+ +See [`IERC777.operatorBurn`](#IERC777-operatorBurn-address-uint256-bytes-bytes-). + +Emits [`IERC777.Burned`](#IERC777-Burned-address-address-uint256-bytes-bytes-) and [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) events. + +
+
+ + + +
+
+

allowance(address holder, address spender) → uint256

+
+

public

+# +
+
+
+ +See [`IERC20.allowance`](/contracts/4.x/api/token/ERC20#IERC20-allowance-address-address-). + +Note that operator and allowance concepts are orthogonal: operators may +not have allowance, and accounts with allowance may not be operators +themselves. + +
+
+ + + +
+
+

approve(address spender, uint256 value) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.approve`](/contracts/4.x/api/token/ERC20#IERC20-approve-address-uint256-). + +NOTE: If `value` is the maximum `uint256`, the allowance is not updated on +`transferFrom`. This is semantically equivalent to an infinite approval. + +Note that accounts cannot have allowance issued by their operators. + +
+
+ + + +
+
+

transferFrom(address holder, address recipient, uint256 amount) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.transferFrom`](/contracts/4.x/api/token/ERC20#IERC20-transferFrom-address-address-uint256-). + +NOTE: Does not update the allowance if the current allowance +is the maximum `uint256`. + +Note that operator and allowance concepts are orthogonal: operators cannot +call `transferFrom` (unless they have allowance), and accounts with +allowance cannot call `operatorSend` (unless they are operators). + +Emits [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-), [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) and [`IERC20.Approval`](/contracts/4.x/api/token/ERC20#IERC20-Approval-address-address-uint256-) events. + +
+
+ + + +
+
+

_mint(address account, uint256 amount, bytes userData, bytes operatorData)

+
+

internal

+# +
+
+
+ +Creates `amount` tokens and assigns them to `account`, increasing +the total supply. + +If a send hook is registered for `account`, the corresponding function +will be called with the caller address as the `operator` and with +`userData` and `operatorData`. + +See [`IERC777Sender`](#IERC777Sender) and [`IERC777Recipient`](#IERC777Recipient). + +Emits [`IERC777.Minted`](#IERC777-Minted-address-address-uint256-bytes-bytes-) and [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) events. + +Requirements + +- `account` cannot be the zero address. +- if `account` is a contract, it must implement the [`IERC777Recipient`](#IERC777Recipient) +interface. + +
+
+ + + +
+
+

_mint(address account, uint256 amount, bytes userData, bytes operatorData, bool requireReceptionAck)

+
+

internal

+# +
+
+
+ +Creates `amount` tokens and assigns them to `account`, increasing +the total supply. + +If `requireReceptionAck` is set to true, and if a send hook is +registered for `account`, the corresponding function will be called with +`operator`, `data` and `operatorData`. + +See [`IERC777Sender`](#IERC777Sender) and [`IERC777Recipient`](#IERC777Recipient). + +Emits [`IERC777.Minted`](#IERC777-Minted-address-address-uint256-bytes-bytes-) and [`IERC20.Transfer`](/contracts/4.x/api/token/ERC20#IERC20-Transfer-address-address-uint256-) events. + +Requirements + +- `account` cannot be the zero address. +- if `account` is a contract, it must implement the [`IERC777Recipient`](#IERC777Recipient) +interface. + +
+
+ + + +
+
+

_send(address from, address to, uint256 amount, bytes userData, bytes operatorData, bool requireReceptionAck)

+
+

internal

+# +
+
+
+ +Send tokens + +
+
+ + + +
+
+

_burn(address from, uint256 amount, bytes data, bytes operatorData)

+
+

internal

+# +
+
+
+ +Burn tokens + +
+
+ + + +
+
+

_approve(address holder, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +See [`ERC20._approve`](/contracts/4.x/api/token/ERC20#ERC20-_approve-address-address-uint256-). + +Note that accounts cannot have allowance issued by their operators. + +
+
+ + + +
+
+

_spendAllowance(address owner, address spender, uint256 amount)

+
+

internal

+# +
+
+
+ +Updates `owner` s allowance for `spender` based on spent `amount`. + +Does not update the allowance amount in case of infinite allowance. +Revert if not enough allowance is available. + +Might emit an [`IERC20.Approval`](/contracts/4.x/api/token/ERC20#IERC20-Approval-address-address-uint256-) event. + +
+
+ + + +
+
+

_beforeTokenTransfer(address operator, address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Hook that is called before any token transfer. This includes +calls to [`ERC777.send`](#ERC777-send-address-uint256-bytes-), [`ERC20.transfer`](/contracts/4.x/api/token/ERC20#ERC20-transfer-address-uint256-), [`ERC777.operatorSend`](#ERC777-operatorSend-address-address-uint256-bytes-bytes-), [`ERC20.transferFrom`](/contracts/4.x/api/token/ERC20#ERC20-transferFrom-address-address-uint256-), minting and burning. + +Calling conditions: + +- when `from` and `to` are both non-zero, `amount` of ``from``'s tokens +will be to transferred to `to`. +- when `from` is zero, `amount` tokens will be minted for `to`. +- when `to` is zero, `amount` of ``from``'s tokens will be burned. +- `from` and `to` are never both zero. + +To learn more about hooks, head to xref:ROOT:extending-contracts#using-hooks[Using Hooks]. + +
+
+ + + +
+ +## `IERC777` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC777/IERC777.sol"; +``` + +Interface of the ERC777Token standard as defined in the EIP. + +This contract uses the +[ERC1820 registry standard](https://eips.ethereum.org/EIPS/eip-1820) to let +token holders and recipients react to token movements by using setting implementers +for the associated interfaces in said registry. See [`IERC1820Registry`](../utils#IERC1820Registry) and +[`ERC1820Implementer`](../utils#ERC1820Implementer). + +
+

Functions

+
+- [name()](#IERC777-name--) +- [symbol()](#IERC777-symbol--) +- [granularity()](#IERC777-granularity--) +- [totalSupply()](#IERC777-totalSupply--) +- [balanceOf(owner)](#IERC777-balanceOf-address-) +- [send(recipient, amount, data)](#IERC777-send-address-uint256-bytes-) +- [burn(amount, data)](#IERC777-burn-uint256-bytes-) +- [isOperatorFor(operator, tokenHolder)](#IERC777-isOperatorFor-address-address-) +- [authorizeOperator(operator)](#IERC777-authorizeOperator-address-) +- [revokeOperator(operator)](#IERC777-revokeOperator-address-) +- [defaultOperators()](#IERC777-defaultOperators--) +- [operatorSend(sender, recipient, amount, data, operatorData)](#IERC777-operatorSend-address-address-uint256-bytes-bytes-) +- [operatorBurn(account, amount, data, operatorData)](#IERC777-operatorBurn-address-uint256-bytes-bytes-) +
+
+ +
+

Events

+
+- [Minted(operator, to, amount, data, operatorData)](#IERC777-Minted-address-address-uint256-bytes-bytes-) +- [Burned(operator, from, amount, data, operatorData)](#IERC777-Burned-address-address-uint256-bytes-bytes-) +- [AuthorizedOperator(operator, tokenHolder)](#IERC777-AuthorizedOperator-address-address-) +- [RevokedOperator(operator, tokenHolder)](#IERC777-RevokedOperator-address-address-) +- [Sent(operator, from, to, amount, data, operatorData)](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the symbol of the token, usually a shorter version of the +name. + +
+
+ + + +
+
+

granularity() → uint256

+
+

external

+# +
+
+
+ +Returns the smallest part of the token that is not divisible. This +means all token operations (creation, movement and destruction) must have +amounts that are a multiple of this number. + +For most token contracts, this value will equal 1. + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens in existence. + +
+
+ + + +
+
+

balanceOf(address owner) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens owned by an account (`owner`). + +
+
+ + + +
+
+

send(address recipient, uint256 amount, bytes data)

+
+

external

+# +
+
+
+ +Moves `amount` tokens from the caller's account to `recipient`. + +If send or receive hooks are registered for the caller and `recipient`, +the corresponding functions will be called with `data` and empty +`operatorData`. See [`IERC777Sender`](#IERC777Sender) and [`IERC777Recipient`](#IERC777Recipient). + +Emits a [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) event. + +Requirements + +- the caller must have at least `amount` tokens. +- `recipient` cannot be the zero address. +- if `recipient` is a contract, it must implement the [`IERC777Recipient`](#IERC777Recipient) +interface. + +
+
+ + + +
+
+

burn(uint256 amount, bytes data)

+
+

external

+# +
+
+
+ +Destroys `amount` tokens from the caller's account, reducing the +total supply. + +If a send hook is registered for the caller, the corresponding function +will be called with `data` and empty `operatorData`. See [`IERC777Sender`](#IERC777Sender). + +Emits a [`IERC777.Burned`](#IERC777-Burned-address-address-uint256-bytes-bytes-) event. + +Requirements + +- the caller must have at least `amount` tokens. + +
+
+ + + +
+
+

isOperatorFor(address operator, address tokenHolder) → bool

+
+

external

+# +
+
+
+ +Returns true if an account is an operator of `tokenHolder`. +Operators can send and burn tokens on behalf of their owners. All +accounts are their own operator. + +See [`ERC777.operatorSend`](#ERC777-operatorSend-address-address-uint256-bytes-bytes-) and [`ERC777.operatorBurn`](#ERC777-operatorBurn-address-uint256-bytes-bytes-). + +
+
+ + + +
+
+

authorizeOperator(address operator)

+
+

external

+# +
+
+
+ +Make an account an operator of the caller. + +See [`ERC777.isOperatorFor`](#ERC777-isOperatorFor-address-address-). + +Emits an [`IERC777.AuthorizedOperator`](#IERC777-AuthorizedOperator-address-address-) event. + +Requirements + +- `operator` cannot be calling address. + +
+
+ + + +
+
+

revokeOperator(address operator)

+
+

external

+# +
+
+
+ +Revoke an account's operator status for the caller. + +See [`ERC777.isOperatorFor`](#ERC777-isOperatorFor-address-address-) and [`ERC777.defaultOperators`](#ERC777-defaultOperators--). + +Emits a [`IERC777.RevokedOperator`](#IERC777-RevokedOperator-address-address-) event. + +Requirements + +- `operator` cannot be calling address. + +
+
+ + + +
+
+

defaultOperators() → address[]

+
+

external

+# +
+
+
+ +Returns the list of default operators. These accounts are operators +for all token holders, even if [`ERC777.authorizeOperator`](#ERC777-authorizeOperator-address-) was never called on +them. + +This list is immutable, but individual holders may revoke these via +[`ERC777.revokeOperator`](#ERC777-revokeOperator-address-), in which case [`ERC777.isOperatorFor`](#ERC777-isOperatorFor-address-address-) will return false. + +
+
+ + + +
+
+

operatorSend(address sender, address recipient, uint256 amount, bytes data, bytes operatorData)

+
+

external

+# +
+
+
+ +Moves `amount` tokens from `sender` to `recipient`. The caller must +be an operator of `sender`. + +If send or receive hooks are registered for `sender` and `recipient`, +the corresponding functions will be called with `data` and +`operatorData`. See [`IERC777Sender`](#IERC777Sender) and [`IERC777Recipient`](#IERC777Recipient). + +Emits a [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) event. + +Requirements + +- `sender` cannot be the zero address. +- `sender` must have at least `amount` tokens. +- the caller must be an operator for `sender`. +- `recipient` cannot be the zero address. +- if `recipient` is a contract, it must implement the [`IERC777Recipient`](#IERC777Recipient) +interface. + +
+
+ + + +
+
+

operatorBurn(address account, uint256 amount, bytes data, bytes operatorData)

+
+

external

+# +
+
+
+ +Destroys `amount` tokens from `account`, reducing the total supply. +The caller must be an operator of `account`. + +If a send hook is registered for `account`, the corresponding function +will be called with `data` and `operatorData`. See [`IERC777Sender`](#IERC777Sender). + +Emits a [`IERC777.Burned`](#IERC777-Burned-address-address-uint256-bytes-bytes-) event. + +Requirements + +- `account` cannot be the zero address. +- `account` must have at least `amount` tokens. +- the caller must be an operator for `account`. + +
+
+ + + +
+
+

Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData)

+
+

event

+# +
+
+ +
+ +Emitted when `amount` tokens are created by `operator` and assigned to `to`. + +Note that some additional user `data` and `operatorData` can be logged in the event. + +
+
+ + +
+
+

Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData)

+
+

event

+# +
+
+ +
+ +Emitted when `operator` destroys `amount` tokens from `account`. + +Note that some additional user `data` and `operatorData` can be logged in the event. + +
+
+ + +
+
+

AuthorizedOperator(address indexed operator, address indexed tokenHolder)

+
+

event

+# +
+
+ +
+ +Emitted when `operator` is made operator for `tokenHolder`. + +
+
+ + +
+
+

RevokedOperator(address indexed operator, address indexed tokenHolder)

+
+

event

+# +
+
+ +
+ +Emitted when `operator` is revoked its operator status for `tokenHolder`. + +
+
+ + +
+
+

Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC777Recipient` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol"; +``` + +Interface of the ERC777TokensRecipient standard as defined in the EIP. + +Accounts can be notified of [`IERC777`](#IERC777) tokens being sent to them by having a +contract implement this interface (contract holders can be their own +implementer) and registering it on the +[ERC1820 global registry](https://eips.ethereum.org/EIPS/eip-1820). + +See [`IERC1820Registry`](../utils#IERC1820Registry) and [`ERC1820Implementer`](../utils#ERC1820Implementer). + +
+

Functions

+
+- [tokensReceived(operator, from, to, amount, userData, operatorData)](#IERC777Recipient-tokensReceived-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

tokensReceived(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData)

+
+

external

+# +
+
+
+ +Called by an [`IERC777`](#IERC777) token contract whenever tokens are being +moved or created into a registered account (`to`). The type of operation +is conveyed by `from` being the zero address or not. + +This call occurs _after_ the token contract's state is updated, so +[`IERC777.balanceOf`](#IERC777-balanceOf-address-), etc., can be used to query the post-operation state. + +This function may revert to prevent the operation from being executed. + +
+
+ + + +
+ +## `IERC777Sender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC777/IERC777Sender.sol"; +``` + +Interface of the ERC777TokensSender standard as defined in the EIP. + +[`IERC777`](#IERC777) Token holders can be notified of operations performed on their +tokens by having a contract implement this interface (contract holders can be +their own implementer) and registering it on the +[ERC1820 global registry](https://eips.ethereum.org/EIPS/eip-1820). + +See [`IERC1820Registry`](../utils#IERC1820Registry) and [`ERC1820Implementer`](../utils#ERC1820Implementer). + +
+

Functions

+
+- [tokensToSend(operator, from, to, amount, userData, operatorData)](#IERC777Sender-tokensToSend-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

tokensToSend(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData)

+
+

external

+# +
+
+
+ +Called by an [`IERC777`](#IERC777) token contract whenever a registered holder's +(`from`) tokens are about to be moved or destroyed. The type of operation +is conveyed by `to` being the zero address or not. + +This call occurs _before_ the token contract's state is updated, so +[`IERC777.balanceOf`](#IERC777-balanceOf-address-), etc., can be used to query the pre-operation state. + +This function may revert to prevent the operation from being executed. + +
+
+ + + +
+ +## `ERC777PresetFixedSupply` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC777/presets/ERC777PresetFixedSupply.sol"; +``` + +[`ERC777`](#ERC777) token, including: + + - Preminted initial supply + - No access control mechanism (for minting/pausing) and hence no governance + +_Available since v3.4._ + +
+

Functions

+
+- [constructor(name, symbol, defaultOperators, initialSupply, owner)](#ERC777PresetFixedSupply-constructor-string-string-address---uint256-address-) +#### ERC777 +- [name()](#ERC777-name--) +- [symbol()](#ERC777-symbol--) +- [decimals()](#ERC777-decimals--) +- [granularity()](#ERC777-granularity--) +- [totalSupply()](#ERC777-totalSupply--) +- [balanceOf(tokenHolder)](#ERC777-balanceOf-address-) +- [send(recipient, amount, data)](#ERC777-send-address-uint256-bytes-) +- [transfer(recipient, amount)](#ERC777-transfer-address-uint256-) +- [burn(amount, data)](#ERC777-burn-uint256-bytes-) +- [isOperatorFor(operator, tokenHolder)](#ERC777-isOperatorFor-address-address-) +- [authorizeOperator(operator)](#ERC777-authorizeOperator-address-) +- [revokeOperator(operator)](#ERC777-revokeOperator-address-) +- [defaultOperators()](#ERC777-defaultOperators--) +- [operatorSend(sender, recipient, amount, data, operatorData)](#ERC777-operatorSend-address-address-uint256-bytes-bytes-) +- [operatorBurn(account, amount, data, operatorData)](#ERC777-operatorBurn-address-uint256-bytes-bytes-) +- [allowance(holder, spender)](#ERC777-allowance-address-address-) +- [approve(spender, value)](#ERC777-approve-address-uint256-) +- [transferFrom(holder, recipient, amount)](#ERC777-transferFrom-address-address-uint256-) +- [_mint(account, amount, userData, operatorData)](#ERC777-_mint-address-uint256-bytes-bytes-) +- [_mint(account, amount, userData, operatorData, requireReceptionAck)](#ERC777-_mint-address-uint256-bytes-bytes-bool-) +- [_send(from, to, amount, userData, operatorData, requireReceptionAck)](#ERC777-_send-address-address-uint256-bytes-bytes-bool-) +- [_burn(from, amount, data, operatorData)](#ERC777-_burn-address-uint256-bytes-bytes-) +- [_approve(holder, spender, value)](#ERC777-_approve-address-address-uint256-) +- [_spendAllowance(owner, spender, amount)](#ERC777-_spendAllowance-address-address-uint256-) +- [_beforeTokenTransfer(operator, from, to, amount)](#ERC777-_beforeTokenTransfer-address-address-address-uint256-) +#### IERC20 +#### IERC777 +
+
+ +
+

Events

+
+#### ERC777 +#### IERC20 +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +#### IERC777 +- [Minted(operator, to, amount, data, operatorData)](#IERC777-Minted-address-address-uint256-bytes-bytes-) +- [Burned(operator, from, amount, data, operatorData)](#IERC777-Burned-address-address-uint256-bytes-bytes-) +- [AuthorizedOperator(operator, tokenHolder)](#IERC777-AuthorizedOperator-address-address-) +- [RevokedOperator(operator, tokenHolder)](#IERC777-RevokedOperator-address-address-) +- [Sent(operator, from, to, amount, data, operatorData)](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

constructor(string name, string symbol, address[] defaultOperators, uint256 initialSupply, address owner)

+
+

public

+# +
+
+
+ +Mints `initialSupply` amount of token and transfers them to `owner`. + +See [`ERC777.constructor`](#ERC777-constructor-string-string-address---). + +
+
diff --git a/docs/content/contracts/4.x/api/token/common.mdx b/docs/content/contracts/4.x/api/token/common.mdx new file mode 100644 index 00000000..f956c78f --- /dev/null +++ b/docs/content/contracts/4.x/api/token/common.mdx @@ -0,0 +1,193 @@ +--- +title: "Common" +description: "Smart contract common utilities and implementations" +--- + +Functionality that is common to multiple token standards. + +* [`ERC2981`](#ERC2981): NFT Royalties compatible with both ERC721 and ERC1155. + * For ERC721 consider [`ERC721Royalty`](contracts/4.x/api/token/ERC721#ERC721Royalty) which clears the royalty information from storage on burn. + +## Contracts + +[`ERC2981`](#ERC2981) + + + +
+ +## `ERC2981` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/common/ERC2981.sol"; +``` + +Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information. + +Royalty information can be specified globally for all token ids via [`ERC2981._setDefaultRoyalty`](#ERC2981-_setDefaultRoyalty-address-uint96-), and/or individually for +specific token ids via [`ERC2981._setTokenRoyalty`](#ERC2981-_setTokenRoyalty-uint256-address-uint96-). The latter takes precedence over the first. + +Royalty is specified as a fraction of sale price. [`ERC2981._feeDenominator`](#ERC2981-_feeDenominator--) is overridable but defaults to 10000, meaning the +fee is specified in basis points by default. + + +ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See +[Rationale](https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments) in the EIP. Marketplaces are expected to +voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. + + +_Available since v4.5._ + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC2981-supportsInterface-bytes4-) +- [royaltyInfo(tokenId, salePrice)](#ERC2981-royaltyInfo-uint256-uint256-) +- [_feeDenominator()](#ERC2981-_feeDenominator--) +- [_setDefaultRoyalty(receiver, feeNumerator)](#ERC2981-_setDefaultRoyalty-address-uint96-) +- [_deleteDefaultRoyalty()](#ERC2981-_deleteDefaultRoyalty--) +- [_setTokenRoyalty(tokenId, receiver, feeNumerator)](#ERC2981-_setTokenRoyalty-uint256-address-uint96-) +- [_resetTokenRoyalty(tokenId)](#ERC2981-_resetTokenRoyalty-uint256-) +#### ERC165 +#### IERC2981 +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](../utils#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

royaltyInfo(uint256 tokenId, uint256 salePrice) → address, uint256

+
+

public

+# +
+
+
+ +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of +exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + +
+
+ + + +
+
+

_feeDenominator() → uint96

+
+

internal

+# +
+
+
+ +The denominator with which to interpret the fee set in [`ERC2981._setTokenRoyalty`](#ERC2981-_setTokenRoyalty-uint256-address-uint96-) and [`ERC2981._setDefaultRoyalty`](#ERC2981-_setDefaultRoyalty-address-uint96-) as a +fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an +override. + +
+
+ + + +
+
+

_setDefaultRoyalty(address receiver, uint96 feeNumerator)

+
+

internal

+# +
+
+
+ +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- `receiver` cannot be the zero address. +- `feeNumerator` cannot be greater than the fee denominator. + +
+
+ + + +
+
+

_deleteDefaultRoyalty()

+
+

internal

+# +
+
+
+ +Removes default royalty information. + +
+
+ + + +
+
+

_setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)

+
+

internal

+# +
+
+
+ +Sets the royalty information for a specific token id, overriding the global default. + +Requirements: + +- `receiver` cannot be the zero address. +- `feeNumerator` cannot be greater than the fee denominator. + +
+
+ + + +
+
+

_resetTokenRoyalty(uint256 tokenId)

+
+

internal

+# +
+
+
+ +Resets royalty information for the token id back to the global default. + +
+
diff --git a/docs/content/contracts/4.x/api/utils.mdx b/docs/content/contracts/4.x/api/utils.mdx new file mode 100644 index 00000000..c7690cdf --- /dev/null +++ b/docs/content/contracts/4.x/api/utils.mdx @@ -0,0 +1,8440 @@ +--- +title: "Utils" +description: "Smart contract utils utilities and implementations" +--- + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + +The [`Address`](#Address), [`Arrays`](#Arrays), [`Base64`](#Base64) and [`Strings`](#Strings) libraries provide more operations related to these native data types, while [`SafeCast`](#SafeCast) adds ways to safely convert between the different signed and unsigned numeric types. +[`Multicall`](#Multicall) provides a function to batch together multiple calls in a single external call. + +For new data types: + +* [`Counters`](#Counters): a simple way to get a counter that can only be incremented, decremented or reset. Very useful for ID generation, counting contract activity, among others. +* [`EnumerableMap`](#EnumerableMap): like Solidity’s [`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types) type, but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). +* [`EnumerableSet`](#EnumerableSet): like [`EnumerableMap`](#EnumerableMap), but for [sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)). Can be used to store privileged accounts, issued IDs, etc. + + +Because Solidity does not support generic types, [`EnumerableMap`](#EnumerableMap) and [`EnumerableSet`](#EnumerableSet) are specialized to a limited number of key-value types. + +As of v3.0, [`EnumerableMap`](#EnumerableMap) supports `uint256 -> address` (`UintToAddressMap`), and [`EnumerableSet`](#EnumerableSet) supports `address` and `uint256` (`AddressSet` and `UintSet`). + + +Finally, [`Create2`](#Create2) contains all necessary utilities to safely use the [`CREATE2` EVM opcode](https://blog.openzeppelin.com/getting-the-most-out-of-create2/), without having to deal with low-level assembly. + +## Math + +[`Math`](#Math) + +[`SignedMath`](#SignedMath) + +[`SafeCast`](#SafeCast) + +[`SafeMath`](#SafeMath) + +[`SignedSafeMath`](#SignedSafeMath) + +## Cryptography + +[`ECDSA`](#ECDSA) + +[`SignatureChecker`](#SignatureChecker) + +[`MerkleProof`](#MerkleProof) + +[`EIP712`](#EIP712) + +## Escrow + +[`ConditionalEscrow`](#ConditionalEscrow) + +[`Escrow`](#Escrow) + +[`RefundEscrow`](#RefundEscrow) + +## Introspection + +This set of interfaces and contracts deal with [type introspection](https://en.wikipedia.org/wiki/Type_introspection) of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s _interface_. + +Ethereum contracts have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. `ERC20` tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract _declaring_ its interface can be very helpful in preventing errors. + +There are two main ways to approach this. + +* Locally, where a contract implements `IERC165` and declares an interface, and a second one queries it directly via `ERC165Checker`. +* Globally, where a global and unique registry (`IERC1820Registry`) is used to register implementers of a certain interface (`IERC1820Implementer`). It is then the registry that is queried, which allows for more complex setups, like contracts implementing interfaces for externally-owned accounts. + +Note that, in all cases, accounts simply _declare_ their interfaces, but they are not required to actually implement them. This mechanism can therefore be used to both prevent errors and allow for complex interactions (see `ERC777`), but it must not be relied on for security. + +[`IERC165`](#IERC165) + +[`ERC165`](#ERC165) + +[`ERC165Storage`](#ERC165Storage) + +[`ERC165Checker`](#ERC165Checker) + +[`IERC1820Registry`](#IERC1820Registry) + +[`IERC1820Implementer`](#IERC1820Implementer) + +[`ERC1820Implementer`](#ERC1820Implementer) + +## Data Structures + +[`BitMaps`](#BitMaps) + +[`EnumerableMap`](#EnumerableMap) + +[`EnumerableSet`](#EnumerableSet) + +[`DoubleEndedQueue`](#DoubleEndedQueue) + +[`Checkpoints`](#Checkpoints) + +## Libraries + +[`Create2`](#Create2) + +[`Address`](#Address) + +[`Arrays`](#Arrays) + +[`Base64`](#Base64) + +[`Counters`](#Counters) + +[`Strings`](#Strings) + +[`ShortStrings`](#ShortStrings) + +[`StorageSlot`](#StorageSlot) + +[`Multicall`](#Multicall) + + + +
+ +## `Address` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Address.sol"; +``` + +Collection of functions related to the address type + +
+

Functions

+
+- [isContract(account)](#Address-isContract-address-) +- [sendValue(recipient, amount)](#Address-sendValue-address-payable-uint256-) +- [functionCall(target, data)](#Address-functionCall-address-bytes-) +- [functionCall(target, data, errorMessage)](#Address-functionCall-address-bytes-string-) +- [functionCallWithValue(target, data, value)](#Address-functionCallWithValue-address-bytes-uint256-) +- [functionCallWithValue(target, data, value, errorMessage)](#Address-functionCallWithValue-address-bytes-uint256-string-) +- [functionStaticCall(target, data)](#Address-functionStaticCall-address-bytes-) +- [functionStaticCall(target, data, errorMessage)](#Address-functionStaticCall-address-bytes-string-) +- [functionDelegateCall(target, data)](#Address-functionDelegateCall-address-bytes-) +- [functionDelegateCall(target, data, errorMessage)](#Address-functionDelegateCall-address-bytes-string-) +- [verifyCallResultFromTarget(target, success, returndata, errorMessage)](#Address-verifyCallResultFromTarget-address-bool-bytes-string-) +- [verifyCallResult(success, returndata, errorMessage)](#Address-verifyCallResult-bool-bytes-string-) +
+
+ + + +
+
+

isContract(address account) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` is a contract. + +[IMPORTANT] +==== +It is unsafe to assume that an address for which this function returns +false is an externally-owned account (EOA) and not a contract. + +Among others, `isContract` will return false for the following +types of addresses: + + - an externally-owned account + - a contract in construction + - an address where a contract will be created + - an address where a contract lived, but was destroyed + +Furthermore, `isContract` will also return true if the target contract within +the same transaction is already scheduled for destruction by `SELFDESTRUCT`, +which only has an effect at the end of a transaction. +==== + +[IMPORTANT] +==== +You shouldn't rely on `isContract` to protect against flash loan attacks! + +Preventing 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. +==== + +
+
+ + + +
+
+

sendValue(address payable recipient, uint256 amount)

+
+

internal

+# +
+
+
+ +Replacement for Solidity's `transfer`: sends `amount` wei to +`recipient`, forwarding all available gas and reverting on errors. + +[EIP1884](https://eips.ethereum.org/EIPS/eip-1884) increases the gas cost +of certain opcodes, possibly making contracts go over the 2300 gas limit +imposed by `transfer`, making them unable to receive funds via +`transfer`. [`Address.sendValue`](#Address-sendValue-address-payable-uint256-) removes this limitation. + +[Learn more](https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/). + + +because control is transferred to `recipient`, care must be +taken to not create reentrancy vulnerabilities. Consider using +[`ReentrancyGuard`](/contracts/4.x/api/security#ReentrancyGuard) or the +[checks-effects-interactions pattern](https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern). + + +
+
+ + + +
+
+

functionCall(address target, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `call`. A +plain `call` is an unsafe replacement for a function call: use this +function instead. + +If `target` reverts with a revert reason, it is bubbled up by this +function (like regular Solidity function calls). + +Returns the raw returned data. To convert to the expected return value, +use [`abi.decode`](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions). + +Requirements: + +- `target` must be a contract. +- calling `target` with `data` must not revert. + +_Available since v3.1._ + +
+
+ + + +
+
+

functionCall(address target, bytes data, string errorMessage) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), but with +`errorMessage` as a fallback revert reason when `target` reverts. + +_Available since v3.1._ + +
+
+ + + +
+
+

functionCallWithValue(address target, bytes data, uint256 value) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but also transferring `value` wei to `target`. + +Requirements: + +- the calling contract must have an ETH balance of at least `value`. +- the called Solidity function must be `payable`. + +_Available since v3.1._ + +
+
+ + + +
+
+

functionCallWithValue(address target, bytes data, uint256 value, string errorMessage) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCallWithValue`](#Address-functionCallWithValue-address-bytes-uint256-), but +with `errorMessage` as a fallback revert reason when `target` reverts. + +_Available since v3.1._ + +
+
+ + + +
+
+

functionStaticCall(address target, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but performing a static call. + +_Available since v3.3._ + +
+
+ + + +
+
+

functionStaticCall(address target, bytes data, string errorMessage) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-string-), +but performing a static call. + +_Available since v3.3._ + +
+
+ + + +
+
+

functionDelegateCall(address target, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but performing a delegate call. + +_Available since v3.4._ + +
+
+ + + +
+
+

functionDelegateCall(address target, bytes data, string errorMessage) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-string-), +but performing a delegate call. + +_Available since v3.4._ + +
+
+ + + +
+
+

verifyCallResultFromTarget(address target, bool success, bytes returndata, string errorMessage) → bytes

+
+

internal

+# +
+
+
+ +Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling +the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. + +_Available since v4.8._ + +
+
+ + + +
+
+

verifyCallResult(bool success, bytes returndata, string errorMessage) → bytes

+
+

internal

+# +
+
+
+ +Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the +revert reason or using the provided one. + +_Available since v4.3._ + +
+
+ + + +
+ +## `Arrays` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Arrays.sol"; +``` + +Collection of functions related to array types. + +
+

Functions

+
+- [findUpperBound(array, element)](#Arrays-findUpperBound-uint256---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-address---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-bytes32---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-uint256---uint256-) +
+
+ + + +
+
+

findUpperBound(uint256[] array, uint256 element) → uint256

+
+

internal

+# +
+
+
+ +Searches a sorted `array` and returns the first index that contains +a value greater or equal to `element`. If no such index exists (i.e. all +values in the array are strictly less than `element`), the array length is +returned. Time complexity O(log n). + +`array` is expected to be sorted in ascending order, and to contain no +repeated elements. + +
+
+ + + +
+
+

unsafeAccess(address[] arr, uint256 pos) → struct StorageSlot.AddressSlot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeAccess(bytes32[] arr, uint256 pos) → struct StorageSlot.Bytes32Slot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeAccess(uint256[] arr, uint256 pos) → struct StorageSlot.Uint256Slot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+ +## `Base64` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Base64.sol"; +``` + +Provides a set of functions to operate with Base64 strings. + +_Available since v4.5._ + +
+

Functions

+
+- [encode(data)](#Base64-encode-bytes-) +
+
+ + + +
+
+

encode(bytes data) → string

+
+

internal

+# +
+
+
+ +Converts a `bytes` to its Bytes64 `string` representation. + +
+
+ + + +
+ +## `Checkpoints` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Checkpoints.sol"; +``` + +This library defines the `History` struct, for checkpointing values as they change at different points in +time, and later looking up past values by block number. See [`Votes`](/contracts/4.x/api/governance#Votes) as an example. + +To create a history of checkpoints define a variable type `Checkpoints.History` in your contract, and store a new +checkpoint for the current transaction block using the [`Checkpoints.push`](#Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-) function. + +_Available since v4.5._ + +
+

Functions

+
+- [getAtBlock(self, blockNumber)](#Checkpoints-getAtBlock-struct-Checkpoints-History-uint256-) +- [getAtProbablyRecentBlock(self, blockNumber)](#Checkpoints-getAtProbablyRecentBlock-struct-Checkpoints-History-uint256-) +- [push(self, value)](#Checkpoints-push-struct-Checkpoints-History-uint256-) +- [push(self, op, delta)](#Checkpoints-push-struct-Checkpoints-History-function--uint256-uint256--view-returns--uint256--uint256-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-History-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-History-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-History-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace224-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace224-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace160-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace160-) +
+
+ + + +
+
+

getAtBlock(struct Checkpoints.History self, uint256 blockNumber) → uint256

+
+

internal

+# +
+
+
+ +Returns the value at a given block number. If a checkpoint is not available at that block, the closest one +before it is returned, or zero otherwise. Because the number returned corresponds to that at the end of the +block, the requested block number must be in the past, excluding the current block. + +
+
+ + + +
+
+

getAtProbablyRecentBlock(struct Checkpoints.History self, uint256 blockNumber) → uint256

+
+

internal

+# +
+
+
+ +Returns the value at a given block number. If a checkpoint is not available at that block, the closest one +before it is returned, or zero otherwise. Similar to [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) but optimized for the case when the searched +checkpoint is probably "recent", defined as being among the last sqrt(N) checkpoints where N is the number of +checkpoints. + +
+
+ + + +
+
+

push(struct Checkpoints.History self, uint256 value) → uint256, uint256

+
+

internal

+# +
+
+
+ +Pushes a value onto a History so that it is stored as the checkpoint for the current block. + +Returns previous value and new value. + +
+
+ + + +
+
+

push(struct Checkpoints.History self, function (uint256,uint256) view returns (uint256) op, uint256 delta) → uint256, uint256

+
+

internal

+# +
+
+
+ +Pushes a value onto a History, by updating the latest value using binary operation `op`. The new value will +be set to `op(latest, delta)`. + +Returns previous value and new value. + +
+
+ + + +
+
+

latest(struct Checkpoints.History self) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.History self) → bool exists, uint32 _blockNumber, uint224 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.History self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoint. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace224 self, uint32 key, uint224 value) → uint224, uint224

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint. + +Returns previous value and new value. + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. + +NOTE: This is a variant of [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) that is optimised to find "recent" checkpoint (checkpoints with high keys). + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace224 self) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace224 self) → bool exists, uint32 _key, uint224 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace224 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoint. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace160 self, uint96 key, uint160 value) → uint160, uint160

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint. + +Returns previous value and new value. + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero if there is none. + +NOTE: This is a variant of [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) that is optimised to find "recent" checkpoint (checkpoints with high keys). + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace160 self) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace160 self) → bool exists, uint96 _key, uint160 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace160 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoint. + +
+
+ + + +
+ +## `Context` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Context.sol"; +``` + +Provides information about the current execution context, including the +sender of the transaction and its data. While these are generally available +via msg.sender and msg.data, they should not be accessed in such a direct +manner, since when dealing with meta-transactions the account sending and +paying for execution may not be the actual sender (as far as an application +is concerned). + +This contract is only required for intermediate, library-like contracts. + +
+

Functions

+
+- [_msgSender()](#Context-_msgSender--) +- [_msgData()](#Context-_msgData--) +- [_contextSuffixLength()](#Context-_contextSuffixLength--) +
+
+ + + +
+
+

_msgSender() → address

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_msgData() → bytes

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_contextSuffixLength() → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `Counters` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Counters.sol"; +``` + +Provides counters that can only be incremented, decremented or reset. This can be used e.g. to track the number +of elements in a mapping, issuing ERC721 ids, or counting request ids. + +Include with `using Counters for Counters.Counter;` + +
+

Functions

+
+- [current(counter)](#Counters-current-struct-Counters-Counter-) +- [increment(counter)](#Counters-increment-struct-Counters-Counter-) +- [decrement(counter)](#Counters-decrement-struct-Counters-Counter-) +- [reset(counter)](#Counters-reset-struct-Counters-Counter-) +
+
+ + + +
+
+

current(struct Counters.Counter counter) → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

increment(struct Counters.Counter counter)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

decrement(struct Counters.Counter counter)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

reset(struct Counters.Counter counter)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `Create2` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Create2.sol"; +``` + +Helper to make usage of the `CREATE2` EVM opcode easier and safer. +`CREATE2` can be used to compute in advance the address where a smart +contract will be deployed, which allows for interesting new mechanisms known +as 'counterfactual interactions'. + +See the [EIP](https://eips.ethereum.org/EIPS/eip-1014#motivation) for more +information. + +
+

Functions

+
+- [deploy(amount, salt, bytecode)](#Create2-deploy-uint256-bytes32-bytes-) +- [computeAddress(salt, bytecodeHash)](#Create2-computeAddress-bytes32-bytes32-) +- [computeAddress(salt, bytecodeHash, deployer)](#Create2-computeAddress-bytes32-bytes32-address-) +
+
+ + + +
+
+

deploy(uint256 amount, bytes32 salt, bytes bytecode) → address addr

+
+

internal

+# +
+
+
+ +Deploys a contract using `CREATE2`. The address where the contract +will be deployed can be known in advance via [`Create2.computeAddress`](#Create2-computeAddress-bytes32-bytes32-address-). + +The bytecode for a contract can be obtained from Solidity with +`type(contractName).creationCode`. + +Requirements: + +- `bytecode` must not be empty. +- `salt` must have not been used for `bytecode` already. +- the factory must have a balance of at least `amount`. +- if `amount` is non-zero, `bytecode` must have a `payable` constructor. + +
+
+ + + +
+
+

computeAddress(bytes32 salt, bytes32 bytecodeHash) → address

+
+

internal

+# +
+
+
+ +Returns the address where a contract will be stored if deployed via [`Create2.deploy`](#Create2-deploy-uint256-bytes32-bytes-). Any change in the +`bytecodeHash` or `salt` will result in a new destination address. + +
+
+ + + +
+
+

computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) → address addr

+
+

internal

+# +
+
+
+ +Returns the address where a contract will be stored if deployed via [`Create2.deploy`](#Create2-deploy-uint256-bytes32-bytes-) from a contract located at +`deployer`. If `deployer` is this contract's address, returns the same value as [`Create2.computeAddress`](#Create2-computeAddress-bytes32-bytes32-address-). + +
+
+ + + +
+ +## `Multicall` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Multicall.sol"; +``` + +Provides a function to batch together multiple calls in a single external call. + +Consider any assumption about calldata validation performed by the sender may be violated if it's not especially +careful about sending transactions invoking [`Multicall.multicall`](#Multicall-multicall-bytes---). For example, a relay address that filters function +selectors won't filter calls nested within a [`Multicall.multicall`](#Multicall-multicall-bytes---) operation. + +NOTE: Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not [`ERC2771Context._msgSender`](/contracts/4.x/api/metatx#ERC2771Context-_msgSender--)). +If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data` +to the subcall. This makes it safe to use with [`ERC2771Context`](/contracts/4.x/api/metatx#ERC2771Context). Contexts that don't affect the resolution of +[`ERC2771Context._msgSender`](/contracts/4.x/api/metatx#ERC2771Context-_msgSender--) are not propagated to subcalls. + +_Available since v4.1._ + +
+

Functions

+
+- [multicall(data)](#Multicall-multicall-bytes---) +
+
+ + + +
+
+

multicall(bytes[] data) → bytes[] results

+
+

external

+# +
+
+
+ +Receives and executes a batch of function calls on this contract. + +
+
+ + + +
+ +## `ShortString` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/ShortStrings.sol"; +``` + + + +
+ +## `ShortStrings` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/ShortStrings.sol"; +``` + +This library provides functions to convert short memory strings +into a `ShortString` type that can be used as an immutable variable. + +Strings of arbitrary length can be optimized using this library if +they are short enough (up to 31 bytes) by packing them with their +length (1 byte) in a single EVM word (32 bytes). Additionally, a +fallback mechanism can be used for every other case. + +Usage example: + +```solidity +contract Named { + using ShortStrings for *; + + ShortString private immutable _name; + string private _nameFallback; + + constructor(string memory contractName) { + _name = contractName.toShortStringWithFallback(_nameFallback); + } + + function name() external view returns (string memory) { + return _name.toStringWithFallback(_nameFallback); + } +} +``` + +
+

Functions

+
+- [toShortString(str)](#ShortStrings-toShortString-string-) +- [toString(sstr)](#ShortStrings-toString-ShortString-) +- [byteLength(sstr)](#ShortStrings-byteLength-ShortString-) +- [toShortStringWithFallback(value, store)](#ShortStrings-toShortStringWithFallback-string-string-) +- [toStringWithFallback(value, store)](#ShortStrings-toStringWithFallback-ShortString-string-) +- [byteLengthWithFallback(value, store)](#ShortStrings-byteLengthWithFallback-ShortString-string-) +
+
+ +
+

Errors

+
+- [StringTooLong(str)](#ShortStrings-StringTooLong-string-) +- [InvalidShortString()](#ShortStrings-InvalidShortString--) +
+
+ + + +
+
+

toShortString(string str) → ShortString

+
+

internal

+# +
+
+
+ +Encode a string of at most 31 chars into a `ShortString`. + +This will trigger a `StringTooLong` error is the input string is too long. + +
+
+ + + +
+
+

toString(ShortString sstr) → string

+
+

internal

+# +
+
+
+ +Decode a `ShortString` back to a "normal" string. + +
+
+ + + +
+
+

byteLength(ShortString sstr) → uint256

+
+

internal

+# +
+
+
+ +Return the length of a `ShortString`. + +
+
+ + + +
+
+

toShortStringWithFallback(string value, string store) → ShortString

+
+

internal

+# +
+
+
+ +Encode a string into a `ShortString`, or write it to storage if it is too long. + +
+
+ + + +
+
+

toStringWithFallback(ShortString value, string store) → string

+
+

internal

+# +
+
+
+ +Decode a string that was encoded to `ShortString` or written to storage using `setWithFallback`. + +
+
+ + + +
+
+

byteLengthWithFallback(ShortString value, string store) → uint256

+
+

internal

+# +
+
+
+ +Return the length of a string that was encoded to `ShortString` or written to storage using `setWithFallback`. + + +This will return the "byte length" of the string. This may not reflect the actual length in terms of +actual characters as the UTF-8 encoding of a single character can span over multiple bytes. + + +
+
+ + + +
+
+

StringTooLong(string str)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InvalidShortString()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `StorageSlot` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/StorageSlot.sol"; +``` + +Library for reading and writing primitive types to specific storage slots. + +Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. +This library helps with reading and writing to such slots without the need for inline assembly. + +The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + +Example usage to set ERC1967 implementation slot: +```solidity +contract ERC1967 { + bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + function _getImplementation() internal view returns (address) { + return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; + } + + function _setImplementation(address newImplementation) internal { + require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); + StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; + } +} +``` + +_Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._ +_Available since v4.9 for `string`, `bytes`._ + +
+

Functions

+
+- [getAddressSlot(slot)](#StorageSlot-getAddressSlot-bytes32-) +- [getBooleanSlot(slot)](#StorageSlot-getBooleanSlot-bytes32-) +- [getBytes32Slot(slot)](#StorageSlot-getBytes32Slot-bytes32-) +- [getUint256Slot(slot)](#StorageSlot-getUint256Slot-bytes32-) +- [getStringSlot(slot)](#StorageSlot-getStringSlot-bytes32-) +- [getStringSlot(store)](#StorageSlot-getStringSlot-string-) +- [getBytesSlot(slot)](#StorageSlot-getBytesSlot-bytes32-) +- [getBytesSlot(store)](#StorageSlot-getBytesSlot-bytes-) +
+
+ + + +
+
+

getAddressSlot(bytes32 slot) → struct StorageSlot.AddressSlot r

+
+

internal

+# +
+
+
+ +Returns an `AddressSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getBooleanSlot(bytes32 slot) → struct StorageSlot.BooleanSlot r

+
+

internal

+# +
+
+
+ +Returns an `BooleanSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getBytes32Slot(bytes32 slot) → struct StorageSlot.Bytes32Slot r

+
+

internal

+# +
+
+
+ +Returns an `Bytes32Slot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getUint256Slot(bytes32 slot) → struct StorageSlot.Uint256Slot r

+
+

internal

+# +
+
+
+ +Returns an `Uint256Slot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getStringSlot(bytes32 slot) → struct StorageSlot.StringSlot r

+
+

internal

+# +
+
+
+ +Returns an `StringSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getStringSlot(string store) → struct StorageSlot.StringSlot r

+
+

internal

+# +
+
+
+ +Returns an `StringSlot` representation of the string storage pointer `store`. + +
+
+ + + +
+
+

getBytesSlot(bytes32 slot) → struct StorageSlot.BytesSlot r

+
+

internal

+# +
+
+
+ +Returns an `BytesSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getBytesSlot(bytes store) → struct StorageSlot.BytesSlot r

+
+

internal

+# +
+
+
+ +Returns an `BytesSlot` representation of the bytes storage pointer `store`. + +
+
+ + + +
+ +## `Strings` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Strings.sol"; +``` + +String operations. + +
+

Functions

+
+- [toString(value)](#Strings-toString-uint256-) +- [toString(value)](#Strings-toString-int256-) +- [toHexString(value)](#Strings-toHexString-uint256-) +- [toHexString(value, length)](#Strings-toHexString-uint256-uint256-) +- [toHexString(addr)](#Strings-toHexString-address-) +- [equal(a, b)](#Strings-equal-string-string-) +
+
+ + + +
+
+

toString(uint256 value) → string

+
+

internal

+# +
+
+
+ +Converts a `uint256` to its ASCII `string` decimal representation. + +
+
+ + + +
+
+

toString(int256 value) → string

+
+

internal

+# +
+
+
+ +Converts a `int256` to its ASCII `string` decimal representation. + +
+
+ + + +
+
+

toHexString(uint256 value) → string

+
+

internal

+# +
+
+
+ +Converts a `uint256` to its ASCII `string` hexadecimal representation. + +
+
+ + + +
+
+

toHexString(uint256 value, uint256 length) → string

+
+

internal

+# +
+
+
+ +Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + +
+
+ + + +
+
+

toHexString(address addr) → string

+
+

internal

+# +
+
+
+ +Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + +
+
+ + + +
+
+

equal(string a, string b) → bool

+
+

internal

+# +
+
+
+ +Returns true if the two strings are equal. + +
+
+ + + +
+ +## `Timers` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Timers.sol"; +``` + +Tooling for timepoints, timers and delays + +CAUTION: This file is deprecated as of 4.9 and will be removed in the next major release. + +
+

Functions

+
+- [getDeadline(timer)](#Timers-getDeadline-struct-Timers-Timestamp-) +- [setDeadline(timer, timestamp)](#Timers-setDeadline-struct-Timers-Timestamp-uint64-) +- [reset(timer)](#Timers-reset-struct-Timers-Timestamp-) +- [isUnset(timer)](#Timers-isUnset-struct-Timers-Timestamp-) +- [isStarted(timer)](#Timers-isStarted-struct-Timers-Timestamp-) +- [isPending(timer)](#Timers-isPending-struct-Timers-Timestamp-) +- [isExpired(timer)](#Timers-isExpired-struct-Timers-Timestamp-) +- [getDeadline(timer)](#Timers-getDeadline-struct-Timers-BlockNumber-) +- [setDeadline(timer, timestamp)](#Timers-setDeadline-struct-Timers-BlockNumber-uint64-) +- [reset(timer)](#Timers-reset-struct-Timers-BlockNumber-) +- [isUnset(timer)](#Timers-isUnset-struct-Timers-BlockNumber-) +- [isStarted(timer)](#Timers-isStarted-struct-Timers-BlockNumber-) +- [isPending(timer)](#Timers-isPending-struct-Timers-BlockNumber-) +- [isExpired(timer)](#Timers-isExpired-struct-Timers-BlockNumber-) +
+
+ + + +
+
+

getDeadline(struct Timers.Timestamp timer) → uint64

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

setDeadline(struct Timers.Timestamp timer, uint64 timestamp)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

reset(struct Timers.Timestamp timer)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isUnset(struct Timers.Timestamp timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isStarted(struct Timers.Timestamp timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isPending(struct Timers.Timestamp timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isExpired(struct Timers.Timestamp timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

getDeadline(struct Timers.BlockNumber timer) → uint64

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

setDeadline(struct Timers.BlockNumber timer, uint64 timestamp)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

reset(struct Timers.BlockNumber timer)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isUnset(struct Timers.BlockNumber timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isStarted(struct Timers.BlockNumber timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isPending(struct Timers.BlockNumber timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

isExpired(struct Timers.BlockNumber timer) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `ECDSA` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +``` + +Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + +These functions can be used to verify that a message was signed by the holder +of the private keys of a given address. + +
+

Functions

+
+- [tryRecover(hash, signature)](#ECDSA-tryRecover-bytes32-bytes-) +- [recover(hash, signature)](#ECDSA-recover-bytes32-bytes-) +- [tryRecover(hash, r, vs)](#ECDSA-tryRecover-bytes32-bytes32-bytes32-) +- [recover(hash, r, vs)](#ECDSA-recover-bytes32-bytes32-bytes32-) +- [tryRecover(hash, v, r, s)](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) +- [recover(hash, v, r, s)](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) +- [toEthSignedMessageHash(hash)](#ECDSA-toEthSignedMessageHash-bytes32-) +- [toEthSignedMessageHash(s)](#ECDSA-toEthSignedMessageHash-bytes-) +- [toTypedDataHash(domainSeparator, structHash)](#ECDSA-toTypedDataHash-bytes32-bytes32-) +- [toDataWithIntendedValidatorHash(validator, data)](#ECDSA-toDataWithIntendedValidatorHash-address-bytes-) +
+
+ + + +
+
+

tryRecover(bytes32 hash, bytes signature) → address, enum ECDSA.RecoverError

+
+

internal

+# +
+
+
+ +Returns the address that signed a hashed message (`hash`) with +`signature` or error string. This address can then be used for verification purposes. + +The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: +this function rejects them by requiring the `s` value to be in the lower +half order, and the `v` value to be either 27 or 28. + + +`hash` _must_ be the result of a hash operation for the +verification to be secure: it is possible to craft signatures that +recover to arbitrary addresses for non-hashed data. A safe way to ensure +this is by receiving a hash of the original message (which may otherwise +be too long), and then calling [`ECDSA.toEthSignedMessageHash`](#ECDSA-toEthSignedMessageHash-bytes-) on it. + + +Documentation for signature generation: +- with [Web3.js](https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign) +- with [ethers](https://docs.ethers.io/v5/api/signer/#Signer-signMessage) + +_Available since v4.3._ + +
+
+ + + +
+
+

recover(bytes32 hash, bytes signature) → address

+
+

internal

+# +
+
+
+ +Returns the address that signed a hashed message (`hash`) with +`signature`. This address can then be used for verification purposes. + +The `ecrecover` EVM opcode allows for malleable (non-unique) signatures: +this function rejects them by requiring the `s` value to be in the lower +half order, and the `v` value to be either 27 or 28. + + +`hash` _must_ be the result of a hash operation for the +verification to be secure: it is possible to craft signatures that +recover to arbitrary addresses for non-hashed data. A safe way to ensure +this is by receiving a hash of the original message (which may otherwise +be too long), and then calling [`ECDSA.toEthSignedMessageHash`](#ECDSA-toEthSignedMessageHash-bytes-) on it. + + +
+
+ + + +
+
+

tryRecover(bytes32 hash, bytes32 r, bytes32 vs) → address, enum ECDSA.RecoverError

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.tryRecover`](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) that receives the `r` and `vs` short-signature fields separately. + +See [EIP-2098 short signatures](https://eips.ethereum.org/EIPS/eip-2098) + +_Available since v4.3._ + +
+
+ + + +
+
+

recover(bytes32 hash, bytes32 r, bytes32 vs) → address

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) that receives the `r and `vs` short-signature fields separately. + +_Available since v4.2._ + +
+
+ + + +
+
+

tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address, enum ECDSA.RecoverError

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.tryRecover`](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) that receives the `v`, +`r` and `s` signature fields separately. + +_Available since v4.3._ + +
+
+ + + +
+
+

recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) that receives the `v`, +`r` and `s` signature fields separately. + +
+
+ + + +
+
+

toEthSignedMessageHash(bytes32 hash) → bytes32 message

+
+

internal

+# +
+
+
+ +Returns an Ethereum Signed Message, created from a `hash`. This +produces hash corresponding to the one signed with the +[`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) +JSON-RPC method as part of EIP-191. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

toEthSignedMessageHash(bytes s) → bytes32

+
+

internal

+# +
+
+
+ +Returns an Ethereum Signed Message, created from `s`. This +produces hash corresponding to the one signed with the +[`eth_sign`](https://eth.wiki/json-rpc/API#eth_sign) +JSON-RPC method as part of EIP-191. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) → bytes32 data

+
+

internal

+# +
+
+
+ +Returns an Ethereum Signed Typed Data, created from a +`domainSeparator` and a `structHash`. This produces hash corresponding +to the one signed with the +[`eth_signTypedData`](https://eips.ethereum.org/EIPS/eip-712) +JSON-RPC method as part of EIP-712. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

toDataWithIntendedValidatorHash(address validator, bytes data) → bytes32

+
+

internal

+# +
+
+
+ +Returns an Ethereum Signed Data with intended validator, created from a +`validator` and `data` according to the version 0 of EIP-191. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+ +## `EIP712` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +``` + +[EIP 712](https://eips.ethereum.org/EIPS/eip-712) is a standard for hashing and signing of typed structured data. + +The encoding specified in the EIP is very generic, and such a generic implementation in Solidity is not feasible, +thus this contract does not implement the encoding itself. Protocols need to implement the type-specific encoding +they need in their contracts using a combination of `abi.encode` and `keccak256`. + +This contract implements the EIP 712 domain separator ([`EIP712._domainSeparatorV4`](#EIP712-_domainSeparatorV4--)) that is used as part of the encoding +scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA +([`EIP712._hashTypedDataV4`](#EIP712-_hashTypedDataV4-bytes32-)). + +The implementation of the domain separator was designed to be as efficient as possible while still properly updating +the chain id to protect against replay attacks on an eventual fork of the chain. + +NOTE: This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method +[`eth_signTypedDataV4` in MetaMask](https://docs.metamask.io/guide/signing-data.html). + +NOTE: In the upgradeable version of this contract, the cached values will correspond to the address, and the domain +separator of the implementation contract. This will cause the `_domainSeparatorV4` function to always rebuild the +separator from the immutable values, which is cheaper than accessing a cached version in cold storage. + +_Available since v3.4._ + +
+

Functions

+
+- [constructor(name, version)](#EIP712-constructor-string-string-) +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +#### IERC5267 +
+
+ +
+

Events

+
+#### IERC5267 +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ + + +
+
+

constructor(string name, string version)

+
+

internal

+# +
+
+
+ +Initializes the domain separator and parameter caches. + +The meaning of `name` and `version` is specified in +[EIP 712](https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator): + +- `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. +- `version`: the current major version of the signing domain. + +NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts[smart +contract upgrade]. + +
+
+ + + +
+
+

_domainSeparatorV4() → bytes32

+
+

internal

+# +
+
+
+ +Returns the domain separator for the current chain. + +
+
+ + + +
+
+

_hashTypedDataV4(bytes32 structHash) → bytes32

+
+

internal

+# +
+
+
+ +Given an already [hashed struct](https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct), this +function returns the hash of the fully encoded EIP712 message for this domain. + +This hash can be used together with [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) to obtain the signer of a message. For example: + +```solidity +bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + keccak256("Mail(address to,string contents)"), + mailTo, + keccak256(bytes(mailContents)) +))); +address signer = ECDSA.recover(digest, signature); +``` + +
+
+ + + +
+
+

eip712Domain() → bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions

+
+

public

+# +
+
+
+ +See `EIP-5267`. + +_Available since v4.9._ + +
+
+ + + +
+ +## `MerkleProof` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +``` + +These functions deal with verification of Merkle Tree proofs. + +The tree and the proofs can be generated using our +[JavaScript library](https://github.com/OpenZeppelin/merkle-tree). +You will find a quickstart guide in the readme. + + +You should avoid using leaf values that are 64 bytes long prior to +hashing, or use a hash function other than keccak256 for hashing leaves. +This is because the concatenation of a sorted pair of internal nodes in +the merkle tree could be reinterpreted as a leaf value. +OpenZeppelin's JavaScript library generates merkle trees that are safe +against this attack out of the box. + + +
+

Functions

+
+- [verify(proof, root, leaf)](#MerkleProof-verify-bytes32---bytes32-bytes32-) +- [verifyCalldata(proof, root, leaf)](#MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-) +- [processProof(proof, leaf)](#MerkleProof-processProof-bytes32---bytes32-) +- [processProofCalldata(proof, leaf)](#MerkleProof-processProofCalldata-bytes32---bytes32-) +- [multiProofVerify(proof, proofFlags, root, leaves)](#MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---) +- [multiProofVerifyCalldata(proof, proofFlags, root, leaves)](#MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---) +- [processMultiProof(proof, proofFlags, leaves)](#MerkleProof-processMultiProof-bytes32---bool---bytes32---) +- [processMultiProofCalldata(proof, proofFlags, leaves)](#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---) +
+
+ + + +
+
+

verify(bytes32[] proof, bytes32 root, bytes32 leaf) → bool

+
+

internal

+# +
+
+
+ +Returns true if a `leaf` can be proved to be a part of a Merkle tree +defined by `root`. For this, a `proof` must be provided, containing +sibling hashes on the branch from the leaf to the root of the tree. Each +pair of leaves and each pair of pre-images are assumed to be sorted. + +
+
+ + + +
+
+

verifyCalldata(bytes32[] proof, bytes32 root, bytes32 leaf) → bool

+
+

internal

+# +
+
+
+ +Calldata version of [`MinimalForwarder.verify`](/contracts/4.x/api/metatx#MinimalForwarder-verify-struct-MinimalForwarder-ForwardRequest-bytes-) + +_Available since v4.7._ + +
+
+ + + +
+
+

processProof(bytes32[] proof, bytes32 leaf) → bytes32

+
+

internal

+# +
+
+
+ +Returns the rebuilt hash obtained by traversing a Merkle tree up +from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt +hash matches the root of the tree. When processing the proof, the pairs +of leafs & pre-images are assumed to be sorted. + +_Available since v4.4._ + +
+
+ + + +
+
+

processProofCalldata(bytes32[] proof, bytes32 leaf) → bytes32

+
+

internal

+# +
+
+
+ +Calldata version of [`MerkleProof.processProof`](#MerkleProof-processProof-bytes32---bytes32-) + +_Available since v4.7._ + +
+
+ + + +
+
+

multiProofVerify(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) → bool

+
+

internal

+# +
+
+
+ +Returns true if the `leaves` can be simultaneously proven to be a part of a merkle tree defined by +`root`, according to `proof` and `proofFlags` as described in [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---). + +CAUTION: Not all merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---) for details. + +_Available since v4.7._ + +
+
+ + + +
+
+

multiProofVerifyCalldata(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) → bool

+
+

internal

+# +
+
+
+ +Calldata version of [`MerkleProof.multiProofVerify`](#MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---) + +CAUTION: Not all merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---) for details. + +_Available since v4.7._ + +
+
+ + + +
+
+

processMultiProof(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) → bytes32 merkleRoot

+
+

internal

+# +
+
+
+ +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction +proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another +leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false +respectively. + +CAUTION: Not all merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree +is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the +tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + +_Available since v4.7._ + +
+
+ + + +
+
+

processMultiProofCalldata(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) → bytes32 merkleRoot

+
+

internal

+# +
+
+
+ +Calldata version of [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---). + +CAUTION: Not all merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---) for details. + +_Available since v4.7._ + +
+
+ + + +
+ +## `SignatureChecker` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +``` + +Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support both ECDSA +signatures from externally owned accounts (EOAs) as well as ERC1271 signatures from smart contract wallets like +Argent and Gnosis Safe. + +_Available since v4.1._ + +
+

Functions

+
+- [isValidSignatureNow(signer, hash, signature)](#SignatureChecker-isValidSignatureNow-address-bytes32-bytes-) +- [isValidERC1271SignatureNow(signer, hash, signature)](#SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes-) +
+
+ + + +
+
+

isValidSignatureNow(address signer, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Checks if a signature is valid for a given signer and data hash. If the signer is a smart contract, the +signature is validated against that smart contract using ERC1271, otherwise it's validated using `ECDSA.recover`. + +NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus +change through time. It could return true at block N and false at block N+1 (or the opposite). + +
+
+ + + +
+
+

isValidERC1271SignatureNow(address signer, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Checks if a signature is valid for a given signer and data hash. The signature is validated +against the signer smart contract using ERC1271. + +NOTE: Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus +change through time. It could return true at block N and false at block N+1 (or the opposite). + +
+
+ + + +
+ +## `ConditionalEscrow` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/escrow/ConditionalEscrow.sol"; +``` + +Base abstract escrow to only allow withdrawal if a condition is met. +Intended usage: See [`Escrow`](#Escrow). Same usage guidelines apply here. + +
+

Functions

+
+- [withdrawalAllowed(payee)](#ConditionalEscrow-withdrawalAllowed-address-) +- [withdraw(payee)](#ConditionalEscrow-withdraw-address-payable-) +#### Escrow +- [depositsOf(payee)](#Escrow-depositsOf-address-) +- [deposit(payee)](#Escrow-deposit-address-) +#### Ownable +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+#### Escrow +- [Deposited(payee, weiAmount)](#Escrow-Deposited-address-uint256-) +- [Withdrawn(payee, weiAmount)](#Escrow-Withdrawn-address-uint256-) +#### Ownable +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ + + +
+
+

withdrawalAllowed(address payee) → bool

+
+

public

+# +
+
+
+ +Returns whether an address is allowed to withdraw their funds. To be +implemented by derived contracts. + +
+
+ + + +
+
+

withdraw(address payable payee)

+
+

public

+# +
+
+
+ +Withdraw accumulated balance for a payee, forwarding all gas to the +recipient. + + +Forwarding all gas opens the door to reentrancy vulnerabilities. +Make sure you trust the recipient, or are either following the +checks-effects-interactions pattern or using [`ReentrancyGuard`](/contracts/4.x/api/security#ReentrancyGuard). + + +
+
+ + + +
+ +## `Escrow` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/escrow/Escrow.sol"; +``` + +Base escrow contract, holds funds designated for a payee until they +withdraw them. + +Intended usage: This contract (and derived escrow contracts) should be a +standalone contract, that only interacts with the contract that instantiated +it. That way, it is guaranteed that all Ether will be handled according to +the `Escrow` rules, and there is no need to check for payable functions or +transfers in the inheritance tree. The contract that uses the escrow as its +payment method should be its owner, and provide public methods redirecting +to the escrow's deposit and withdraw. + +
+

Functions

+
+- [depositsOf(payee)](#Escrow-depositsOf-address-) +- [deposit(payee)](#Escrow-deposit-address-) +- [withdraw(payee)](#Escrow-withdraw-address-payable-) +#### Ownable +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+- [Deposited(payee, weiAmount)](#Escrow-Deposited-address-uint256-) +- [Withdrawn(payee, weiAmount)](#Escrow-Withdrawn-address-uint256-) +#### Ownable +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ + + +
+
+

depositsOf(address payee) → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

deposit(address payee)

+
+

public

+# +
+
+
+ +Stores the sent amount as credit to be withdrawn. + +
+
+ + + +
+
+

withdraw(address payable payee)

+
+

public

+# +
+
+
+ +Withdraw accumulated balance for a payee, forwarding all gas to the +recipient. + + +Forwarding all gas opens the door to reentrancy vulnerabilities. +Make sure you trust the recipient, or are either following the +checks-effects-interactions pattern or using [`ReentrancyGuard`](/contracts/4.x/api/security#ReentrancyGuard). + + +
+
+ + + +
+
+

Deposited(address indexed payee, uint256 weiAmount)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

Withdrawn(address indexed payee, uint256 weiAmount)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `RefundEscrow` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/escrow/RefundEscrow.sol"; +``` + +Escrow that holds funds for a beneficiary, deposited from multiple +parties. +Intended usage: See [`Escrow`](#Escrow). Same usage guidelines apply here. +The owner account (that is, the contract that instantiates this +contract) may deposit, close the deposit period, and allow for either +withdrawal by the beneficiary, or refunds to the depositors. All interactions +with `RefundEscrow` will be made through the owner contract. + +
+

Functions

+
+- [constructor(beneficiary_)](#RefundEscrow-constructor-address-payable-) +- [state()](#RefundEscrow-state--) +- [beneficiary()](#RefundEscrow-beneficiary--) +- [deposit(refundee)](#RefundEscrow-deposit-address-) +- [close()](#RefundEscrow-close--) +- [enableRefunds()](#RefundEscrow-enableRefunds--) +- [beneficiaryWithdraw()](#RefundEscrow-beneficiaryWithdraw--) +- [withdrawalAllowed()](#RefundEscrow-withdrawalAllowed-address-) +#### ConditionalEscrow +- [withdraw(payee)](#ConditionalEscrow-withdraw-address-payable-) +#### Escrow +- [depositsOf(payee)](#Escrow-depositsOf-address-) +#### Ownable +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+- [RefundsClosed()](#RefundEscrow-RefundsClosed--) +- [RefundsEnabled()](#RefundEscrow-RefundsEnabled--) +#### ConditionalEscrow +#### Escrow +- [Deposited(payee, weiAmount)](#Escrow-Deposited-address-uint256-) +- [Withdrawn(payee, weiAmount)](#Escrow-Withdrawn-address-uint256-) +#### Ownable +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ + + +
+
+

constructor(address payable beneficiary_)

+
+

public

+# +
+
+
+ +Constructor. + +
+
+ + + +
+
+

state() → enum RefundEscrow.State

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

beneficiary() → address payable

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

deposit(address refundee)

+
+

public

+# +
+
+
+ +Stores funds that may later be refunded. + +
+
+ + + +
+
+

close()

+
+

public

+# +
+
+
+ +Allows for the beneficiary to withdraw their funds, rejecting +further deposits. + +
+
+ + + +
+
+

enableRefunds()

+
+

public

+# +
+
+
+ +Allows for refunds to take place, rejecting further deposits. + +
+
+ + + +
+
+

beneficiaryWithdraw()

+
+

public

+# +
+
+
+ +Withdraws the beneficiary's funds. + +
+
+ + + +
+
+

withdrawalAllowed(address) → bool

+
+

public

+# +
+
+
+ +Returns whether refundees can withdraw their deposits (be refunded). The overridden function receives a +'payee' argument, but we ignore it here since the condition is global, not per-payee. + +
+
+ + + +
+
+

RefundsClosed()

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

RefundsEnabled()

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `ERC165` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +``` + +Implementation of the [`IERC165`](#IERC165) interface. + +Contracts that want to implement ERC165 should inherit from this contract and override [`AccessControl.supportsInterface`](/contracts/4.x/api/access#AccessControl-supportsInterface-bytes4-) to check +for the additional interface id that will be supported. For example: + +```solidity +function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); +} +``` + +Alternatively, [`ERC165Storage`](#ERC165Storage) provides an easier to use but more expensive implementation. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC165-supportsInterface-bytes4-) +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+ +## `ERC165Checker` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +``` + +Library used to query support of an interface declared via [`IERC165`](#IERC165). + +Note that these functions return the actual result of the query: they do not +`revert` if an interface is not supported. It is up to the caller to decide +what to do in these cases. + +
+

Functions

+
+- [supportsERC165(account)](#ERC165Checker-supportsERC165-address-) +- [supportsInterface(account, interfaceId)](#ERC165Checker-supportsInterface-address-bytes4-) +- [getSupportedInterfaces(account, interfaceIds)](#ERC165Checker-getSupportedInterfaces-address-bytes4---) +- [supportsAllInterfaces(account, interfaceIds)](#ERC165Checker-supportsAllInterfaces-address-bytes4---) +- [supportsERC165InterfaceUnchecked(account, interfaceId)](#ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4-) +
+
+ + + +
+
+

supportsERC165(address account) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` supports the [`IERC165`](#IERC165) interface. + +
+
+ + + +
+
+

supportsInterface(address account, bytes4 interfaceId) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` supports the interface defined by +`interfaceId`. Support for [`IERC165`](#IERC165) itself is queried automatically. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

getSupportedInterfaces(address account, bytes4[] interfaceIds) → bool[]

+
+

internal

+# +
+
+
+ +Returns a boolean array where each value corresponds to the +interfaces passed in and whether they're supported or not. This allows +you to batch check interfaces for a contract where your expectation +is that some interfaces may not be supported. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +_Available since v3.4._ + +
+
+ + + +
+
+

supportsAllInterfaces(address account, bytes4[] interfaceIds) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` supports all the interfaces defined in +`interfaceIds`. Support for [`IERC165`](#IERC165) itself is queried automatically. + +Batch-querying can lead to gas savings by skipping repeated checks for +[`IERC165`](#IERC165) support. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) → bool

+
+

internal

+# +
+
+
+ +Assumes that account contains a contract that supports ERC165, otherwise +the behavior of this method is undefined. This precondition can be checked +with [`ERC165Checker.supportsERC165`](#ERC165Checker-supportsERC165-address-). + +Some precompiled contracts will falsely indicate support for a given interface, so caution +should be exercised when using this function. + +Interface identification is specified in ERC-165. + +
+
+ + + +
+ +## `ERC165Storage` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165Storage.sol"; +``` + +Storage based implementation of the [`IERC165`](#IERC165) interface. + +Contracts may inherit from this and call [`ERC165Storage._registerInterface`](#ERC165Storage-_registerInterface-bytes4-) to declare +their support of an interface. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC165Storage-supportsInterface-bytes4-) +- [_registerInterface(interfaceId)](#ERC165Storage-_registerInterface-bytes4-) +#### ERC165 +#### IERC165 +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

_registerInterface(bytes4 interfaceId)

+
+

internal

+# +
+
+
+ +Registers the contract as an implementer of the interface defined by +`interfaceId`. Support of the actual ERC165 interface is automatic and +registering its interface id is not required. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +Requirements: + +- `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`). + +
+
+ + + +
+ +## `ERC1820Implementer` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC1820Implementer.sol"; +``` + +Implementation of the [`IERC1820Implementer`](#IERC1820Implementer) interface. + +Contracts may inherit from this and call [`ERC1820Implementer._registerInterfaceForAddress`](#ERC1820Implementer-_registerInterfaceForAddress-bytes32-address-) to +declare their willingness to be implementers. +[`IERC1820Registry.setInterfaceImplementer`](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-) should then be called for the +registration to be complete. + +CAUTION: This file is deprecated as of v4.9 and will be removed in the next major release. + +
+

Functions

+
+- [canImplementInterfaceForAddress(interfaceHash, account)](#ERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-) +- [_registerInterfaceForAddress(interfaceHash, account)](#ERC1820Implementer-_registerInterfaceForAddress-bytes32-address-) +#### IERC1820Implementer +
+
+ + + +
+
+

canImplementInterfaceForAddress(bytes32 interfaceHash, address account) → bytes32

+
+

public

+# +
+
+
+ +See [`IERC1820Implementer.canImplementInterfaceForAddress`](#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-). + +
+
+ + + +
+
+

_registerInterfaceForAddress(bytes32 interfaceHash, address account)

+
+

internal

+# +
+
+
+ +Declares the contract as willing to be an implementer of +`interfaceHash` for `account`. + +See [`IERC1820Registry.setInterfaceImplementer`](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-) and +[`IERC1820Registry.interfaceHash`](#IERC1820Registry-interfaceHash-string-). + +
+
+ + + +
+ +## `IERC165` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +``` + +Interface of the ERC165 standard, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-165). + +Implementers can declare support of contract interfaces, which can then be +queried by others ([`ERC165Checker`](#ERC165Checker)). + +For an implementation, see [`ERC165`](#ERC165). + +
+

Functions

+
+- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

external

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+ +## `IERC1820Implementer` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/IERC1820Implementer.sol"; +``` + +Interface for an ERC1820 implementer, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface). +Used by contracts that will be registered as implementers in the +[`IERC1820Registry`](#IERC1820Registry). + +
+

Functions

+
+- [canImplementInterfaceForAddress(interfaceHash, account)](#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-) +
+
+ + + +
+
+

canImplementInterfaceForAddress(bytes32 interfaceHash, address account) → bytes32

+
+

external

+# +
+
+
+ +Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract +implements `interfaceHash` for `account`. + +See [`IERC1820Registry.setInterfaceImplementer`](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-). + +
+
+ + + +
+ +## `IERC1820Registry` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol"; +``` + +Interface of the global ERC1820 Registry, as defined in the +[EIP](https://eips.ethereum.org/EIPS/eip-1820). Accounts may register +implementers for interfaces in this registry, as well as query support. + +Implementers may be shared by multiple accounts, and can also implement more +than a single interface for each account. Contracts can implement interfaces +for themselves, but externally-owned accounts (EOA) must delegate this to a +contract. + +[`IERC165`](#IERC165) interfaces can also be queried via the registry. + +For an in-depth explanation and source code analysis, see the EIP text. + +
+

Functions

+
+- [setManager(account, newManager)](#IERC1820Registry-setManager-address-address-) +- [getManager(account)](#IERC1820Registry-getManager-address-) +- [setInterfaceImplementer(account, _interfaceHash, implementer)](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-) +- [getInterfaceImplementer(account, _interfaceHash)](#IERC1820Registry-getInterfaceImplementer-address-bytes32-) +- [interfaceHash(interfaceName)](#IERC1820Registry-interfaceHash-string-) +- [updateERC165Cache(account, interfaceId)](#IERC1820Registry-updateERC165Cache-address-bytes4-) +- [implementsERC165Interface(account, interfaceId)](#IERC1820Registry-implementsERC165Interface-address-bytes4-) +- [implementsERC165InterfaceNoCache(account, interfaceId)](#IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-) +
+
+ +
+

Events

+
+- [InterfaceImplementerSet(account, interfaceHash, implementer)](#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-) +- [ManagerChanged(account, newManager)](#IERC1820Registry-ManagerChanged-address-address-) +
+
+ + + +
+
+

setManager(address account, address newManager)

+
+

external

+# +
+
+
+ +Sets `newManager` as the manager for `account`. A manager of an +account is able to set interface implementers for it. + +By default, each account is its own manager. Passing a value of `0x0` in +`newManager` will reset the manager to this initial state. + +Emits a [`IERC1820Registry.ManagerChanged`](#IERC1820Registry-ManagerChanged-address-address-) event. + +Requirements: + +- the caller must be the current manager for `account`. + +
+
+ + + +
+
+

getManager(address account) → address

+
+

external

+# +
+
+
+ +Returns the manager for `account`. + +See [`IERC1820Registry.setManager`](#IERC1820Registry-setManager-address-address-). + +
+
+ + + +
+
+

setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer)

+
+

external

+# +
+
+
+ +Sets the `implementer` contract as ``account``'s implementer for +`interfaceHash`. + +`account` being the zero address is an alias for the caller's address. +The zero address can also be used in `implementer` to remove an old one. + +See [`IERC1820Registry.interfaceHash`](#IERC1820Registry-interfaceHash-string-) to learn how these are created. + +Emits an [`IERC1820Registry.InterfaceImplementerSet`](#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-) event. + +Requirements: + +- the caller must be the current manager for `account`. +- `interfaceHash` must not be an [`IERC165`](#IERC165) interface id (i.e. it must not +end in 28 zeroes). +- `implementer` must implement [`IERC1820Implementer`](#IERC1820Implementer) and return true when +queried for support, unless `implementer` is the caller. See +[`IERC1820Implementer.canImplementInterfaceForAddress`](#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-). + +
+
+ + + +
+
+

getInterfaceImplementer(address account, bytes32 _interfaceHash) → address

+
+

external

+# +
+
+
+ +Returns the implementer of `interfaceHash` for `account`. If no such +implementer is registered, returns the zero address. + +If `interfaceHash` is an [`IERC165`](#IERC165) interface id (i.e. it ends with 28 +zeroes), `account` will be queried for support of it. + +`account` being the zero address is an alias for the caller's address. + +
+
+ + + +
+
+

interfaceHash(string interfaceName) → bytes32

+
+

external

+# +
+
+
+ +Returns the interface hash for an `interfaceName`, as defined in the +corresponding +[section of the EIP](https://eips.ethereum.org/EIPS/eip-1820#interface-name). + +
+
+ + + +
+
+

updateERC165Cache(address account, bytes4 interfaceId)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

implementsERC165Interface(address account, bytes4 interfaceId) → bool

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) → bool

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ManagerChanged(address indexed account, address indexed newManager)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `Math` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/Math.sol"; +``` + +Standard math utilities missing in the Solidity language. + +
+

Functions

+
+- [max(a, b)](#Math-max-uint256-uint256-) +- [min(a, b)](#Math-min-uint256-uint256-) +- [average(a, b)](#Math-average-uint256-uint256-) +- [ceilDiv(a, b)](#Math-ceilDiv-uint256-uint256-) +- [mulDiv(x, y, denominator)](#Math-mulDiv-uint256-uint256-uint256-) +- [mulDiv(x, y, denominator, rounding)](#Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding-) +- [sqrt(a)](#Math-sqrt-uint256-) +- [sqrt(a, rounding)](#Math-sqrt-uint256-enum-Math-Rounding-) +- [log2(value)](#Math-log2-uint256-) +- [log2(value, rounding)](#Math-log2-uint256-enum-Math-Rounding-) +- [log10(value)](#Math-log10-uint256-) +- [log10(value, rounding)](#Math-log10-uint256-enum-Math-Rounding-) +- [log256(value)](#Math-log256-uint256-) +- [log256(value, rounding)](#Math-log256-uint256-enum-Math-Rounding-) +
+
+ + + +
+
+

max(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the largest of two numbers. + +
+
+ + + +
+
+

min(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the smallest of two numbers. + +
+
+ + + +
+
+

average(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the average of two numbers. The result is rounded towards +zero. + +
+
+ + + +
+
+

ceilDiv(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the ceiling of the division of two numbers. + +This differs from standard division with `/` in that it rounds up instead +of rounding down. + +
+
+ + + +
+
+

mulDiv(uint256 x, uint256 y, uint256 denominator) → uint256 result

+
+

internal

+# +
+
+
+ +Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) +with further edits by Uniswap Labs also under MIT license. + +
+
+ + + +
+
+

mulDiv(uint256 x, uint256 y, uint256 denominator, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

sqrt(uint256 a) → uint256

+
+

internal

+# +
+
+
+ +Returns the square root of a number. If the number is not a perfect square, the value is rounded down. + +Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11). + +
+
+ + + +
+
+

sqrt(uint256 a, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

log2(uint256 value) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 2, rounded down, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

log2(uint256 value, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 2, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

log10(uint256 value) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 10, rounded down, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

log10(uint256 value, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 10, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

log256(uint256 value) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 256, rounded down, of a positive value. +Returns 0 if given 0. + +Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + +
+
+ + + +
+
+

log256(uint256 value, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 256, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+ +## `SafeCast` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +``` + +Wrappers over Solidity's uintXX/intXX casting operators with added overflow +checks. + +Downcasting from uint256/int256 in Solidity does not revert on overflow. This can +easily result in undesired exploitation or bugs, since developers usually +assume that overflows raise errors. `SafeCast` restores this intuition by +reverting the transaction when such an operation overflows. + +Using this library instead of the unchecked operations eliminates an entire +class of bugs, so it's recommended to use it always. + +Can be combined with [`SafeMath`](#SafeMath) and [`SignedSafeMath`](#SignedSafeMath) to extend it to smaller types, by performing +all math on `uint256` and `int256` and then downcasting. + +
+

Functions

+
+- [toUint248(value)](#SafeCast-toUint248-uint256-) +- [toUint240(value)](#SafeCast-toUint240-uint256-) +- [toUint232(value)](#SafeCast-toUint232-uint256-) +- [toUint224(value)](#SafeCast-toUint224-uint256-) +- [toUint216(value)](#SafeCast-toUint216-uint256-) +- [toUint208(value)](#SafeCast-toUint208-uint256-) +- [toUint200(value)](#SafeCast-toUint200-uint256-) +- [toUint192(value)](#SafeCast-toUint192-uint256-) +- [toUint184(value)](#SafeCast-toUint184-uint256-) +- [toUint176(value)](#SafeCast-toUint176-uint256-) +- [toUint168(value)](#SafeCast-toUint168-uint256-) +- [toUint160(value)](#SafeCast-toUint160-uint256-) +- [toUint152(value)](#SafeCast-toUint152-uint256-) +- [toUint144(value)](#SafeCast-toUint144-uint256-) +- [toUint136(value)](#SafeCast-toUint136-uint256-) +- [toUint128(value)](#SafeCast-toUint128-uint256-) +- [toUint120(value)](#SafeCast-toUint120-uint256-) +- [toUint112(value)](#SafeCast-toUint112-uint256-) +- [toUint104(value)](#SafeCast-toUint104-uint256-) +- [toUint96(value)](#SafeCast-toUint96-uint256-) +- [toUint88(value)](#SafeCast-toUint88-uint256-) +- [toUint80(value)](#SafeCast-toUint80-uint256-) +- [toUint72(value)](#SafeCast-toUint72-uint256-) +- [toUint64(value)](#SafeCast-toUint64-uint256-) +- [toUint56(value)](#SafeCast-toUint56-uint256-) +- [toUint48(value)](#SafeCast-toUint48-uint256-) +- [toUint40(value)](#SafeCast-toUint40-uint256-) +- [toUint32(value)](#SafeCast-toUint32-uint256-) +- [toUint24(value)](#SafeCast-toUint24-uint256-) +- [toUint16(value)](#SafeCast-toUint16-uint256-) +- [toUint8(value)](#SafeCast-toUint8-uint256-) +- [toUint256(value)](#SafeCast-toUint256-int256-) +- [toInt248(value)](#SafeCast-toInt248-int256-) +- [toInt240(value)](#SafeCast-toInt240-int256-) +- [toInt232(value)](#SafeCast-toInt232-int256-) +- [toInt224(value)](#SafeCast-toInt224-int256-) +- [toInt216(value)](#SafeCast-toInt216-int256-) +- [toInt208(value)](#SafeCast-toInt208-int256-) +- [toInt200(value)](#SafeCast-toInt200-int256-) +- [toInt192(value)](#SafeCast-toInt192-int256-) +- [toInt184(value)](#SafeCast-toInt184-int256-) +- [toInt176(value)](#SafeCast-toInt176-int256-) +- [toInt168(value)](#SafeCast-toInt168-int256-) +- [toInt160(value)](#SafeCast-toInt160-int256-) +- [toInt152(value)](#SafeCast-toInt152-int256-) +- [toInt144(value)](#SafeCast-toInt144-int256-) +- [toInt136(value)](#SafeCast-toInt136-int256-) +- [toInt128(value)](#SafeCast-toInt128-int256-) +- [toInt120(value)](#SafeCast-toInt120-int256-) +- [toInt112(value)](#SafeCast-toInt112-int256-) +- [toInt104(value)](#SafeCast-toInt104-int256-) +- [toInt96(value)](#SafeCast-toInt96-int256-) +- [toInt88(value)](#SafeCast-toInt88-int256-) +- [toInt80(value)](#SafeCast-toInt80-int256-) +- [toInt72(value)](#SafeCast-toInt72-int256-) +- [toInt64(value)](#SafeCast-toInt64-int256-) +- [toInt56(value)](#SafeCast-toInt56-int256-) +- [toInt48(value)](#SafeCast-toInt48-int256-) +- [toInt40(value)](#SafeCast-toInt40-int256-) +- [toInt32(value)](#SafeCast-toInt32-int256-) +- [toInt24(value)](#SafeCast-toInt24-int256-) +- [toInt16(value)](#SafeCast-toInt16-int256-) +- [toInt8(value)](#SafeCast-toInt8-int256-) +- [toInt256(value)](#SafeCast-toInt256-uint256-) +
+
+ + + +
+
+

toUint248(uint256 value) → uint248

+
+

internal

+# +
+
+
+ +Returns the downcasted uint248 from uint256, reverting on +overflow (when the input is greater than largest uint248). + +Counterpart to Solidity's `uint248` operator. + +Requirements: + +- input must fit into 248 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint240(uint256 value) → uint240

+
+

internal

+# +
+
+
+ +Returns the downcasted uint240 from uint256, reverting on +overflow (when the input is greater than largest uint240). + +Counterpart to Solidity's `uint240` operator. + +Requirements: + +- input must fit into 240 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint232(uint256 value) → uint232

+
+

internal

+# +
+
+
+ +Returns the downcasted uint232 from uint256, reverting on +overflow (when the input is greater than largest uint232). + +Counterpart to Solidity's `uint232` operator. + +Requirements: + +- input must fit into 232 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint224(uint256 value) → uint224

+
+

internal

+# +
+
+
+ +Returns the downcasted uint224 from uint256, reverting on +overflow (when the input is greater than largest uint224). + +Counterpart to Solidity's `uint224` operator. + +Requirements: + +- input must fit into 224 bits + +_Available since v4.2._ + +
+
+ + + +
+
+

toUint216(uint256 value) → uint216

+
+

internal

+# +
+
+
+ +Returns the downcasted uint216 from uint256, reverting on +overflow (when the input is greater than largest uint216). + +Counterpart to Solidity's `uint216` operator. + +Requirements: + +- input must fit into 216 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint208(uint256 value) → uint208

+
+

internal

+# +
+
+
+ +Returns the downcasted uint208 from uint256, reverting on +overflow (when the input is greater than largest uint208). + +Counterpart to Solidity's `uint208` operator. + +Requirements: + +- input must fit into 208 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint200(uint256 value) → uint200

+
+

internal

+# +
+
+
+ +Returns the downcasted uint200 from uint256, reverting on +overflow (when the input is greater than largest uint200). + +Counterpart to Solidity's `uint200` operator. + +Requirements: + +- input must fit into 200 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint192(uint256 value) → uint192

+
+

internal

+# +
+
+
+ +Returns the downcasted uint192 from uint256, reverting on +overflow (when the input is greater than largest uint192). + +Counterpart to Solidity's `uint192` operator. + +Requirements: + +- input must fit into 192 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint184(uint256 value) → uint184

+
+

internal

+# +
+
+
+ +Returns the downcasted uint184 from uint256, reverting on +overflow (when the input is greater than largest uint184). + +Counterpart to Solidity's `uint184` operator. + +Requirements: + +- input must fit into 184 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint176(uint256 value) → uint176

+
+

internal

+# +
+
+
+ +Returns the downcasted uint176 from uint256, reverting on +overflow (when the input is greater than largest uint176). + +Counterpart to Solidity's `uint176` operator. + +Requirements: + +- input must fit into 176 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint168(uint256 value) → uint168

+
+

internal

+# +
+
+
+ +Returns the downcasted uint168 from uint256, reverting on +overflow (when the input is greater than largest uint168). + +Counterpart to Solidity's `uint168` operator. + +Requirements: + +- input must fit into 168 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint160(uint256 value) → uint160

+
+

internal

+# +
+
+
+ +Returns the downcasted uint160 from uint256, reverting on +overflow (when the input is greater than largest uint160). + +Counterpart to Solidity's `uint160` operator. + +Requirements: + +- input must fit into 160 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint152(uint256 value) → uint152

+
+

internal

+# +
+
+
+ +Returns the downcasted uint152 from uint256, reverting on +overflow (when the input is greater than largest uint152). + +Counterpart to Solidity's `uint152` operator. + +Requirements: + +- input must fit into 152 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint144(uint256 value) → uint144

+
+

internal

+# +
+
+
+ +Returns the downcasted uint144 from uint256, reverting on +overflow (when the input is greater than largest uint144). + +Counterpart to Solidity's `uint144` operator. + +Requirements: + +- input must fit into 144 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint136(uint256 value) → uint136

+
+

internal

+# +
+
+
+ +Returns the downcasted uint136 from uint256, reverting on +overflow (when the input is greater than largest uint136). + +Counterpart to Solidity's `uint136` operator. + +Requirements: + +- input must fit into 136 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint128(uint256 value) → uint128

+
+

internal

+# +
+
+
+ +Returns the downcasted uint128 from uint256, reverting on +overflow (when the input is greater than largest uint128). + +Counterpart to Solidity's `uint128` operator. + +Requirements: + +- input must fit into 128 bits + +_Available since v2.5._ + +
+
+ + + +
+
+

toUint120(uint256 value) → uint120

+
+

internal

+# +
+
+
+ +Returns the downcasted uint120 from uint256, reverting on +overflow (when the input is greater than largest uint120). + +Counterpart to Solidity's `uint120` operator. + +Requirements: + +- input must fit into 120 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint112(uint256 value) → uint112

+
+

internal

+# +
+
+
+ +Returns the downcasted uint112 from uint256, reverting on +overflow (when the input is greater than largest uint112). + +Counterpart to Solidity's `uint112` operator. + +Requirements: + +- input must fit into 112 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint104(uint256 value) → uint104

+
+

internal

+# +
+
+
+ +Returns the downcasted uint104 from uint256, reverting on +overflow (when the input is greater than largest uint104). + +Counterpart to Solidity's `uint104` operator. + +Requirements: + +- input must fit into 104 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint96(uint256 value) → uint96

+
+

internal

+# +
+
+
+ +Returns the downcasted uint96 from uint256, reverting on +overflow (when the input is greater than largest uint96). + +Counterpart to Solidity's `uint96` operator. + +Requirements: + +- input must fit into 96 bits + +_Available since v4.2._ + +
+
+ + + +
+
+

toUint88(uint256 value) → uint88

+
+

internal

+# +
+
+
+ +Returns the downcasted uint88 from uint256, reverting on +overflow (when the input is greater than largest uint88). + +Counterpart to Solidity's `uint88` operator. + +Requirements: + +- input must fit into 88 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint80(uint256 value) → uint80

+
+

internal

+# +
+
+
+ +Returns the downcasted uint80 from uint256, reverting on +overflow (when the input is greater than largest uint80). + +Counterpart to Solidity's `uint80` operator. + +Requirements: + +- input must fit into 80 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint72(uint256 value) → uint72

+
+

internal

+# +
+
+
+ +Returns the downcasted uint72 from uint256, reverting on +overflow (when the input is greater than largest uint72). + +Counterpart to Solidity's `uint72` operator. + +Requirements: + +- input must fit into 72 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint64(uint256 value) → uint64

+
+

internal

+# +
+
+
+ +Returns the downcasted uint64 from uint256, reverting on +overflow (when the input is greater than largest uint64). + +Counterpart to Solidity's `uint64` operator. + +Requirements: + +- input must fit into 64 bits + +_Available since v2.5._ + +
+
+ + + +
+
+

toUint56(uint256 value) → uint56

+
+

internal

+# +
+
+
+ +Returns the downcasted uint56 from uint256, reverting on +overflow (when the input is greater than largest uint56). + +Counterpart to Solidity's `uint56` operator. + +Requirements: + +- input must fit into 56 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint48(uint256 value) → uint48

+
+

internal

+# +
+
+
+ +Returns the downcasted uint48 from uint256, reverting on +overflow (when the input is greater than largest uint48). + +Counterpart to Solidity's `uint48` operator. + +Requirements: + +- input must fit into 48 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint40(uint256 value) → uint40

+
+

internal

+# +
+
+
+ +Returns the downcasted uint40 from uint256, reverting on +overflow (when the input is greater than largest uint40). + +Counterpart to Solidity's `uint40` operator. + +Requirements: + +- input must fit into 40 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint32(uint256 value) → uint32

+
+

internal

+# +
+
+
+ +Returns the downcasted uint32 from uint256, reverting on +overflow (when the input is greater than largest uint32). + +Counterpart to Solidity's `uint32` operator. + +Requirements: + +- input must fit into 32 bits + +_Available since v2.5._ + +
+
+ + + +
+
+

toUint24(uint256 value) → uint24

+
+

internal

+# +
+
+
+ +Returns the downcasted uint24 from uint256, reverting on +overflow (when the input is greater than largest uint24). + +Counterpart to Solidity's `uint24` operator. + +Requirements: + +- input must fit into 24 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toUint16(uint256 value) → uint16

+
+

internal

+# +
+
+
+ +Returns the downcasted uint16 from uint256, reverting on +overflow (when the input is greater than largest uint16). + +Counterpart to Solidity's `uint16` operator. + +Requirements: + +- input must fit into 16 bits + +_Available since v2.5._ + +
+
+ + + +
+
+

toUint8(uint256 value) → uint8

+
+

internal

+# +
+
+
+ +Returns the downcasted uint8 from uint256, reverting on +overflow (when the input is greater than largest uint8). + +Counterpart to Solidity's `uint8` operator. + +Requirements: + +- input must fit into 8 bits + +_Available since v2.5._ + +
+
+ + + +
+
+

toUint256(int256 value) → uint256

+
+

internal

+# +
+
+
+ +Converts a signed int256 into an unsigned uint256. + +Requirements: + +- input must be greater than or equal to 0. + +_Available since v3.0._ + +
+
+ + + +
+
+

toInt248(int256 value) → int248 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int248 from int256, reverting on +overflow (when the input is less than smallest int248 or +greater than largest int248). + +Counterpart to Solidity's `int248` operator. + +Requirements: + +- input must fit into 248 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt240(int256 value) → int240 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int240 from int256, reverting on +overflow (when the input is less than smallest int240 or +greater than largest int240). + +Counterpart to Solidity's `int240` operator. + +Requirements: + +- input must fit into 240 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt232(int256 value) → int232 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int232 from int256, reverting on +overflow (when the input is less than smallest int232 or +greater than largest int232). + +Counterpart to Solidity's `int232` operator. + +Requirements: + +- input must fit into 232 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt224(int256 value) → int224 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int224 from int256, reverting on +overflow (when the input is less than smallest int224 or +greater than largest int224). + +Counterpart to Solidity's `int224` operator. + +Requirements: + +- input must fit into 224 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt216(int256 value) → int216 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int216 from int256, reverting on +overflow (when the input is less than smallest int216 or +greater than largest int216). + +Counterpart to Solidity's `int216` operator. + +Requirements: + +- input must fit into 216 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt208(int256 value) → int208 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int208 from int256, reverting on +overflow (when the input is less than smallest int208 or +greater than largest int208). + +Counterpart to Solidity's `int208` operator. + +Requirements: + +- input must fit into 208 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt200(int256 value) → int200 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int200 from int256, reverting on +overflow (when the input is less than smallest int200 or +greater than largest int200). + +Counterpart to Solidity's `int200` operator. + +Requirements: + +- input must fit into 200 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt192(int256 value) → int192 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int192 from int256, reverting on +overflow (when the input is less than smallest int192 or +greater than largest int192). + +Counterpart to Solidity's `int192` operator. + +Requirements: + +- input must fit into 192 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt184(int256 value) → int184 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int184 from int256, reverting on +overflow (when the input is less than smallest int184 or +greater than largest int184). + +Counterpart to Solidity's `int184` operator. + +Requirements: + +- input must fit into 184 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt176(int256 value) → int176 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int176 from int256, reverting on +overflow (when the input is less than smallest int176 or +greater than largest int176). + +Counterpart to Solidity's `int176` operator. + +Requirements: + +- input must fit into 176 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt168(int256 value) → int168 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int168 from int256, reverting on +overflow (when the input is less than smallest int168 or +greater than largest int168). + +Counterpart to Solidity's `int168` operator. + +Requirements: + +- input must fit into 168 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt160(int256 value) → int160 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int160 from int256, reverting on +overflow (when the input is less than smallest int160 or +greater than largest int160). + +Counterpart to Solidity's `int160` operator. + +Requirements: + +- input must fit into 160 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt152(int256 value) → int152 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int152 from int256, reverting on +overflow (when the input is less than smallest int152 or +greater than largest int152). + +Counterpart to Solidity's `int152` operator. + +Requirements: + +- input must fit into 152 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt144(int256 value) → int144 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int144 from int256, reverting on +overflow (when the input is less than smallest int144 or +greater than largest int144). + +Counterpart to Solidity's `int144` operator. + +Requirements: + +- input must fit into 144 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt136(int256 value) → int136 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int136 from int256, reverting on +overflow (when the input is less than smallest int136 or +greater than largest int136). + +Counterpart to Solidity's `int136` operator. + +Requirements: + +- input must fit into 136 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt128(int256 value) → int128 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int128 from int256, reverting on +overflow (when the input is less than smallest int128 or +greater than largest int128). + +Counterpart to Solidity's `int128` operator. + +Requirements: + +- input must fit into 128 bits + +_Available since v3.1._ + +
+
+ + + +
+
+

toInt120(int256 value) → int120 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int120 from int256, reverting on +overflow (when the input is less than smallest int120 or +greater than largest int120). + +Counterpart to Solidity's `int120` operator. + +Requirements: + +- input must fit into 120 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt112(int256 value) → int112 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int112 from int256, reverting on +overflow (when the input is less than smallest int112 or +greater than largest int112). + +Counterpart to Solidity's `int112` operator. + +Requirements: + +- input must fit into 112 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt104(int256 value) → int104 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int104 from int256, reverting on +overflow (when the input is less than smallest int104 or +greater than largest int104). + +Counterpart to Solidity's `int104` operator. + +Requirements: + +- input must fit into 104 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt96(int256 value) → int96 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int96 from int256, reverting on +overflow (when the input is less than smallest int96 or +greater than largest int96). + +Counterpart to Solidity's `int96` operator. + +Requirements: + +- input must fit into 96 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt88(int256 value) → int88 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int88 from int256, reverting on +overflow (when the input is less than smallest int88 or +greater than largest int88). + +Counterpart to Solidity's `int88` operator. + +Requirements: + +- input must fit into 88 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt80(int256 value) → int80 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int80 from int256, reverting on +overflow (when the input is less than smallest int80 or +greater than largest int80). + +Counterpart to Solidity's `int80` operator. + +Requirements: + +- input must fit into 80 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt72(int256 value) → int72 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int72 from int256, reverting on +overflow (when the input is less than smallest int72 or +greater than largest int72). + +Counterpart to Solidity's `int72` operator. + +Requirements: + +- input must fit into 72 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt64(int256 value) → int64 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int64 from int256, reverting on +overflow (when the input is less than smallest int64 or +greater than largest int64). + +Counterpart to Solidity's `int64` operator. + +Requirements: + +- input must fit into 64 bits + +_Available since v3.1._ + +
+
+ + + +
+
+

toInt56(int256 value) → int56 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int56 from int256, reverting on +overflow (when the input is less than smallest int56 or +greater than largest int56). + +Counterpart to Solidity's `int56` operator. + +Requirements: + +- input must fit into 56 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt48(int256 value) → int48 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int48 from int256, reverting on +overflow (when the input is less than smallest int48 or +greater than largest int48). + +Counterpart to Solidity's `int48` operator. + +Requirements: + +- input must fit into 48 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt40(int256 value) → int40 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int40 from int256, reverting on +overflow (when the input is less than smallest int40 or +greater than largest int40). + +Counterpart to Solidity's `int40` operator. + +Requirements: + +- input must fit into 40 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt32(int256 value) → int32 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int32 from int256, reverting on +overflow (when the input is less than smallest int32 or +greater than largest int32). + +Counterpart to Solidity's `int32` operator. + +Requirements: + +- input must fit into 32 bits + +_Available since v3.1._ + +
+
+ + + +
+
+

toInt24(int256 value) → int24 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int24 from int256, reverting on +overflow (when the input is less than smallest int24 or +greater than largest int24). + +Counterpart to Solidity's `int24` operator. + +Requirements: + +- input must fit into 24 bits + +_Available since v4.7._ + +
+
+ + + +
+
+

toInt16(int256 value) → int16 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int16 from int256, reverting on +overflow (when the input is less than smallest int16 or +greater than largest int16). + +Counterpart to Solidity's `int16` operator. + +Requirements: + +- input must fit into 16 bits + +_Available since v3.1._ + +
+
+ + + +
+
+

toInt8(int256 value) → int8 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int8 from int256, reverting on +overflow (when the input is less than smallest int8 or +greater than largest int8). + +Counterpart to Solidity's `int8` operator. + +Requirements: + +- input must fit into 8 bits + +_Available since v3.1._ + +
+
+ + + +
+
+

toInt256(uint256 value) → int256

+
+

internal

+# +
+
+
+ +Converts an unsigned uint256 into a signed int256. + +Requirements: + +- input must be less than or equal to maxInt256. + +_Available since v3.0._ + +
+
+ + + +
+ +## `SafeMath` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/SafeMath.sol"; +``` + +Wrappers over Solidity's arithmetic operations. + +NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler +now has built in overflow checking. + +
+

Functions

+
+- [tryAdd(a, b)](#SafeMath-tryAdd-uint256-uint256-) +- [trySub(a, b)](#SafeMath-trySub-uint256-uint256-) +- [tryMul(a, b)](#SafeMath-tryMul-uint256-uint256-) +- [tryDiv(a, b)](#SafeMath-tryDiv-uint256-uint256-) +- [tryMod(a, b)](#SafeMath-tryMod-uint256-uint256-) +- [add(a, b)](#SafeMath-add-uint256-uint256-) +- [sub(a, b)](#SafeMath-sub-uint256-uint256-) +- [mul(a, b)](#SafeMath-mul-uint256-uint256-) +- [div(a, b)](#SafeMath-div-uint256-uint256-) +- [mod(a, b)](#SafeMath-mod-uint256-uint256-) +- [sub(a, b, errorMessage)](#SafeMath-sub-uint256-uint256-string-) +- [div(a, b, errorMessage)](#SafeMath-div-uint256-uint256-string-) +- [mod(a, b, errorMessage)](#SafeMath-mod-uint256-uint256-string-) +
+
+ + + +
+
+

tryAdd(uint256 a, uint256 b) → bool, uint256

+
+

internal

+# +
+
+
+ +Returns the addition of two unsigned integers, with an overflow flag. + +_Available since v3.4._ + +
+
+ + + +
+
+

trySub(uint256 a, uint256 b) → bool, uint256

+
+

internal

+# +
+
+
+ +Returns the subtraction of two unsigned integers, with an overflow flag. + +_Available since v3.4._ + +
+
+ + + +
+
+

tryMul(uint256 a, uint256 b) → bool, uint256

+
+

internal

+# +
+
+
+ +Returns the multiplication of two unsigned integers, with an overflow flag. + +_Available since v3.4._ + +
+
+ + + +
+
+

tryDiv(uint256 a, uint256 b) → bool, uint256

+
+

internal

+# +
+
+
+ +Returns the division of two unsigned integers, with a division by zero flag. + +_Available since v3.4._ + +
+
+ + + +
+
+

tryMod(uint256 a, uint256 b) → bool, uint256

+
+

internal

+# +
+
+
+ +Returns the remainder of dividing two unsigned integers, with a division by zero flag. + +_Available since v3.4._ + +
+
+ + + +
+
+

add(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the addition of two unsigned integers, reverting on +overflow. + +Counterpart to Solidity's `+` operator. + +Requirements: + +- Addition cannot overflow. + +
+
+ + + +
+
+

sub(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the subtraction of two unsigned integers, reverting on +overflow (when the result is negative). + +Counterpart to Solidity's `-` operator. + +Requirements: + +- Subtraction cannot overflow. + +
+
+ + + +
+
+

mul(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the multiplication of two unsigned integers, reverting on +overflow. + +Counterpart to Solidity's `*` operator. + +Requirements: + +- Multiplication cannot overflow. + +
+
+ + + +
+
+

div(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the integer division of two unsigned integers, reverting on +division by zero. The result is rounded towards zero. + +Counterpart to Solidity's `/` operator. + +Requirements: + +- The divisor cannot be zero. + +
+
+ + + +
+
+

mod(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), +reverting when dividing by zero. + +Counterpart to Solidity's `%` operator. This function uses a `revert` +opcode (which leaves remaining gas untouched) while Solidity uses an +invalid opcode to revert (consuming all remaining gas). + +Requirements: + +- The divisor cannot be zero. + +
+
+ + + +
+
+

sub(uint256 a, uint256 b, string errorMessage) → uint256

+
+

internal

+# +
+
+
+ +Returns the subtraction of two unsigned integers, reverting with custom message on +overflow (when the result is negative). + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`SafeMath.trySub`](#SafeMath-trySub-uint256-uint256-). + +Counterpart to Solidity's `-` operator. + +Requirements: + +- Subtraction cannot overflow. + +
+
+ + + +
+
+

div(uint256 a, uint256 b, string errorMessage) → uint256

+
+

internal

+# +
+
+
+ +Returns the integer division of two unsigned integers, reverting with custom message on +division by zero. The result is rounded towards zero. + +Counterpart to Solidity's `/` operator. Note: this function uses a +`revert` opcode (which leaves remaining gas untouched) while Solidity +uses an invalid opcode to revert (consuming all remaining gas). + +Requirements: + +- The divisor cannot be zero. + +
+
+ + + +
+
+

mod(uint256 a, uint256 b, string errorMessage) → uint256

+
+

internal

+# +
+
+
+ +Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), +reverting with custom message when dividing by zero. + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`SafeMath.tryMod`](#SafeMath-tryMod-uint256-uint256-). + +Counterpart to Solidity's `%` operator. This function uses a `revert` +opcode (which leaves remaining gas untouched) while Solidity uses an +invalid opcode to revert (consuming all remaining gas). + +Requirements: + +- The divisor cannot be zero. + +
+
+ + + +
+ +## `SignedMath` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/SignedMath.sol"; +``` + +Standard signed math utilities missing in the Solidity language. + +
+

Functions

+
+- [max(a, b)](#SignedMath-max-int256-int256-) +- [min(a, b)](#SignedMath-min-int256-int256-) +- [average(a, b)](#SignedMath-average-int256-int256-) +- [abs(n)](#SignedMath-abs-int256-) +
+
+ + + +
+
+

max(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the largest of two signed numbers. + +
+
+ + + +
+
+

min(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the smallest of two signed numbers. + +
+
+ + + +
+
+

average(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the average of two signed numbers without overflow. +The result is rounded towards zero. + +
+
+ + + +
+
+

abs(int256 n) → uint256

+
+

internal

+# +
+
+
+ +Returns the absolute unsigned value of a signed value. + +
+
+ + + +
+ +## `SignedSafeMath` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/SignedSafeMath.sol"; +``` + +Wrappers over Solidity's arithmetic operations. + +NOTE: `SignedSafeMath` is no longer needed starting with Solidity 0.8. The compiler +now has built in overflow checking. + +
+

Functions

+
+- [mul(a, b)](#SignedSafeMath-mul-int256-int256-) +- [div(a, b)](#SignedSafeMath-div-int256-int256-) +- [sub(a, b)](#SignedSafeMath-sub-int256-int256-) +- [add(a, b)](#SignedSafeMath-add-int256-int256-) +
+
+ + + +
+
+

mul(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the multiplication of two signed integers, reverting on +overflow. + +Counterpart to Solidity's `*` operator. + +Requirements: + +- Multiplication cannot overflow. + +
+
+ + + +
+
+

div(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the integer division of two signed integers. Reverts on +division by zero. The result is rounded towards zero. + +Counterpart to Solidity's `/` operator. + +Requirements: + +- The divisor cannot be zero. + +
+
+ + + +
+
+

sub(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the subtraction of two signed integers, reverting on +overflow. + +Counterpart to Solidity's `-` operator. + +Requirements: + +- Subtraction cannot overflow. + +
+
+ + + +
+
+

add(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the addition of two signed integers, reverting on +overflow. + +Counterpart to Solidity's `+` operator. + +Requirements: + +- Addition cannot overflow. + +
+
+ + + +
+ +## `BitMaps` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/BitMaps.sol"; +``` + +Library for managing uint256 to bool mapping in a compact and efficient way, providing the keys are sequential. +Largely inspired by Uniswap's [merkle-distributor](https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol). + +
+

Functions

+
+- [get(bitmap, index)](#BitMaps-get-struct-BitMaps-BitMap-uint256-) +- [setTo(bitmap, index, value)](#BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool-) +- [set(bitmap, index)](#BitMaps-set-struct-BitMaps-BitMap-uint256-) +- [unset(bitmap, index)](#BitMaps-unset-struct-BitMaps-BitMap-uint256-) +
+
+ + + +
+
+

get(struct BitMaps.BitMap bitmap, uint256 index) → bool

+
+

internal

+# +
+
+
+ +Returns whether the bit at `index` is set. + +
+
+ + + +
+
+

setTo(struct BitMaps.BitMap bitmap, uint256 index, bool value)

+
+

internal

+# +
+
+
+ +Sets the bit at `index` to the boolean `value`. + +
+
+ + + +
+
+

set(struct BitMaps.BitMap bitmap, uint256 index)

+
+

internal

+# +
+
+
+ +Sets the bit at `index`. + +
+
+ + + +
+
+

unset(struct BitMaps.BitMap bitmap, uint256 index)

+
+

internal

+# +
+
+
+ +Unsets the bit at `index`. + +
+
+ + + +
+ +## `DoubleEndedQueue` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +``` + +A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of +the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and +FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes [`DoubleEndedQueue.clear`](#DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-), given that +the existing queue contents are left in storage. + +The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be +used in storage, and not in memory. +```solidity +DoubleEndedQueue.Bytes32Deque queue; +``` + +_Available since v4.6._ + +
+

Functions

+
+- [pushBack(deque, value)](#DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32-) +- [popBack(deque)](#DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque-) +- [pushFront(deque, value)](#DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32-) +- [popFront(deque)](#DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque-) +- [front(deque)](#DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-) +- [back(deque)](#DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque-) +- [at(deque, index)](#DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-) +- [clear(deque)](#DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-) +- [length(deque)](#DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque-) +- [empty(deque)](#DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque-) +
+
+ +
+

Errors

+
+- [Empty()](#DoubleEndedQueue-Empty--) +- [OutOfBounds()](#DoubleEndedQueue-OutOfBounds--) +
+
+ + + +
+
+

pushBack(struct DoubleEndedQueue.Bytes32Deque deque, bytes32 value)

+
+

internal

+# +
+
+
+ +Inserts an item at the end of the queue. + +
+
+ + + +
+
+

popBack(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Removes the item at the end of the queue and returns it. + +Reverts with `Empty` if the queue is empty. + +
+
+ + + +
+
+

pushFront(struct DoubleEndedQueue.Bytes32Deque deque, bytes32 value)

+
+

internal

+# +
+
+
+ +Inserts an item at the beginning of the queue. + +
+
+ + + +
+
+

popFront(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Removes the item at the beginning of the queue and returns it. + +Reverts with `Empty` if the queue is empty. + +
+
+ + + +
+
+

front(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Returns the item at the beginning of the queue. + +Reverts with `Empty` if the queue is empty. + +
+
+ + + +
+
+

back(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Returns the item at the end of the queue. + +Reverts with `Empty` if the queue is empty. + +
+
+ + + +
+
+

at(struct DoubleEndedQueue.Bytes32Deque deque, uint256 index) → bytes32 value

+
+

internal

+# +
+
+
+ +Return the item at a position in the queue given by `index`, with the first item at 0 and last item at +`length(deque) - 1`. + +Reverts with `OutOfBounds` if the index is out of bounds. + +
+
+ + + +
+
+

clear(struct DoubleEndedQueue.Bytes32Deque deque)

+
+

internal

+# +
+
+
+ +Resets the queue back to being empty. + +NOTE: The current items are left behind in storage. This does not affect the functioning of the queue, but misses +out on potential gas refunds. + +
+
+ + + +
+
+

length(struct DoubleEndedQueue.Bytes32Deque deque) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of items in the queue. + +
+
+ + + +
+
+

empty(struct DoubleEndedQueue.Bytes32Deque deque) → bool

+
+

internal

+# +
+
+
+ +Returns true if the queue is empty. + +
+
+ + + +
+
+

Empty()

+
+

error

+# +
+
+
+ +An operation (e.g. [`DoubleEndedQueue.front`](#DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-)) couldn't be completed due to the queue being empty. + +
+
+ + + +
+
+

OutOfBounds()

+
+

error

+# +
+
+
+ +An operation (e.g. [`DoubleEndedQueue.at`](#DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-)) couldn't be completed due to an index being out of bounds. + +
+
+ + + +
+ +## `EnumerableMap` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +``` + +Library for managing an enumerable variant of Solidity's +[`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types) +type. + +Maps have the following properties: + +- Entries are added, removed, and checked for existence in constant time +(O(1)). +- Entries are enumerated in O(n). No guarantees are made on the ordering. + +```solidity +contract Example { + // Add the library methods + using EnumerableMap for EnumerableMap.UintToAddressMap; + + // Declare a set state variable + EnumerableMap.UintToAddressMap private myMap; +} +``` + +The following map types are supported: + +- `uint256 -> address` (`UintToAddressMap`) since v3.0.0 +- `address -> uint256` (`AddressToUintMap`) since v4.6.0 +- `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 +- `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 +- `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 + +[WARNING] +==== +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See [ethereum/solidity#11843](https://github.com/ethereum/solidity/pull/11843) for more info. + +In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableMap. +==== + +
+

Functions

+
+- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [get(map, key, errorMessage)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-string-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-UintToUintMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-) +- [get(map, key, errorMessage)](#EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-string-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-) +- [get(map, key, errorMessage)](#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-string-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-AddressToUintMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-) +- [get(map, key, errorMessage)](#EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-string-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [get(map, key, errorMessage)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-string-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-) +
+
+ + + +
+
+

set(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Removes a key-value pair from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

contains(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.Bytes32ToBytes32Map map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of key-value pairs in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.Bytes32ToBytes32Map map, uint256 index) → bytes32, bytes32

+
+

internal

+# +
+
+
+ +Returns the key-value pair stored at position `index` in the map. O(1). + +Note that there are no guarantees on the ordering of entries inside the +array, and it may change when more entries are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bool, bytes32

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bytes32

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key, string errorMessage) → bytes32

+
+

internal

+# +
+
+
+ +Same as [`BitMaps.get`](#BitMaps-get-struct-BitMaps-BitMap-uint256-), with a custom error message when `key` is not in the map. + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`EnumerableMap.tryGet`](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-). + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToBytes32Map map) → bytes32[]

+
+

internal

+# +
+
+
+ +Return the an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.UintToUintMap map, uint256 key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.UintToUintMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

contains(struct EnumerableMap.UintToUintMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.UintToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.UintToUintMap map, uint256 index) → uint256, uint256

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.UintToUintMap map, uint256 key) → bool, uint256

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToUintMap map, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToUintMap map, uint256 key, string errorMessage) → uint256

+
+

internal

+# +
+
+
+ +Same as [`BitMaps.get`](#BitMaps-get-struct-BitMaps-BitMap-uint256-), with a custom error message when `key` is not in the map. + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`EnumerableMap.tryGet`](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-). + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToUintMap map) → uint256[]

+
+

internal

+# +
+
+
+ +Return the an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.UintToAddressMap map, uint256 key, address value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

contains(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.UintToAddressMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.UintToAddressMap map, uint256 index) → uint256, address

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool, address

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToAddressMap map, uint256 key) → address

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToAddressMap map, uint256 key, string errorMessage) → address

+
+

internal

+# +
+
+
+ +Same as [`BitMaps.get`](#BitMaps-get-struct-BitMaps-BitMap-uint256-), with a custom error message when `key` is not in the map. + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`EnumerableMap.tryGet`](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-). + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToAddressMap map) → uint256[]

+
+

internal

+# +
+
+
+ +Return the an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.AddressToUintMap map, address key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.AddressToUintMap map, address key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

contains(struct EnumerableMap.AddressToUintMap map, address key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.AddressToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.AddressToUintMap map, uint256 index) → address, uint256

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.AddressToUintMap map, address key) → bool, uint256

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.AddressToUintMap map, address key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.AddressToUintMap map, address key, string errorMessage) → uint256

+
+

internal

+# +
+
+
+ +Same as [`BitMaps.get`](#BitMaps-get-struct-BitMaps-BitMap-uint256-), with a custom error message when `key` is not in the map. + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`EnumerableMap.tryGet`](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-). + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToUintMap map) → address[]

+
+

internal

+# +
+
+
+ +Return the an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

contains(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.Bytes32ToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.Bytes32ToUintMap map, uint256 index) → bytes32, uint256

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → bool, uint256

+
+

internal

+# +
+
+
+ +Tries to returns the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key, string errorMessage) → uint256

+
+

internal

+# +
+
+
+ +Same as [`BitMaps.get`](#BitMaps-get-struct-BitMaps-BitMap-uint256-), with a custom error message when `key` is not in the map. + +CAUTION: This function is deprecated because it requires allocating memory for the error +message unnecessarily. For custom revert reasons use [`EnumerableMap.tryGet`](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-). + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToUintMap map) → bytes32[]

+
+

internal

+# +
+
+
+ +Return the an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+ +## `EnumerableSet` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +``` + +Library for managing +[sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) of primitive +types. + +Sets have the following properties: + +- Elements are added, removed, and checked for existence in constant time +(O(1)). +- Elements are enumerated in O(n). No guarantees are made on the ordering. + +```solidity +contract Example { + // Add the library methods + using EnumerableSet for EnumerableSet.AddressSet; + + // Declare a set state variable + EnumerableSet.AddressSet private mySet; +} +``` + +As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) +and `uint256` (`UintSet`) are supported. + +[WARNING] +==== +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See [ethereum/solidity#11843](https://github.com/ethereum/solidity/pull/11843) for more info. + +In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableSet. +==== + +
+

Functions

+
+- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-Bytes32Set-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-Bytes32Set-) +- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-AddressSet-address-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-AddressSet-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-AddressSet-) +- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-UintSet-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-UintSet-) +
+
+ + + +
+
+

add(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

contains(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.Bytes32Set set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values in the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.Bytes32Set set, uint256 index) → bytes32

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

values(struct EnumerableSet.Bytes32Set set) → bytes32[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

add(struct EnumerableSet.AddressSet set, address value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.AddressSet set, address value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

contains(struct EnumerableSet.AddressSet set, address value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.AddressSet set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values in the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.AddressSet set, uint256 index) → address

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

values(struct EnumerableSet.AddressSet set) → address[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

add(struct EnumerableSet.UintSet set, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.UintSet set, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

contains(struct EnumerableSet.UintSet set, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.UintSet set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values in the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.UintSet set, uint256 index) → uint256

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Checkpoints.length`](#Checkpoints-length-struct-Checkpoints-Trace160-). + +
+
+ + + +
+
+

values(struct EnumerableSet.UintSet set) → uint256[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
diff --git a/docs/content/contracts/4.x/crosschain.mdx b/docs/content/contracts/4.x/crosschain.mdx new file mode 100644 index 00000000..7ef224f0 --- /dev/null +++ b/docs/content/contracts/4.x/crosschain.mdx @@ -0,0 +1,205 @@ +--- +title: Adding cross-chain support to contracts +--- + +If your contract is targeting to be used in the context of multichain operations, you may need specific tools to identify and process these cross-chain operations. + +OpenZeppelin provides the [`CrossChainEnabled`](/contracts/4.x/api/crosschain#CrossChainEnabled) abstract contract, that includes dedicated internal functions. + +In this guide, we will go through an example use case: _how to build an upgradeable & mintable ERC20 token controlled by a governor present on a foreign chain_. + +## Starting point, our ERC20 contract + +Let’s start with a small ERC20 contract, that we bootstrapped using the [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/), and extended with an owner that has the ability to mint. Note that for demonstration purposes we have not used the built-in `Ownable` contract. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract MyToken is Initializable, ERC20Upgradeable, UUPSUpgradeable + address public owner; + + modifier onlyOwner() { + require(owner == _msgSender(), "Not authorized"); + _; + + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + function initialize(address initialOwner) initializer public + __ERC20_init("MyToken", "MTK"); + __UUPSUpgradeable_init(); + + owner = initialOwner; + + + function mint(address to, uint256 amount) public onlyOwner + _mint(to, amount); + + + function _authorizeUpgrade(address newImplementation) internal override onlyOwner + +} +``` + +This token is mintable and upgradeable by the owner of the contract. + +## Preparing our contract for cross-chain operations. + +Let’s now imagine that this contract is going to live on one chain, but we want the minting and the upgrading to be performed by a [`governor`](/contracts/4.x/governance) contract on another chain. + +For example, we could have our token on xDai, with our governor on mainnet, or we could have our token on mainnet, with our governor on optimism. + +In order to do that, we will start by adding [`CrossChainEnabled`](/contracts/4.x/api/crosschain#CrossChainEnabled) to our contract. You will notice that the contract is now abstract. This is because `CrossChainEnabled` is an abstract contract: it is not tied to any particular chain and it deals with cross-chain interactions in an abstract way. This is what enables us to easily reuse the code for different chains. We will specialize it later by inheriting from a chain-specific implementation of the abstraction. + +```diff + import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; ++import "@openzeppelin/contracts-upgradeable/crosschain/CrossChainEnabled.sol"; + +-contract MyToken is Initializable, ERC20Upgradeable, UUPSUpgradeable ++abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, UUPSUpgradeable, CrossChainEnabled { +``` + +Once that is done, we can use the `onlyCrossChainSender` modifier, provided by `CrossChainEnabled` in order to protect the minting and upgrading operations. + +```diff +- function mint(address to, uint256 amount) public onlyOwner { ++ function mint(address to, uint256 amount) public onlyCrossChainSender(owner) { + +* function _authorizeUpgrade(address newImplementation) internal override onlyOwner { ++ function _authorizeUpgrade(address newImplementation) internal override onlyCrossChainSender(owner) { +``` + +This change will effectively restrict the mint and upgrade operations to the `owner` on the remote chain. + +## Specializing for a specific chain + +Once the abstract cross-chain version of our token is ready we can easily specialize it for the chain we want, or more precisely for the bridge system that we want to rely on. + +This is done using one of the many `CrossChainEnabled` implementations. + +For example, if our token is on xDai, and our governor on mainnet, we can use the [AMB](https://docs.tokenbridge.net/amb-bridge/about-amb-bridge) bridge available on xDai at [0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59](https://blockscout.com/xdai/mainnet/address/0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59) + +```solidity +[...] + +import "@openzeppelin/contracts-upgradeable/crosschain/amb/CrossChainEnabledAMB.sol"; + +contract MyTokenXDAI is + MyTokenCrossChain, + CrossChainEnabledAMB(0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59) +{ +``` + +If the token is on Ethereum mainnet, and our governor on Optimism, we use the Optimism [CrossDomainMessenger](https://community.optimism.io/docs/protocol/protocol-2.0/#l1crossdomainmessenger) available on mainnet at [0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1](https://etherscan.io/address/0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1). + +```solidity +[...] + +import "@openzeppelin/contracts-upgradeable/crosschain/optimismCrossChainEnabledOptimism.sol"; + +contract MyTokenOptimism is + MyTokenCrossChain, + CrossChainEnabledOptimism(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1) +{} +``` + +## Mixing cross domain addresses is dangerous + +When designing a contract with cross-chain support, it is essential to understand possible fallbacks and the security assumption that are being made. + +In this guide, we are particularly focusing on restricting access to a specific caller. This is usually done (as shown above) using `msg.sender` or `_msgSender()`. However, when going cross-chain, it is not just that simple. Even without considering possible bridge issues, it is important to keep in mind that the same address can correspond to very different entities when considering a multi-chain space. EOA wallets can only execute operations if the wallet’s private-key signs the transaction. To our knowledge this is the case in all EVM chains, so a cross-chain message coming from such a wallet is arguably equivalent to a non-cross-chain message by the same wallet. The situation is however very different for smart contracts. + +Due to the way smart contract addresses are computed, and the fact that smart contracts on different chains live independent lives, you could have two very different contracts live at the same address on different chains. You could imagine two multisig wallets with different signers using the same address on different chains. You could also see a very basic smart wallet live on one chain at the same address as a full-fledged governor on another chain. Therefore, you should be careful that whenever you give permissions to a specific address, you control with chain this address can act from. + +## Going further with access control + +In the previous example, we have both an `onlyOwner()` modifier and the `onlyCrossChainSender(owner)` mechanism. We didn’t use the [`Ownable`](/contracts/4.x/access-control#ownership-and-ownable) pattern because the ownership transfer mechanism in includes is not designed to work with the owner being a cross-chain entity. Unlike [`Ownable`](/contracts/4.x/access-control#ownership-and-ownable), [`AccessControl`](/contracts/4.x/access-control#role-based-access-control) is more effective at capturing the nuances and can effectively be used to build cross-chain-aware contracts. + +Using [`AccessControlCrossChain`](/contracts/4.x/api/access#AccessControlCrossChain) includes both the [`AccessControl`](/contracts/4.x/api/access#AccessControl) core and the [`CrossChainEnabled`](/contracts/4.x/api/crosschain#CrossChainEnabled) abstraction. It also includes some binding to make role management compatible with cross-chain operations. + +In the case of the `mint` function, the caller must have the `MINTER_ROLE` when the call originates from the same chain. If the caller is on a remote chain, then the caller should not have the `MINTER_ROLE`, but the "aliased" version (`MINTER_ROLE ^ CROSSCHAIN_ALIAS`). This mitigates the danger described in the previous section by strictly separating local accounts from remote accounts from a different chain. See the [`AccessControlCrossChain`](/contracts/4.x/api/access#AccessControlCrossChain) documentation for more details. + +```diff + import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; + import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; ++import "@openzeppelin/contracts-upgradeable/access/AccessControlCrossChainUpgradeable.sol"; + +-abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, UUPSUpgradeable, CrossChainEnabled ++abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, UUPSUpgradeable, AccessControlCrossChainUpgradeable { + +* address public owner; +* modifier onlyOwner() { +* require(owner == _msgSender(), "Not authorized"); +* _; +* } ++ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); ++ bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + + function initialize(address initialOwner) initializer public + __ERC20_init("MyToken", "MTK"); + __UUPSUpgradeable_init(); ++ __AccessControl_init(); ++ _grantRole(_crossChainRoleAlias(DEFAULT_ADMIN_ROLE), initialOwner); // initialOwner is on a remote chain +- owner = initialOwner; + + +* function mint(address to, uint256 amount) public onlyCrossChainSender(owner) ++ function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { +* function _authorizeUpgrade(address newImplementation) internal override onlyCrossChainSender(owner) { ++ function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) { +``` + +This results in the following, final, code: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/access/AccessControlCrossChainUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +abstract contract MyTokenCrossChain is Initializable, ERC20Upgradeable, AccessControlCrossChainUpgradeable, UUPSUpgradeable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer { + + function initialize(address initialOwner) initializer public + __ERC20_init("MyToken", "MTK"); + __AccessControl_init(); + __UUPSUpgradeable_init(); + + _grantRole(_crossChainRoleAlias(DEFAULT_ADMIN_ROLE), initialOwner); // initialOwner is on a remote chain + + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) + _mint(to, amount); + + + function _authorizeUpgrade(address newImplementation) internal onlyRole(UPGRADER_ROLE) override + +} + +import "@openzeppelin/contracts-upgradeable/crosschain/amb/CrossChainEnabledAMB.sol"; + +contract MyTokenXDAI is + MyTokenCrossChain, + CrossChainEnabledAMB(0x75Df5AF045d91108662D8080fD1FEFAd6aA0bb59) +{} + +import "@openzeppelin/contracts-upgradeable/crosschain/optimismCrossChainEnabledOptimism.sol"; + +contract MyTokenOptimism is + MyTokenCrossChain, + CrossChainEnabledOptimism(0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1) +{} +``` diff --git a/docs/content/contracts/4.x/crowdsales.mdx b/docs/content/contracts/4.x/crowdsales.mdx new file mode 100644 index 00000000..9240a0b2 --- /dev/null +++ b/docs/content/contracts/4.x/crowdsales.mdx @@ -0,0 +1,13 @@ +--- +title: Crowdsales +--- + +All crowdsale-related contracts were removed from the OpenZeppelin Contracts library on the [v3.0.0 release](https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256) due to both a decline in their usage and the complexity associated with migrating them to Solidity v0.6. + +They are however still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the [v2.x documentation](https://docs.openzeppelin.com/contracts/2.x/crowdsales) when working with them. diff --git a/docs/content/contracts/4.x/drafts.mdx b/docs/content/contracts/4.x/drafts.mdx new file mode 100644 index 00000000..6c2d0e85 --- /dev/null +++ b/docs/content/contracts/4.x/drafts.mdx @@ -0,0 +1,21 @@ +--- +title: Drafts +--- + +All draft contracts were either moved into a different directory or removed from the OpenZeppelin Contracts library on the [v3.0.0 release](https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0-beta-release/2256). + +* `ERC20Migrator`: removed. +* [`ERC20Snapshot`](/contracts/4.x/api/token/ERC20#ERC20Snapshot): moved to `token/ERC20`. +* `ERC20Detailed` and `ERC1046`: removed. +* `TokenVesting`: removed. Pending a replacement that is being discussed in [`#1214`](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1214). +* [`Counters`](/contracts/4.x/api/utils#Counters): moved to [`utils`](/contracts/4.x/api/utils). +* [`Strings`](/contracts/4.x/api/utils#Strings): moved to [`utils`](/contracts/4.x/api/utils). +* [`SignedSafeMath`](/contracts/4.x/api/utils#SignedSafeMath): moved to [`utils`](/contracts/4.x/api/utils). + +Removed contracts are still available on the v2.5 release of OpenZeppelin Contracts, which you can install by running: + +```console +$ npm install @openzeppelin/contracts@v2.5 +``` + +Refer to the [v2.x documentation] when working with them. diff --git a/docs/content/contracts/4.x/erc1155.mdx b/docs/content/contracts/4.x/erc1155.mdx new file mode 100644 index 00000000..77a3603b --- /dev/null +++ b/docs/content/contracts/4.x/erc1155.mdx @@ -0,0 +1,153 @@ +--- +title: ERC1155 +--- + +ERC1155 is a novel token standard that aims to take the best from previous standards to create a [**fungibility-agnostic**](/contracts/4.x/tokens#different-kinds-of-tokens) and **gas-efficient** [token contract](/contracts/4.x/tokens#but-first-coffee-a-primer-on-token-contracts). + + +ERC1155 draws ideas from all of [ERC20](/contracts/4.x/erc20), [ERC721](/contracts/4.x/erc721), and [ERC777](/contracts/4.x/erc777). If you’re unfamiliar with those standards, head to their guides before moving on. + + +## Multi Token Standard + +The distinctive feature of ERC1155 is that it uses a single smart contract to represent multiple tokens at once. This is why its [`balanceOf`](/contracts/4.x/api/token/ERC1155#IERC1155-balanceOf-address-uint256-) function differs from ERC20’s and ERC777’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 ERC721 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 ERC721 [`balanceOf`](/contracts/4.x/api/token/ERC721#IERC721-balanceOf-address-) function refers to _how many different tokens_ an account has, not how many of each. On the other hand, in ERC1155 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 ERC1155 token contract can hold the entire system state, reducing deployment costs and complexity. + +## 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, [`balanceOfBatch`](/contracts/4.x/api/token/ERC1155#IERC1155-balanceOfBatch-address---uint256---) and [`safeBatchTransferFrom`](/contracts/4.x/api/token/ERC1155#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-), 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 [`_mintBatch`](/contracts/4.x/api/token/ERC1155#ERC1155-_mintBatch-address-uint256---uint256---bytes-). + +## Constructing an ERC1155 Token Contract + +We’ll use ERC1155 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. + + +For an overview of minting mechanisms, check out [Creating ERC20 Supply](/contracts/4.x/erc20-supply). + + +Here’s what a contract for tokenized items might look like: + +```solidity +// contracts/GameItems.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +contract GameItems is ERC1155 + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + constructor() ERC1155("https://game.example/api/item/{id.json") + _mint(msg.sender, GOLD, 10**18, ""); + _mint(msg.sender, SILVER, 10**27, ""); + _mint(msg.sender, THORS_HAMMER, 1, ""); + _mint(msg.sender, SWORD, 10**9, ""); + _mint(msg.sender, SHIELD, 10**9, ""); + +} +``` + +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 [`ERC1155`](/contracts/4.x/api/token/ERC1155#ERC1155) contract includes the optional extension [`IERC1155MetadataURI`](/contracts/4.x/api/token/ERC1155#IERC1155MetadataURI). That’s where the [`uri`](/contracts/4.x/api/token/ERC1155#IERC1155MetadataURI-uri-uint256-) function comes from: we use it to retrieve the metadata uri. + +Also note that, unlike ERC20, ERC1155 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: +```javascript +> gameItems.balanceOf(deployerAddress,3) +1000000000 +``` + +We can transfer items to player accounts: +```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: +```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: + +```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: + +```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 [ERC-1155 Metadata URI JSON Schema](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema). + + +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! + + + +If you’d like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly) by providing a [`Base64`](/contracts/4.x/utilities#base64) Data URI with the JSON schema encoded. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide + + +## Sending Tokens to Contracts + +A key difference when using [`safeTransferFrom`](/contracts/4.x/api/token/ERC1155#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) is that token transfers to other contracts may revert with the following message: + +```text +ERC1155: transfer to non ERC1155Receiver implementer +``` + +This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC1155 protocol, so transfers to it are disabled to **prevent tokens from being locked forever**. As an example, [the Golem contract currently holds over 350k `GNT` tokens](https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d), worth multiple tens of thousands of dollars, 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 ERC1155 tokens we can inherit from the convenience contract [`ERC1155Holder`](/contracts/4.x/api/token/ERC1155#ERC1155Holder) which handles the registering for us. Though we need to remember to implement functionality to allow tokens to be transferred out of our contract: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; + +contract MyContract is ERC1155Holder + +``` + +We can also implement more complex scenarios using the [`onERC1155Received`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and [`onERC1155BatchReceived`](/contracts/4.x/api/token/ERC1155#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) functions. + +## Preset ERC1155 contract +A preset ERC1155 is available, [`ERC1155PresetMinterPauser`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC1155/presets/ERC1155PresetMinterPauser.sol). It is preset to allow for token minting (create) - including batch minting, stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses [Access Control](/contracts/4.x/access-control) to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. + +This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. + + +Contract presets are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com) as a more powerful alternative. + diff --git a/docs/content/contracts/4.x/erc20-supply.mdx b/docs/content/contracts/4.x/erc20-supply.mdx new file mode 100644 index 00000000..7ba4b071 --- /dev/null +++ b/docs/content/contracts/4.x/erc20-supply.mdx @@ -0,0 +1,107 @@ +--- +title: Creating ERC20 Supply +--- + +In this guide, you will learn how to create an ERC20 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 ERC20, and Contracts includes a widely used implementation of it: the aptly named [`ERC20`](/contracts/4.x/api/token/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 ERC20 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 + +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: + +```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 [`_mint`](/contracts/4.x/api/token/ERC20#ERC20-_mint-address-uint256-) function that will do exactly this: + +```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 Miners + +The internal [`_mint`](/contracts/4.x/api/token/ERC20#ERC20-_mint-address-uint256-) function is the key building block that allows us to write ERC20 extensions that implement a supply mechanism. + +The mechanism we will implement is a token reward for the miners that produce Ethereum blocks. In Solidity, we can access the address of the current block’s miner in the global variable `block.coinbase`. 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! + +```solidity +contract ERC20WithMinerReward is ERC20 + constructor() ERC20("Reward", "RWD") { + + function mintMinerReward() public + _mint(block.coinbase, 1000); + +} +``` + +As we can see, `_mint` makes it super easy to do this correctly. + +## Modularizing the Mechanism + +There is one supply mechanism already included in Contracts: `ERC20PresetMinterPauser`. This is a generic mechanism in which a set of accounts is assigned the `minter` role, granting them the permission to call a `mint` function, an external version of `_mint`. + +This can be used for centralized minting, where an externally owned account (i.e. someone with a pair of cryptographic keys) decides how much supply to create and for whom. There are very legitimate use cases for this mechanism, such as [traditional asset-backed stablecoins](https://medium.com/reserve-currency/why-another-stablecoin-866f774afede#3aea). + +The accounts with the minter role don’t need to be externally owned, though, and can just as well be smart contracts that implement a trustless mechanism. We can in fact implement the same behavior as the previous section. + +```solidity +contract MinerRewardMinter + ERC20PresetMinterPauser _token; + + constructor(ERC20PresetMinterPauser token) { + _token = token; + + + function mintMinerReward() public + _token.mint(block.coinbase, 1000); + +} +``` + +This contract, when initialized with an `ERC20PresetMinterPauser` instance, and granted the `minter` role for that contract, will result in exactly the same behavior implemented in the previous section. What is interesting about using `ERC20PresetMinterPauser` is that we can easily combine multiple supply mechanisms by assigning the role to multiple contracts, and moreover that we can do this dynamically. + + +To learn more about roles and permissioned systems, head to our [Access Control guide](/contracts/4.x/access-control). + + +## Automating the Reward + +So far our supply mechanisms were triggered manually, but `ERC20` also allows us to extend the core functionality of the token through the [`_beforeTokenTransfer`](/contracts/4.x/api/token/ERC20#ERC20-_beforeTokenTransfer-address-address-uint256-) hook (see [Using Hooks(/contracts/4.x/extending-contracts#using-hooks)). + +Adding to the supply mechanism from previous sections, we can use this hook to mint a miner reward for every token transfer that is included in the blockchain. + +```solidity +contract ERC20WithAutoMinerReward is ERC20 + constructor() ERC20("Reward", "RWD") { + + function _mintMinerReward() internal + _mint(block.coinbase, 1000); + + + function _beforeTokenTransfer(address from, address to, uint256 value) internal virtual override + if (!(from == address(0) && to == block.coinbase)) { + _mintMinerReward(); + + super._beforeTokenTransfer(from, to, value); + } +} +``` + +## Wrapping Up + +We’ve seen two ways to implement ERC20 supply mechanisms: internally through `_mint`, and externally through `ERC20PresetMinterPauser`. 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. diff --git a/docs/content/contracts/4.x/erc20.mdx b/docs/content/contracts/4.x/erc20.mdx new file mode 100644 index 00000000..e7e585a4 --- /dev/null +++ b/docs/content/contracts/4.x/erc20.mdx @@ -0,0 +1,87 @@ +--- +title: ERC20 +--- + +An ERC20 token contract keeps track of [_fungible_ tokens](/contracts/4.x/tokens#different-kinds-of-tokens): any one token is exactly equal to any other token; no tokens have special rights or behavior associated with them. This makes ERC20 tokens useful for things like a **medium of exchange currency**, **voting rights**, **staking**, and more. + +OpenZeppelin Contracts provides many ERC20-related contracts. On the [`API reference`](/contracts/4.x/api/token/ERC20) you’ll find detailed information on their properties and usage. + +## Constructing an ERC20 Token Contract + +Using Contracts, we can easily create our own ERC20 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. + +```solidity +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract GLDToken is ERC20 + constructor(uint256 initialSupply) ERC20("Gold", "GLD") { + _mint(msg.sender, initialSupply); + +} +``` + +Our contracts are often used via [inheritance](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance), and here we’re reusing [`ERC20`](/contracts/4.x/api/token/ERC20#erc20) for both the basic standard implementation and the [`name`](/contracts/4.x/api/token/ERC20#ERC20-name--), [`symbol`](/contracts/4.x/api/token/ERC20#ERC20-symbol--), and [`decimals`](/contracts/4.x/api/token/ERC20#ERC20-decimals--) optional extensions. Additionally, we’re creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. + + +For a more complete discussion of ERC20 supply mechanisms, see [Creating ERC20 Supply](/contracts/4.x/erc20-supply). + + +That’s it! Once deployed, we will be able to query the deployer’s balance: + +```javascript +> GLDToken.balanceOf(deployerAddress) +1000000000000000000000 +``` + +We can also [transfer](/contracts/4.x/api/token/ERC20#IERC20-transfer-address-uint256-) these tokens to other accounts: + +```javascript +> GLDToken.transfer(otherAddress, 300000000000000000000) +> GLDToken.balanceOf(otherAddress) +300000000000000000000 +> GLDToken.balanceOf(deployerAddress) +700000000000000000000 +``` + +## 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, [`ERC20`](/contracts/4.x/api/token/ERC20#ERC20) provides a [`decimals`](/contracts/4.x/api/token/ERC20#ERC20-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 ERC20 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)`. + + +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)); +``` + +## Preset ERC20 contract +A preset ERC20 is available, [`ERC20PresetMinterPauser`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol). It is preset to allow for token minting (create), stop all token transfers (pause) and allow holders to burn (destroy) their tokens. The contract uses [Access Control](/contracts/4.x/access-control) to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. + +This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing, but is also suitable for production environments. + + +Contract presets are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com) as a more powerful alternative. + diff --git a/docs/content/contracts/4.x/erc4626.mdx b/docs/content/contracts/4.x/erc4626.mdx new file mode 100644 index 00000000..8e58395c --- /dev/null +++ b/docs/content/contracts/4.x/erc4626.mdx @@ -0,0 +1,168 @@ +--- +title: ERC4626 +--- + +[ERC4626](https://eips.ethereum.org/EIPS/eip-4626) is an extension of [ERC20](/contracts/4.x/erc20) 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 ERC4626 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 ERC4626. We will also discuss common customizations of the vault. + +## Security concern: Inflation attack + +### Visualizing the vault + +In exchange for the assets deposited into an ERC4626 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. + +![Exchange rates in linear scale](/erc4626-rate-linear.png) + +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. + +![Exchange rates in logarithmic scale](/erc4626-rate-loglog.png) + +In such a reprentation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. + +![More exchange rates in logarithmic scale](/erc4626-rate-loglogext.png) + +### The attack + +When depositing tokens, the number of shares a user gets is rounded down. This rounding takes away value from the user in favor or the vault (i.e. in favor of all the current share holders). 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. + +![Depositing assets](/erc4626-deposit.png) + +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%. + +![Minting shares](/erc4626-mint.png) + +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 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. + +![Inflation attack without protection](/erc4626-attack.png) + +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 share holder (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: + +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | +| --- | --- | --- | --- | +| Assets | Shares | Rate | initial | +| $0$ | $0$ | - | after attacker’s deposit | +| $a_0$ | $a_0$ | $1$ | after attacker’s donation | + +This means a deposit of $u$ will give $\fracu \times a_0a_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 + +```math +\fracu \times a_0a_0+a_1 < 1 \iff u < 1 + \fraca_1a_0 +``` + +Using $a_0 = 1$ and $a_1 = u$ is enough. So the attacker only needs $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 $\fracun$, the user needs to suffer rounding of a similar fraction, which means the user must receive at most $n$ shares. This results in: + +```math +\fracu \times a_0a_0+a_1 < n \iff \fracun < 1 + \fraca_1a_0 +``` + +In this scenario, the attack is $n$ times less powerful (in how much it is stealing) and costs $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 [YieldBox](https://github.com/boringcrypto/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: + +* $\delta$ the vault offset +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | +| --- | --- | --- | --- | +| Assets | Shares | Rate | initial | +| $1$ | $10^\delta$ | $10^\delta$ | after attacker’s deposit | +| $1+a_0$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | after attacker’s donation | + +One important thing to note is that the attacker only owns a fraction $\fraca_01 + a_0$ of the shares, so when doing the donation, he will only be able to recover that fraction $\fraca_1 \times a_01 + a_0$ of the donation. The remaining $\fraca_11+a_0$ are captured by the vault. + +```math +\mathitloss = \fraca_11+a_0 +``` + +When the user deposits $u$, he receives + +```math +10^\delta \times u \times \frac1+a_01+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 + +```math +10^\delta \times u \times \frac1+a_01+a_0+a_1 < 1 +``` + +```math +\iff 10^\delta \times u < \frac1+a_0+a_11+a_0 +``` + +```math +\iff 10^\delta \times u < 1 + \fraca_11+a_0 +``` + +```math +\iff 10^\delta \times u \le \mathitloss +``` + +* If the offset is 0, the attacker 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. + +![Inflation attack without offset=3](/erc4626-attack-3a.png) +$\delta = 3$, $a_0 = 1$, $a_1 = 10^5$ + +![Inflation attack without offset=3 and an attacker deposit that limits its losses](/erc4626-attack-3b.png) +$\delta = 3$, $a_0 = 100$, $a_1 = 10^5$ + +![Inflation attack without offset=6](/erc4626-attack-6.png) +$\delta = 6$, $a_0 = 1$, $a_1 = 10^5$ + +## Custom behavior: Adding fees to the vault + +In an ERC4626 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 ERC4626 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: + +./examples/ERC4626Fees.sol diff --git a/docs/content/contracts/4.x/erc721.mdx b/docs/content/contracts/4.x/erc721.mdx new file mode 100644 index 00000000..ec6507cf --- /dev/null +++ b/docs/content/contracts/4.x/erc721.mdx @@ -0,0 +1,93 @@ +--- +title: ERC721 +--- + +We’ve discussed how you can make a _fungible_ token using [ERC20](/contracts/4.x/erc20), 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. ERC721 is a standard for representing ownership of [_non-fungible_ tokens](/contracts/4.x/tokens#different-kinds-of-tokens), that is, where each token is unique. + +ERC721 is a more complex standard than ERC20, 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 [API Reference](/contracts/4.x/api/token/ERC721) to learn more about these. + +## Constructing an ERC721 Token Contract + +We’ll use ERC721 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 any account can call `awardItem` to mint items. To restrict what accounts can mint items we can add [Access Control](/contracts/4.x/access-control). + +Here’s what a contract for tokenized items might look like: + +```solidity +// contracts/GameItem.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +import "@openzeppelin/contracts/utils/Counters.sol"; + +contract GameItem is ERC721URIStorage + using Counters for Counters.Counter; + Counters.Counter private _tokenIds; + + constructor() ERC721("GameItem", "ITM") { + + function awardItem(address player, string memory tokenURI) + public + returns (uint256) + + uint256 newItemId = _tokenIds.current(); + _mint(player, newItemId); + _setTokenURI(newItemId, tokenURI); + + _tokenIds.increment(); + return newItemId; + +} +``` + +The [`ERC721URIStorage`](/contracts/4.x/api/token/ERC721#ERC721URIStorage) contract is an implementation of ERC721 that includes the metadata standard extensions ([`IERC721Metadata`](/contracts/4.x/api/token/ERC721#IERC721Metadata)) as well as a mechanism for per-token metadata. That’s where the [`_setTokenURI`](/contracts/4.x/api/token/ERC721#ERC721-_setTokenURI-uint256-string-) method comes from: we use it to store an item’s metadata. + +Also note that, unlike ERC20, ERC721 lacks a `decimals` field, since each token is distinct and cannot be partitioned. + +New items can be created: + +```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: + +```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: + +```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 [ERC721 specification](https://eips.ethereum.org/EIPS/eip-721). + + +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! + + + +If you’d like to put all item information on-chain, you can extend ERC721 to do so (though it will be rather costly) by providing a [`Base64`](/contracts/4.x/utilities#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. + + +## Preset ERC721 contract +A preset ERC721 is available, [`ERC721PresetMinterPauserAutoId`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.7/contracts/token/ERC721/presets/ERC721PresetMinterPauserAutoId.sol). It is preconfigured with token minting (creation) with token ID and URI auto generation, the ability to stop all token transfers (pause), and it allows holders to burn (destroy) their tokens. The contract uses [Access Control](/contracts/4.x/access-control) to control access to the minting and pausing functionality. The account that deploys the contract will be granted the minter and pauser roles, as well as the default admin role. + +This contract is ready to deploy without having to write any Solidity code. It can be used as-is for quick prototyping and testing but is also suitable for production environments. + + +Contract presets are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com) as a more powerful alternative. + diff --git a/docs/content/contracts/4.x/erc777.mdx b/docs/content/contracts/4.x/erc777.mdx new file mode 100644 index 00000000..94e13496 --- /dev/null +++ b/docs/content/contracts/4.x/erc777.mdx @@ -0,0 +1,75 @@ +--- +title: ERC777 +--- + + +As of v4.9, OpenZeppelin’s implementation of ERC-777 is deprecated and will be removed in the next major release. + + +Like [ERC20](/contracts/4.x/erc20), ERC777 is a standard for [_fungible_ tokens](/contracts/4.x/tokens#different-kinds-of-tokens), and is focused around allowing more complex interactions when trading tokens. More generally, it brings tokens and Ether closer together by providing the equivalent of a `msg.value` field, but for tokens. + +The standard also brings multiple quality-of-life improvements, such as getting rid of the confusion around `decimals`, minting and burning with proper events, among others, but its killer feature is **receive hooks**. A hook is simply a function in a contract that is called when tokens are sent to it, meaning **accounts and contracts can react to receiving tokens**. + +This enables a lot of interesting use cases, including atomic purchases using tokens (no need to do `approve` and `transferFrom` in two separate transactions), rejecting reception of tokens (by reverting on the hook call), redirecting the received tokens to other addresses (similarly to how [`PaymentSplitter`] does it), among many others. + +Furthermore, since contracts are required to implement these hooks in order to receive tokens, _no tokens can get stuck in a contract that is unaware of the ERC777 protocol_, as has happened countless times when using ERC20s. + +## What If I Already Use ERC20? + +The standard has you covered! The ERC777 standard is **backwards compatible with ERC20**, meaning you can interact with these tokens as if they were ERC20, using the standard functions, while still getting all of the niceties, including send hooks. See the [EIP’s Backwards Compatibility section](https://eips.ethereum.org/EIPS/eip-777#backward-compatibility) to learn more. + +## Constructing an ERC777 Token Contract + +We will replicate the `GLD` example of the [ERC20 guide](/contracts/4.x/erc20#constructing-an-erc20-token-contract), this time using ERC777. As always, check out the [`API reference`](/contracts/4.x/api/token/ERC777) to learn more about the details of each function. + +```solidity +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC777/ERC777.sol"; + +contract GLDToken is ERC777 + constructor(uint256 initialSupply, address[] memory defaultOperators) + ERC777("Gold", "GLD", defaultOperators) + { + _mint(msg.sender, initialSupply, "", ""); + +} +``` + +In this case, we’ll be extending from the [`ERC777`](/contracts/4.x/api/token/ERC777#ERC777) contract, which provides an implementation with compatibility support for ERC20. The API is quite similar to that of [`ERC777`](/contracts/4.x/api/token/ERC777#ERC777), and we’ll once again make use of [`_mint`](/contracts/4.x/api/token/ERC777#ERC777-_mint-address-address-uint256-bytes-bytes-) to assign the `initialSupply` to the deployer account. Unlike [ERC20’s `_mint`](/contracts/4.x/api/token/ERC20#ERC20-_mint-address-uint256-), this one includes some extra parameters, but you can safely ignore those for now. + +You’ll notice both [`name`](/contracts/4.x/api/token/ERC777#IERC777-name--) and [`symbol`](/contracts/4.x/api/token/ERC777#IERC777-symbol--) are assigned, but not [`decimals`](/contracts/4.x/api/token/ERC777#ERC777-decimals--). The ERC777 specification makes it mandatory to include support for these functions (unlike ERC20, where it is optional and we had to include [`ERC20Detailed`](/contracts/4.x/api/token/ERC20#ERC20Detailed)), but also mandates that `decimals` always returns a fixed value of `18`, so there’s no need to set it ourselves. For a review of ``decimals`’s role and importance, refer back to our [ERC20 guide](/contracts/4.x/erc20#a-note-on-decimals). + +Finally, we’ll need to set the [`defaultOperators`](/contracts/4.x/api/token/ERC777#IERC777-defaultOperators--): special accounts (usually other smart contracts) that will be able to transfer tokens on behalf of their holders. If you’re not planning on using operators in your token, you can simply pass an empty array. _Stay tuned for an upcoming in-depth guide on ERC777 operators!_ + +That’s it for a basic token contract! We can now deploy it, and use the same [`balanceOf`](/contracts/4.x/api/token/ERC777#IERC777-balanceOf-address-) method to query the deployer’s balance: + +```javascript +> GLDToken.balanceOf(deployerAddress) +1000 +``` + +To move tokens from one account to another, we can use both [`ERC20`s `transfer`](/contracts/4.x/api/token/ERC777#ERC777-transfer-address-uint256-) method, or the new [`ERC777`’s `send`](/contracts/4.x/api/token/ERC777#ERC777-send-address-uint256-bytes-), which fulfills a very similar role, but adds an optional `data` field: + +```javascript +> GLDToken.transfer(otherAddress, 300) +> GLDToken.send(otherAddress, 300, "") +> GLDToken.balanceOf(otherAddress) +600 +> GLDToken.balanceOf(deployerAddress) +400 +``` + +## Sending Tokens to Contracts + +A key difference when using [`send`](/contracts/4.x/api/token/ERC777#ERC777-send-address-uint256-bytes-) is that token transfers to other contracts may revert with the following message: + +```text +ERC777: token recipient contract has no implementer for ERC777TokensRecipient +``` + +This is a good thing! It means that the recipient contract has not registered itself as aware of the ERC777 protocol, so transfers to it are disabled to **prevent tokens from being locked forever**. As an example, [the Golem contract currently holds over 350k `GNT` tokens](https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d), worth multiple tens of thousands of dollars, and lacks methods to get them out of there. This has happened to virtually every ERC20-backed project, usually due to user error. + +_An upcoming guide will cover how a contract can register itself as a recipient, send and receive hooks, and other advanced features of ERC777!_ diff --git a/docs/content/contracts/4.x/extending-contracts.mdx b/docs/content/contracts/4.x/extending-contracts.mdx new file mode 100644 index 00000000..ec0096f5 --- /dev/null +++ b/docs/content/contracts/4.x/extending-contracts.mdx @@ -0,0 +1,130 @@ +--- +title: Extending Contracts +--- + +Most of the OpenZeppelin Contracts are expected to be used via [inheritance](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance): you will _inherit_ from them when writing your own contracts. + +This is the commonly found `is` syntax, like in `contract MyToken is ERC20`. + + + +Unlike ``contract``s, Solidity ``library``s are not inherited from and instead rely on the [`using for`](https://solidity.readthedocs.io/en/latest/contracts.html#using-for) syntax. + +OpenZeppelin Contracts has some ``library``s: most are in the [Utils](/contracts/4.x/api/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 [`AccessControl`](/contracts/4.x/api/access#AccessControl) so that [`revokeRole`](/contracts/4.x/api/access#AccessControl-revokeRole-bytes32-address-) can no longer be called. This can be achieved using overrides: + +```solidity +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract ModifiedAccessControl is AccessControl + // Override the revokeRole function + function revokeRole(bytes32, address) public override { + revert("ModifiedAccessControl: cannot revoke roles"); + +} +``` + +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. + + +For more information on how overrides work, head over to the [official Solidity documentation](https://solidity.readthedocs.io/en/latest/contracts.html#index-17). + + +Here is a modified version of [`AccessControl`](/contracts/4.x/api/access#AccessControl) where [`revokeRole`](/contracts/4.x/api/access#AccessControl-revokeRole-bytes32-address-) cannot be used to revoke the `DEFAULT_ADMIN_ROLE`: + +```solidity +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/AccessControl.sol"; + +contract ModifiedAccessControl is AccessControl + function revokeRole(bytes32 role, address account) public override { + require( + role != DEFAULT_ADMIN_ROLE, + "ModifiedAccessControl: cannot revoke default admin role" + ); + + 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. + + +The same rule is implemented and extended in [`AccessControlDefaultAdminRules`](/contracts/4.x/api/access#AccessControlDefaultAdminRules), an extension that also adds enforced security measures for the `DEFAULT_ADMIN_ROLE`. + + +## Using Hooks + +Sometimes, in order to extend a parent contract you will need to override multiple related functions, which leads to code duplication and increased likelihood of bugs. + +For example, consider implementing safe [`ERC20`](/contracts/4.x/api/token/ERC20#ERC20) transfers in the style of [`IERC721Receiver`](/contracts/4.x/api/token/ERC721#IERC721Receiver). You may think overriding [`transfer`](/contracts/4.x/api/token/ERC20#ERC20-transfer-address-uint256-) and [`transferFrom`](/contracts/4.x/api/token/ERC20#ERC20-transferFrom-address-address-uint256-) would be enough, but what about [`_transfer`](/contracts/4.x/api/token/ERC20#ERC20-_transfer-address-address-uint256-) and [`_mint`](/contracts/4.x/api/token/ERC20#ERC20-_mint-address-uint256-)? To prevent you from having to deal with these details, we introduced ***hooks***. + +Hooks are simply functions that are called before or after some action takes place. They provide a centralized point to _hook into_ and extend the original behavior. + +Here’s how you would implement the `IERC721Receiver` pattern in `ERC20`, using the [`_beforeTokenTransfer`](/contracts/4.x/api/token/ERC20#ERC20-_beforeTokenTransfer-address-address-uint256-) hook: + +```solidity +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20WithSafeTransfer is ERC20 + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override + { + super._beforeTokenTransfer(from, to, amount); + + require(_validRecipient(to), "ERC20WithSafeTransfer: invalid recipient"); + + function _validRecipient(address to) private view returns (bool) { + ... + + + ... +} +``` + +Using hooks this way leads to cleaner and safer code, without having to rely on a deep understanding of the parent’s internals. + +### Rules of Hooks + +There’s a few guidelines you should follow when writing code that uses hooks in order to prevent issues. They are very simple, but do make sure you follow them: + +1. Whenever you override a parent’s hook, re-apply the `virtual` attribute to the hook. That will allow child contracts to add more functionality to the hook. +2. ***Always*** call the parent’s hook in your override using `super`. This will make sure all hooks in the inheritance tree are called: contracts like [`ERC20Pausable`](/contracts/4.x/api/token/ERC20#ERC20Pausable) rely on this behavior. + +```solidity +contract MyToken is ERC20 + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override // Add virtual here! + { + super._beforeTokenTransfer(from, to, amount); // Call parent hook + ... + +} +``` +That’s it! Enjoy simpler code using hooks! + +## 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, and those of hooks in particular, may break some important assumptions and introduce vulnerabilities in otherwise secure code. 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 so as 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. diff --git a/docs/content/contracts/4.x/governance.mdx b/docs/content/contracts/4.x/governance.mdx new file mode 100644 index 00000000..96050587 --- /dev/null +++ b/docs/content/contracts/4.x/governance.mdx @@ -0,0 +1,238 @@ +--- +title: 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. + + +Find detailed contract documentation at [Governance API](/contracts/4.x/api/governance). + + +## 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 ERC20 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 & GovernorCompatibilityBravo + +An OpenZeppelin Governor contract is by default 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 are Bravo are likewise not available. It’s possible to opt in to a higher level of compatibility by inheriting from the GovernorCompatibilityBravo module, which covers the proposal lifecycle functions such as `propose` and `execute`. + +Note that even with the use of this module, there will still be differences in the way that `proposalId`s 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 `proposalId`s. Due to this and other differences, several of the functions from GovernorBravo are not included in the compatibility module. + +### 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 + +[Tally](https://www.tally.xyz) 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, visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use Defender Admin 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 ERC20 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. + +./examples/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. + +./examples/governance/MyTokenWrapped.sol + + +The only other source of voting power available in OpenZeppelin Contracts currently is [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes). ERC721 tokens that don’t provide this functionality can be wrapped into a voting tokens using a combination of [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes) and [`ERC721Wrapper`](/contracts/4.x/api/token/ERC721#ERC721Wrapper). + + + +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 [IERC6372](/contracts/4.x/api/interfaces#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 (ERC6372) 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 who have enough voting power. + +./examples/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. + + +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). +* 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. + +## 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 ERC20 tokens from the governance treasury. This proposal will consist of a single action where the target is the ERC20 token, calldata is the encoded function call `transfer(, )`, and with 0 ETH attached. + +Generally a proposal will be created with the help of an interface such as Tally or Defender. 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. + +![Voting in Tally](/tally-vote.png) + +### 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. + +![Administration Panel in Tally](/tally-exec.png) + +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.utils.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 ran immediately after the proposal succeeds. + +```javascript +await governor.execute( + [tokenAddress], + [0], + [transferCalldata], + descriptionHash, +); +``` + +Executing the proposal will transfer the ERC20 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 [`ERC20Votes`](/contracts/4.x/api/token/ERC20#ERC20Votes) and [`ERC721Votes`](/contracts/4.x/api/token/ERC721#ERC721Votes)) rely on [IERC6372](/contracts/4.x/api/interfaces#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. + +./examples/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 +pragma solidity ^0.8.2; + +import "@openzeppelin/contracts/governance/Governor.sol"; +import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; + +contract MyGovernor is Governor, GovernorCompatibilityBravo, 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 EIP-6372 and EIP-5805, and introduced in v4.9. At the time this feature is released, governance tooling such as [Tally](https://www.tally.xyz) does not support it yet. While support for timestamps should come soon, users can expect invalid reporting of deadlines & durations. 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. diff --git a/docs/content/contracts/4.x/index.mdx b/docs/content/contracts/4.x/index.mdx new file mode 100644 index 00000000..dd647b68 --- /dev/null +++ b/docs/content/contracts/4.x/index.mdx @@ -0,0 +1,64 @@ +--- +title: Contracts +--- + +**A library for secure smart contract development.** Build on a solid foundation of community-vetted code. + +* Implementations of standards like [ERC20](/contracts/4.x/erc20) and [ERC721](/contracts/4.x/erc721). +* Flexible [role-based permissioning](/contracts/4.x/access-control) scheme. +* Reusable [Solidity components](/contracts/4.x/utilities) to build custom contracts and complex decentralized systems. + +## Overview + +### Installation + +```console +$ npm install @openzeppelin/contracts +``` + +OpenZeppelin Contracts features a [stable API(/contracts/4.x/releases-stability#api-stability), which means your contracts won’t break unexpectedly when upgrading to a newer minor version. + +### Usage + +Once installed, you can use the contracts in the library by importing them: + +```solidity +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MyNFT is ERC721 + constructor() ERC721("MyNFT", "MNFT") { + +} +``` + + +If you’re new to smart contract development, head to [Developing Smart Contracts(/contracts/5.x/learn/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 + +Please report any security issues you find via our [bug bounty program on Immunefi](https://www.immunefi.com/bounty/openzeppelin) or directly to security@openzeppelin.org. + +The [Security Center](https://contracts.openzeppelin.com/security) contains more details about the secure development process. + +## Learn More + +The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides: + +* [Access Control](/contracts/4.x/access-control): decide who can perform each of the actions on your system. +* [Tokens](/contracts/4.x/tokens): create tradable assets or collectibles, like the well known [ERC20](/contracts/4.x/erc20) and [ERC721](/contracts/4.x/erc721) standards. +* [Utilities](/contracts/4.x/utilities): generic useful tools, including non-overflowing math, signature verification, and trustless paying systems. + +The [full API](/contracts/4.x/api/token/ERC20) 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](https://forum.openzeppelin.com). + +Finally, you may want to take a look at the [guides on our blog](https://blog.openzeppelin.com/guides/), which cover several common use cases and good practices. 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. + +* [The Hitchhiker’s Guide to Smart Contracts in Ethereum](https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05) will help you get an overview of the various tools available for smart contract development, and help you set up your environment. +* [A Gentle Introduction to Ethereum Programming, Part 1](https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094) provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. +* For a more in-depth dive, you may read the guide [Designing the architecture for your Ethereum application](https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317), which discusses how to better structure your application and its relationship to the real world. diff --git a/docs/content/contracts/4.x/releases-stability.mdx b/docs/content/contracts/4.x/releases-stability.mdx new file mode 100644 index 00000000..f4e298d6 --- /dev/null +++ b/docs/content/contracts/4.x/releases-stability.mdx @@ -0,0 +1,73 @@ +--- +title: New Releases and API Stability +--- + +Developing smart contracts is hard, and a conservative approach towards dependencies is sometimes favored. However, it is also very important to stay on top of new releases: these may include bug fixes, or deprecate old patterns in favor of newer and better practices. + +Here we describe when you should expect new releases to come out, and how this affects you as a user of OpenZeppelin Contracts. + +## Release Schedule + +OpenZeppelin Contracts follows a [semantic versioning scheme](#versioning-scheme). + +We aim for a new minor release every 1 or 2 months. + +### Release Candidates + +Before every release, we publish a feature-frozen release candidate. The purpose of the release candidate is to have a period where the community can review the new code before the actual release. If important problems are discovered, several more release candidates may be required. After a week of no more changes to the release candidate, the new version is published. + +### Major Releases + +After several months or a year, a new major release may come out. These are not scheduled, but will be based on the need to release breaking changes such as a redesign of a core feature of the library (e.g. [access control](https://github.com/OpenZeppelin/openzeppelin-contracts/pulls/2112) in 3.0). Since we value stability, we aim for these to happen infrequently (expect no less than six months between majors). However, we may be forced to release one when there are big changes to the Solidity language. + +## API Stability + +On the [OpenZeppelin Contracts 2.0 release](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0), we committed ourselves to keeping a stable API. We aim to more precisely define what we understand by _stable_ and _API_ here, so users of the library can understand these guarantees and be confident their project won’t break unexpectedly. + +In a nutshell, the API being stable means _if your project is working today, it will continue to do so after a minor upgrade_. New contracts and features will be added in minor releases, but only in a backwards compatible way. + +### Versioning Scheme + +We follow [SemVer](https://semver.org/), which means API breakage may occur between major releases (which [don’t happen very often](#release-schedule)). + +### Solidity Functions + +While the internal implementation of functions may change, their semantics and signature will remain the same. The domain of their arguments will not be less restrictive (e.g. if transferring a value of 0 is disallowed, it will remain disallowed), nor will general state restrictions be lifted (e.g. `whenPaused` modifiers). + +If new functions are added to a contract, it will be in a backwards-compatible way: their usage won’t be mandatory, and they won’t extend functionality in ways that may foreseeably break an application (e.g. [an `internal` method may be added to make it easier to retrieve information that was already available](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1512)). + +#### `internal` + +This extends not only to `external` and `public` functions, but also `internal` ones: many contracts are meant to be used by inheriting them (e.g. `Pausable`, `PullPayment`, `AccessControl`), and are therefore used by calling these functions. Similarly, since all OpenZeppelin Contracts state variables are `private`, they can only be accessed this way (e.g. to create new `ERC20` tokens, instead of manually modifying `totalSupply` and `balances`, `_mint` should be called). + +`private` functions have no guarantees on their behavior, usage, or existence. + +Finally, sometimes language limitations will force us to e.g. make `internal` a function that should be `private` in order to implement features the way we want to. These cases will be well documented, and the normal stability guarantees won’t apply. + +### Libraries + +Some of our Solidity libraries use ``struct``s to handle internal data that should not be accessed directly (e.g. `Counter`). There’s an [open issue](https://github.com/ethereum/solidity/issues/4637) in the Solidity repository requesting a language feature to prevent said access, but it looks like it won’t be implemented any time soon. Because of this, we will use leading underscores and mark said `struct` s to make it clear to the user that its contents and layout are _not_ part of the API. + +### Events + +No events will be removed, and their arguments won’t be changed in any way. New events may be added in later versions, and existing events may be emitted under new, reasonable circumstances (e.g. [from 2.1 on, `ERC20` also emits `Approval` on `transferFrom` calls](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/707)). + +### Drafts + +Some contracts implement EIPs that are still in Draft status, recognizable by a file name beginning with `draft-`, such as `utils/cryptography/draft-EIP712.sol`. Due to their nature as drafts, the details of these contracts may change and we cannot guarantee their stability. Minor releases of OpenZeppelin Contracts may contain breaking changes for the contracts labelled as Drafts, which will be duly announced in the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md). The EIPs included are used by projects in production and this may make them less likely to change significantly. + +### Gas Costs + +While attempts will generally be made to lower the gas costs of working with OpenZeppelin Contracts, there are no guarantees regarding this. In particular, users should not assume gas costs will not increase when upgrading library versions. + +### Bug Fixes + +The API stability guarantees may need to be broken in order to fix a bug, and we will do so. This decision won’t be made lightly however, and all options will be explored to make the change as non-disruptive as possible. When sufficient, contracts or functions which may result in unsafe behavior will be deprecated instead of removed (e.g. [#1543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1543) and [#1550](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1550)). + +### Solidity Compiler Version + +Starting on version 0.5.0, the Solidity team switched to a faster release cycle, with minor releases every few weeks (v0.5.0 was released on November 2018, and v0.5.5 on March 2019), and major, breaking-change releases every couple of months (with v0.6.0 released on December 2019 and v0.7.0 on July 2020). Including the compiler version in OpenZeppelin Contract’s stability guarantees would therefore force the library to either stick to old compilers, or release frequent major updates simply to keep up with newer Solidity releases. + +Because of this, **the minimum required Solidity compiler version is not part of the stability guarantees**, and users may be required to upgrade their compiler when using newer versions of Contracts. Bug fixes will still be backported to past major releases so that all versions currently in use receive these updates. + +You can read more about the rationale behind this, the other options we considered and why we went down this path [here](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1498#issuecomment-449191611). diff --git a/docs/content/contracts/4.x/tokens.mdx b/docs/content/contracts/4.x/tokens.mdx new file mode 100644 index 00000000..0301c6c8 --- /dev/null +++ b/docs/content/contracts/4.x/tokens.mdx @@ -0,0 +1,32 @@ +--- +title: 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 + +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 + +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 [fungibility](https://en.wikipedia.org/wiki/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 ERC20 or ERC721 token standards, and that’s why you’re here. Head to our specialized guides to learn more about these: + +* [ERC20](/contracts/4.x/erc20): the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. +* [ERC721](/contracts/4.x/erc721): the de-facto solution for non-fungible tokens, often used for collectibles and games. +* [ERC777](/contracts/4.x/erc777): a richer standard for fungible tokens, enabling new use cases and building on past learnings. Backwards compatible with ERC20. +* [ERC1155](/contracts/4.x/erc1155): 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. diff --git a/docs/content/contracts/4.x/upgradeable.mdx b/docs/content/contracts/4.x/upgradeable.mdx new file mode 100644 index 00000000..8bd659e1 --- /dev/null +++ b/docs/content/contracts/4.x/upgradeable.mdx @@ -0,0 +1,77 @@ +--- +title: Using with Upgrades +--- + +If your contract is going to be deployed with upgradeability, such as using the [OpenZeppelin Upgrades Plugins](/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 [OpenZeppelin/openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable). + +It follows all of the rules for [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable): constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions. + + +OpenZeppelin provides a full suite of tools for deploying and securing upgradeable smart contracts. [Check out the full list of resources](/upgrades). + + +## Overview + +### Installation + +```console +$ npm install @openzeppelin/contracts-upgradeable +``` + +### Usage + +The package replicates the structure of the main OpenZeppelin Contracts package, but every file and contract has the suffix `Upgradeable`. + +```diff +-import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; ++import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; + +-contract MyCollectible is ERC721 ++contract MyCollectible is ERC721Upgradeable { +``` + +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") public ++ function initialize() initializer public { ++ __ERC721_init("MyCollectible", "MCO"); + +``` + + +Use with multiple inheritance requires special attention. See the section below titled [Multiple Inheritance](#multiple-inheritance). + + +Once this contract is set up and compiled, you can deploy it using the [Upgrades Plugins](/upgrades-plugins). The following snippet shows an example deployment script using Hardhat. + +```js +const ethers, upgrades = require("hardhat"); + +async function main() + const MyCollectible = await ethers.getContractFactory("MyCollectible"); + + const mc = await upgrades.deployProxy(MyCollectible); + + await mc.deployed(); + console.log("MyCollectible deployed to:", mc.address); + + +main(); +``` + +## Further Notes + +### 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. + +### Storage Gaps + +You may notice that every contract includes a state variable named `__gap`. This is empty reserved space in storage that is put in place in Upgradeable contracts. It allows us to freely add new state variables in the future without compromising the storage compatibility with existing deployments. + +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 [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable#modifying-your-contracts). The size of the `__gap` array is calculated so that the amount of storage used by a contract always adds up to the same number (in this case 50 storage slots). diff --git a/docs/content/contracts/4.x/utilities.mdx b/docs/content/contracts/4.x/utilities.mdx new file mode 100644 index 00000000..f19738fb --- /dev/null +++ b/docs/content/contracts/4.x/utilities.mdx @@ -0,0 +1,182 @@ +--- +title: Utilities +--- + +The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. Here are some of the more popular ones. + +## Cryptography + +### Checking Signatures On-Chain + +[`ECDSA`](/contracts/4.x/api/utils#ECDSA) provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via [`web3.eth.sign`](https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#sign), and are 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 [`ECDSA.recover`](/contracts/4.x/api/utils#ECDSA-recover-bytes32-bytes-), 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 [`toEthSignedMessageHash`](/contracts/4.x/api/utils#ECDSA-toEthSignedMessageHash-bytes32-). + +```solidity +using ECDSA for bytes32; + +function _verify(bytes32 data, bytes memory signature, address account) internal pure returns (bool) + return data + .toEthSignedMessageHash() + .recover(signature) == account; + +``` + + +Getting signature verification right is not trivial: make sure you fully read and understand [`ECDSA`](/contracts/4.x/api/utils#ECDSA)'s documentation. + + +### Verifying Merkle Proofs + +[`MerkleProof`](/contracts/4.x/api/utils#MerkleProof) provides: + +* [`verify`](/contracts/4.x/api/utils#MerkleProof-verify-bytes32---bytes32-bytes32-) - can prove that some value is part of a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). +* [`multiProofVerify`](/contracts/4.x/api/utils#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---) - can prove multiple values are part of a Merkle tree. + +## Introspection + +In Solidity, it’s frequently helpful to know whether or not a contract supports an interface you’d like to use. ERC165 is a standard that helps do runtime interface detection. Contracts provide helpers both for implementing ERC165 in your contracts and querying other contracts: + +* [`IERC165`](/contracts/4.x/api/utils#IERC165) — this is the ERC165 interface that defines [`supportsInterface`](/contracts/4.x/api/utils#IERC165-supportsInterface-bytes4-). When implementing ERC165, you’ll conform to this interface. +* [`ERC165`](/contracts/4.x/api/utils#ERC165) — inherit this contract if you’d like to support interface detection using a lookup table in contract storage. You can register interfaces using [`_registerInterface(bytes4)`](/contracts/4.x/api/utils#ERC165-_registerInterface-bytes4-): check out example usage as part of the ERC721 implementation. +* [`ERC165Checker`](/contracts/4.x/api/utils#ERC165Checker) — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about. +* include with `using ERC165Checker for address;` +* [`myAddress._supportsInterface(bytes4)`](/contracts/4.x/api/utils#ERC165Checker-_supportsInterface-address-bytes4-) +* [`myAddress._supportsAllInterfaces(bytes4[\])`](/contracts/4.x/api/utils#ERC165Checker-_supportsAllInterfaces-address-bytes4---) + +```solidity +contract MyContract + using ERC165Checker for address; + + bytes4 private InterfaceId_ERC721 = 0x80ac58cd; + + /** + * @dev transfer an ERC721 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 + +The most popular math related library OpenZeppelin Contracts provides is [`SafeMath`](/contracts/4.x/api/utils#SafeMath), which provides mathematical functions that protect your contract from overflows and underflows. + +Include the contract with `using SafeMath for uint256;` and then call the functions: + +* `myNumber.add(otherNumber)` +* `myNumber.sub(otherNumber)` +* `myNumber.div(otherNumber)` +* `myNumber.mul(otherNumber)` +* `myNumber.mod(otherNumber)` + +Easy! + +## Payment + +Want to split some payments between multiple people? Maybe you have an app that sends 30% of art purchases to the original creator and 70% of the profits to the current owner; you can build that with [`PaymentSplitter`](/contracts/4.x/api/finance#PaymentSplitter)! + +In Solidity, there are some security concerns with blindly sending money to accounts, since it allows them to execute arbitrary code. You can read up on these security concerns in the [Ethereum Smart Contract Best Practices](https://consensys.github.io/smart-contract-best-practices/) website. One of the ways to fix reentrancy and stalling problems is, instead of immediately sending Ether to accounts that need it, you can use [`PullPayment`](/contracts/4.x/api/security#PullPayment), which offers an [`_asyncTransfer`](/contracts/4.x/api/security#PullPayment-_asyncTransfer-address-uint256-) function for sending money to something and requesting that they [`withdrawPayments()`](/contracts/4.x/api/security#PullPayment-withdrawPayments-address-payable-) it later. + +If you want to Escrow some funds, check out [`Escrow`](/contracts/4.x/api/utils#Escrow) and [`ConditionalEscrow`](/contracts/4.x/api/utils#ConditionalEscrow) for governing the release of some escrowed Ether. + +## Collections + +If you need support for more powerful collections than Solidity’s native arrays and mappings, take a look at [`EnumerableSet`](/contracts/4.x/api/utils#EnumerableSet) and [`EnumerableMap`](/contracts/4.x/api/utils#EnumerableMap). They 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. + +## Misc + +Want to check if an address is a contract? Use [`Address`](/contracts/4.x/api/utils#Address) and [`Address.isContract()`](/contracts/4.x/api/utils#Address-isContract-address-). + +Want to keep track of some numbers that increment by 1 every time you want another one? Check out [`Counters`](/contracts/4.x/api/utils#Counters). This is useful for lots of things, like creating incremental identifiers, as shown on the [ERC721 guide](/contracts/4.x/erc721). + +### Base64 + +[`Base64`](/contracts/4.x/api/utils#Base64) util allows you to transform `bytes32` data into its Base64 `string` representation. + +This is especially useful for building URL-safe tokenURIs for both [`ERC721`](/contracts/4.x/api/token/ERC721#IERC721Metadata-tokenURI-uint256-) or [`ERC1155`](/contracts/4.x/api/token/ERC1155#IERC1155MetadataURI-uri-uint256-). This library provides a clever way to serve URL-safe [Data URI](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/) compliant strings to serve on-chain data structures. + +Here is an example to send JSON Metadata through a Base64 Data URI using an ERC721: + +```solidity +// contracts/My721Token.sol +// SPDX-License-Identifier: MIT + +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/Base64.sol"; + +contract My721Token is ERC721 + using Strings for uint256; + + constructor() ERC721("My721Token", "MTK") { + + ... + + function tokenURI(uint256 tokenId) + public + pure + override + returns (string memory) + + bytes memory dataURI = abi.encodePacked( + '{', + '"name": "My721Token #', tokenId.toString(), '"', + // Replace with extra ERC721 Metadata properties + '' + ); + + return string( + abi.encodePacked( + "data:application/json;base64,", + Base64.encode(dataURI) + ) + ); + } +} +``` + +### 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: + +```solidity +// contracts/Box.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/utils/Multicall.sol"; + +contract Box is Multicall + function foo() public { + ... + + + function bar() public + ... + +} +``` + +This is how to call the `multicall` function using Truffle, allowing `foo` and `bar` to be called in a single transaction: +```javascript +// scripts/foobar.js + +const Box = artifacts.require('Box'); +const instance = await Box.new(); + +await instance.multicall([ + instance.contract.methods.foo().encodeABI(), + instance.contract.methods.bar().encodeABI() +]); +``` diff --git a/docs/content/contracts/5.x/access-control.mdx b/docs/content/contracts/5.x/access-control.mdx new file mode 100644 index 00000000..db04f520 --- /dev/null +++ b/docs/content/contracts/5.x/access-control.mdx @@ -0,0 +1,275 @@ +--- +title: 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 [steals your whole system](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7). + +## 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 [`Ownable`](/contracts/5.x/api/access#Ownable) for implementing ownership in your contracts. + +./examples/access-control/MyContractOwnable.sol + +At deployment, the [`owner`](/contracts/5.x/api/access#Ownable-owner--) of an `Ownable` contract is set to the provided `initialOwner` parameter. + +Ownable also lets you: + +* [`transferOwnership`](/contracts/5.x/api/access#Ownable-transferOwnership-address-) from the owner account to a new one, and +* [`renounceOwnership`](/contracts/5.x/api/access#Ownable-renounceOwnership--) for the owner to relinquish this administrative privilege, a common pattern after an initial stage with centralized administration is over. + + +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 [`Ownable2Step`](/contracts/5.x/api/access#Ownable2Step); a variant of Ownable that requires the new owner to explicitly accept the ownership transfer by calling [`acceptOwnership`](/contracts/5.x/api/access#Ownable2Step-acceptOwnership--). + +Note that **a contract can also be the owner of another one**! This opens the door to using, for example, a [Gnosis Safe](https://safe.global), an [Aragon DAO](https://aragon.org), 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 [MakerDAO](https://makerdao.com), use systems similar to this one. + +## 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 for an account to have permission to ban users from a system, but not create new tokens. [_Role-Based Access Control (RBAC)_](https://en.wikipedia.org/wiki/Role-based_access_control) 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 `AccessControl` + +OpenZeppelin Contracts provides [`AccessControl`](/contracts/5.x/api/access#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 [ERC-20 token](./erc20) to define a 'minter' role, which allows accounts that have it create new tokens: + +./examples/access-control/AccessControlERC20MintBase.sol + + +Make sure you fully understand how [`AccessControl`](/contracts/5.x/api/access#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: + +./examples/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 [principle of least privilege](https://en.wikipedia.org/wiki/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 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 [`AccessControlDefaultAdminRules`](/contracts/5.x/api/access#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 in between steps. + +Let’s take a look at the ERC-20 token example, this time taking advantage of the default admin role: + +./examples/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 [KYC](https://en.wikipedia.org/wiki/Know_your_customer), 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 + +Because accounts might [grant and revoke roles](#granting-and-revoking-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. + +Under the hood, `AccessControl` uses `EnumerableSet`, a more powerful variant of Solidity’s `mapping` type, which allows for key enumeration. `getRoleMemberCount` can be used to retrieve the number of accounts that have a particular role, and `getRoleMember` can then be called to get the address of each of these accounts. + +```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 [`Ownable`](/contracts/5.x/api/access#Ownable) and [`AccessControl`](/contracts/5.x/api/access#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 [`TimelockController`](/contracts/5.x/api/governance#TimelockController) is addressing. + +The [`TimelockController`](/contracts/5.x/api/governance#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 [`TimelockController`](/contracts/5.x/api/governance#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 [`TimelockController`](/contracts/5.x/api/governance#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 [`AccessControl`](/contracts/5.x/api/access#AccessControl) interface and the `bytes32` values for each role are accessible through the `ADMIN_ROLE`, `PROPOSER_ROLE` and `EXECUTOR_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 enforce a delay on timelock maintenance operations. + + +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. + + +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 [`TimelockController`](/contracts/5.x/api/governance#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 [`getMinDelay`](/contracts/5.x/api/governance#TimelockController-getMinDelay--) method) can be updated by calling the [`updateDelay`](/contracts/5.x/api/governance#TimelockController-updateDelay-uint256-) 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 + +For a system of contracts, better integrated role management can be achieved with an [`AccessManager`](/contracts/5.x/api/access#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 [`AccessControl`](/contracts/5.x/api/access#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. + +![Access Control multiple](/access-control-multiple.svg) + +Protocols managing permissions in production systems often require more integrated alternatives to fragmented permissions through multiple `AccessControl` instances. + +![AccessManager](/access-manager.svg) + +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 [function selector](https://docs.soliditylang.org/en/v0.8.20/abi-spec.html#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). + +![AccessManager functions](/access-manager-functions.svg) + +### Using `AccessManager` + +OpenZeppelin Contracts provides [`AccessManager`](/contracts/5.x/api/access#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 [`AccessManaged`](/contracts/5.x/api/access#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 ([`initialAuthority`](/contracts/5.x/api/access#AccessManaged-constructor-address-)) in the constructor so the `restricted` modifier knows which manager to use for checking permissions. + +Here’s a simple example of an [ERC-20 token](./erc20) that defines a `mint` function that is restricted by an [`AccessManager`](/contracts/5.x/api/access#AccessManager): + +./examples/access-control/AccessManagedERC20MintBase.sol + + +Make sure you fully understand how [`AccessManager`](/contracts/5.x/api/access#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 MINTER = 42n; // Roles are uint64 (0 is reserved for the ADMIN_ROLE) + +await manager.grantRole(MINTER, user, 0); + +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 [`schedule`](/contracts/5.x/api/access#AccessManager-schedule-address-bytes-uint48-) function called on them first in the AccessManager before they can be executed, either by calling to the target function or using the AccessManager’s [`execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) 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); + +await manager.connect(initialAdmin).grantRole(MINTER, ACCOUNT, EXECUTION_DELAY); +``` + +Note that roles do not define a name. As opposed to the [`AccessControl`](/contracts/5.x/api/access#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 [`labelRole`](/contracts/5.x/api/access#AccessManager-labelRole-uint64-string-) 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(); + +await manager.setTargetClosed(target, true); + +await manager.setTargetClosed(target, false); +``` + + +Even if an `AccessManager` defines permissions for a target function, these won’t be applied if the managed contract instance is not using the [`restricted`](/contracts/5.x/api/access#AccessManaged-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 [grant](/contracts/5.x/api/access#AccessManager-grantRole-uint64-address-uint32-) and [revoke](/contracts/5.x/api/access#AccessManager-revokeRole-uint64-address-) 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`). + + +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 [`labelRole`](/contracts/5.x/api/access#AccessManager-labelRole-uint64-string-) function. +* Assign the admin and guardian of a role with [`setRoleAdmin`](/contracts/5.x/api/access#AccessManager-setRoleAdmin-uint64-uint64-) and [`setRoleGuardian`](/contracts/5.x/api/access#AccessManager-setRoleGuardian-uint64-uint64-). +* Set each role’s grant delay via [`setGrantDelay`](/contracts/5.x/api/access#AccessManager-setGrantDelay-uint64-uint32-). + +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 [`schedule`](/contracts/5.x/api/access#AccessManager-schedule-address-bytes-uint48-) and [`execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) 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 [`setTargetAdminDelay`](/contracts/5.x/api/access#AccessManager-setTargetAdminDelay-address-uint32-). + +The delayed admin actions are: + +* Updating an `AccessManaged` contract [authority](/contracts/5.x/api/access#AccessManaged-authority--) using [`updateAuthority`](/contracts/5.x/api/access#AccessManager-updateAuthority-address-address-). +* Closing or opening a target via [`setTargetClosed`](/contracts/5.x/api/access#AccessManager-setTargetClosed-address-bool-). +* Changing permissions of whether a role can call a target function with [`setTargetFunctionRole`](/contracts/5.x/api/access#AccessManager-setTargetFunctionRole-address-bytes4---uint64-). + +### Using with Ownable + +Contracts already inheriting from [`Ownable`](/contracts/5.x/api/access#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 [`execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) function, even if the caller doesn’t require a delay. + +```javascript +await ownable.connect(owner).transferOwnership(accessManager); +``` + +### Using with AccessControl + +For systems already using [`AccessControl`](/contracts/5.x/api/access#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 [`execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) method, similar to the Ownable case. + +```javascript +await accessControl.connect(admin).revokeRole(MINTER_ROLE, account); + +await accessControl.connect(admin).grantRole(DEFAULT_ADMIN_ROLE, accessManager); + +await accessControl.connect(admin).renounceRole(DEFAULT_ADMIN_ROLE, admin); +``` diff --git a/docs/content/contracts/5.x/account-abstraction.mdx b/docs/content/contracts/5.x/account-abstraction.mdx new file mode 100644 index 00000000..a099f9ae --- /dev/null +++ b/docs/content/contracts/5.x/account-abstraction.mdx @@ -0,0 +1,108 @@ +--- +title: Account Abstraction +--- + +Unlike Externally Owned Accounts (EOAs), smart contracts may contain arbitrary verification logic based on authentication mechanisms different to Ethereum’s native [ECDSA](/contracts/5.x/api/utils#ECDSA) and have execution advantages such as batching or gas sponsorship. To leverage these properties of smart contracts, the community has widely adopted [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337), a standard to process user operations through an alternative mempool. + +The library provides multiple contracts for Account Abstraction following this standard as it enables more flexible and user-friendly interactions with applications. Account Abstraction use cases include wallets in novel contexts (e.g. embedded wallets), more granular configuration of accounts, and recovery mechanisms. + +## ERC-4337 Overview + +The ERC-4337 is a detailed specification of how to implement the necessary logic to handle operations without making changes to the protocol level (i.e. the rules of the blockchain itself). This specification defines the following components: + +### UserOperation + +A `UserOperation` is a higher-layer pseudo-transaction object that represents the intent of the account. This shares some similarities with regular EVM transactions like the concept of `gasFees` or `callData` but includes fields that enable new capabilities. + +```solidity +struct PackedUserOperation { + address sender; + uint256 nonce; + bytes initCode; // concatenation of factory address and factoryData (or empty) + bytes callData; + bytes32 accountGasLimits; // concatenation of verificationGas (16 bytes) and callGas (16 bytes) + uint256 preVerificationGas; + bytes32 gasFees; // concatenation of maxPriorityFee (16 bytes) and maxFeePerGas (16 bytes) + bytes paymasterAndData; // concatenation of paymaster fields (or empty) + bytes signature; +} +``` + +This process of bundling user operations involves several costs that the bundler must cover, including base transaction fees, calldata serialization, entrypoint execution, and paymaster context costs. To compensate for these expenses, bundlers use the `preVerificationGas` and `gasFees` fields to charge users appropriately. + + +Estimating `preVerificationGas` is not standardized as it varies based on network conditions such as gas prices and the size of the operation bundle. + + + +Use [`ERC4337Utils`](/contracts/5.x/api/account#ERC4337Utils) to manipulate the `UserOperation` struct and other ERC-4337 related values. + + +### Entrypoint + +Each `UserOperation` is executed through a contract known as the [`EntryPoint`](https://etherscan.io/address/0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108#code). This contract is a singleton deployed across multiple networks at the same address although other custom implementations may be used. + +The Entrypoint contracts is considered a trusted entity by the account. + +### Bundlers + +The bundler is a piece of _offchain_ infrastructure that is in charge of processing an alternative mempool of user operations. Bundlers themselves call the Entrypoint contract’s `handleOps` function with an array of UserOperations that are executed and included in a block. + +During the process, the bundler pays for the gas of executing the transaction and gets refunded during the execution phase of the Entrypoint contract. + +```solidity +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; +} +``` + + +The `IAccountExecute` interface is optional. Developers might want to use [`ERC-7821`](/contracts/5.x/api/account#ERC7821) for a minimal batched execution interface or rely on ERC-7579 or any other execution logic. + + +To build your own account, see [accounts](./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 [account factories](./accounts#accounts-factory). + +### Paymaster Contract + +A Paymaster is an optional entity that can sponsor gas fees for Accounts, or allow them to pay for those fees in ERC-20 instead of native currency. This abstracts gas away of the user experience in the same way that computational costs of cloud servers are abstracted away from end-users. + +To build your own paymaster, see [paymasters](/community-contracts/paymasters). + +## Further notes + +### ERC-7562 Validation Rules + +To process a bundle of `UserOperations`, bundlers call [`validateUserOp`](/contracts/5.x/api/account#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-) 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, [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562) proposes a set of limitations to EVM code so that bundlers (or node operators) are protected from unexpected state changes. + +These rules outline the requirements for operations to be processed by the canonical mempool. + +Accounts can access its own storage during the validation phase, they might easily violate ERC-7562 storage access rules in undirect ways. For example, most accounts access their public keys from storage when validating a signature, limiting the ability of having accounts that validate operations for other accounts (e.g. via ERC-1271) + + +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. + diff --git a/docs/content/contracts/5.x/accounts.mdx b/docs/content/contracts/5.x/accounts.mdx new file mode 100644 index 00000000..82835243 --- /dev/null +++ b/docs/content/contracts/5.x/accounts.mdx @@ -0,0 +1,360 @@ +--- +title: Smart Accounts +--- + +OpenZeppelin provides a simple [`Account`](/contracts/5.x/api/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 [`AbstractSigner`](/contracts/5.x/api/utils#AbstractSigner), which requires to implement the internal [`_rawSignatureValidation`](/contracts/5.x/api/utils#AbstractSigner-_rawSignatureValidation-bytes32-bytes-) 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 [`validateUserOp`](/contracts/5.x/api/account#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-). + +## Setting up an account + +To setup an account, you can either start configuring it using our Wizard and selecting a predefined validation scheme, or bring your own logic and start by inheriting [`Account`](/contracts/5.x/api/account#Account) from scratch. + + + + +Accounts don’t support [ERC-721](./erc721) and [ERC-1155](./erc1155) tokens natively since these require the receiving address to implement an acceptance check. It is recommended to inherit [ERC721Holder](/contracts/5.x/api/token/ERC721#ERC721Holder), [ERC1155Holder](/contracts/5.x/api/token/ERC1155#ERC1155Holder) to include these checks in your account. + + +### Selecting a signer + +Since the minimum requirement of [`Account`](/contracts/5.x/api/account#Account) is to provide an implementation of [`_rawSignatureValidation`](/contracts/5.x/api/utils/cryptography#AbstractSigner-_rawSignatureValidation-bytes32-bytes-), the library includes specializations of the `AbstractSigner` contract that use custom digital signature verification algorithms. Some examples that you can select from include: + +* [`SignerECDSA`](/contracts/5.x/api/utils/cryptography#SignerECDSA): Verifies signatures produced by regular EVM Externally Owned Accounts (EOAs). +* [`SignerP256`](/contracts/5.x/api/utils/cryptography#SignerP256): Validates signatures using the secp256r1 curve, common for World Wide Web Consortium (W3C) standards such as FIDO keys, passkeys or secure enclaves. +* [`SignerRSA`](/contracts/5.x/api/utils/cryptography#SignerRSA): Verifies signatures of traditional PKI systems and X.509 certificates. +* [`SignerERC7702`](/contracts/5.x/api/utils/cryptography#SignerERC7702): Checks EOA signatures delegated to this signer using [EIP-7702 authorizations](https://eips.ethereum.org/EIPS/eip-7702#set-code-transaction) +* [`SignerERC7913`](/contracts/5.x/api/utils/cryptography#SignerERC7913): Verifies generalized signatures following [ERC-7913](https://eips.ethereum.org/EIPS/eip-7913). +* [`SignerZKEmail`](https://docs.openzeppelin.com/community-contracts/0.0.1/api/utils#SignerZKEmail): Enables email-based authentication for smart contracts using zero knowledge proofs of email authority signatures. +* [`MultiSignerERC7913`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913): Allows using multiple ERC-7913 signers with a threshold-based signature verification system. +* [`MultiSignerERC7913Weighted`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913Weighted): Overrides the threshold mechanism of [`MultiSignerERC7913`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913), offering different weights per signer. + + +Given [`SignerERC7913`](/contracts/5.x/api/utils/cryptography#SignerERC7913) provides a generalized standard for signature validation, you don’t need to implement your own [`AbstractSigner`](/contracts/5.x/api/utils/cryptography#AbstractSigner) for different signature schemes, consider bringing your own ERC-7913 verifier instead. + + +#### Accounts factory + +The first time you send an user operation, your account will be created deterministically (i.e. its code and address can be predicted) using the the `initCode` field in the UserOperation. This field contains both the address of a smart contract (the factory) and the data required to call it and create your smart account. + +Suggestively, you can create your own account factory using the [Clones library](/contracts/5.x/api/proxy#Clones), taking advantage of decreased deployment costs and account address predictability. + +./examples/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 [minimal clones](/contracts/5.x/api/proxy#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. + +```solidity +import {Account} from "@openzeppelin/community-contracts/account/Account.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {SignerECDSA} from "@openzeppelin/community-contracts/utils/cryptography/SignerECDSA.sol"; + +contract MyAccount is Initializable, Account, SignerECDSA, ... { + // ... + + function initializeECDSA(address signer) public initializer { + _setSigner(signer); + } +} +``` + +Note that some account implementations may be deployed directly and therefore, won’t require a factory. + + +Leaving an account uninitialized may leave it unusable since no public key was associated with it. + + +### Signature validation + +Regularly, accounts implement [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) to enable smart contract signature verification given its wide adoption. To be compliant means that smart contract exposes an [`isValidSignature(bytes32 hash, bytes memory signature)`](/contracts/5.x/api/interfaces#IERC1271-isValidSignature-bytes32-bytes-) 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 [`AbstractSigner`](/contracts/5.x/api/utils/cryptography#AbstractSigner): + +```solidity +function isValidSignature(bytes32 hash, bytes calldata signature) public view override returns (bytes4) { + return _rawSignatureValidation(hash, signature) ? IERC1271.isValidSignature.selector : bytes4(0xffffffff); +} +``` + + +We recommend using [ERC7739](/contracts/5.x/api/utils/cryptography#ERC7739) to avoid replayability across accounts. This defensive rehashing mechanism that prevents signatures for this account to be replayed in another account controlled by the same signer. See [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. [`ERC-7821`](/contracts/5.x/api/account#ERC7821) 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 [`_erc7821AuthorizedExecutor`](/contracts/5.x/api/account#ERC7821-_erc7821AuthorizedExecutor-address-bytes32-bytes-) 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: + +```solidity +import {Account} from "@openzeppelin/community-contracts/account/Account.sol"; +import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/draft-ERC7821.sol"; +import {SignerERC7702} from "@openzeppelin/community-contracts/utils/cryptography/SignerERC7702.sol"; + +contract MyAccount is Account, SignerERC7702, ERC7821 { + // Override to allow the entrypoint to execute batches + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} +``` + +The batched execution data follows a specific format that includes the calls to be executed. This format follows the same format as [ERC-7579 execution](https://eips.ethereum.org/EIPS/eip-7579#execution-behavior) 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 [viem](https://viem.sh/)'s utilities: + +```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` + +[UserOperations](./account-abstraction#useroperation) are a powerful abstraction layer that enable more sophisticated transaction capabilities compared to traditional Ethereum transactions. To get started, you’ll need to an account, which you can get by [deploying a factory](#accounts_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). + + +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 [viem](https://viem.sh/): + +```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: + +```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. + + +The `maxFeePerGas` and `maxPriorityFeePerGas` values are typically provided by your bundler service, either through their SDK or a custom RPC method. + + + +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. + +```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: + +```typescript +const userOpHash = await entrypoint.read.getUserOpHash([userOp]); +userOp.signature = await eoa.sign({ hash: userOpHash }); +``` + + +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`. + +```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, + }) + ); + +// Print receipt +console.log(userOpReceipt); +``` + + +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 [replayability across smart contract accounts controlled by the same private key](https://mirror.xyz/curiousapple.eth/pFqAdW2LiJ-6S4sg_u1z08k4vK6BCJ33LcyXpnNb8yU) (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 [obfuscated message](https://x.com/howydev/status/1780353754333634738), which is a phishing vector that may lead to a user losing its assets. + +To prevent this, developers may use [`ERC7739Signer`](./api/account#ERC7739Signer), a utility that implements [`IERC1271`](./api/interfaces#IERC1271) for smart contract signatures with a defensive rehashing mechanism based on a [nested EIP-712 approach](https://github.com/frangio/eip712-wrapper-for-eip1271) to wrap the signature request in a context where there’s clearer information for the end user. + +### EIP-7702 Delegation + +[EIP-7702](https://eips.ethereum.org/EIPS/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 [SignatureChecker](/contracts/5.x/api/utils/cryptography#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. + + +Learn more about delegating to an ERC-7702 account in our [EOA Delegation](./eoa-delegation) section. + + +### ERC-7579 Modules + +Smart accounts have evolved to embrace modularity as a design principle, with popular implementations like [Safe, Pimlico, Rhinestone, Etherspot and many others](https://erc7579.com/#supporters) 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 [`AccountERC7579`](/contracts/5.x/api/account#AccountERC7579) implementation that supports installing and managing these modules. + + +Learn more in our [account modules](https://docs.openzeppelin.com/community-contracts/0.0.1/account-modules) section. + diff --git a/docs/content/contracts/5.x/api/access.mdx b/docs/content/contracts/5.x/api/access.mdx new file mode 100644 index 00000000..292f8d31 --- /dev/null +++ b/docs/content/contracts/5.x/api/access.mdx @@ -0,0 +1,5130 @@ +--- +title: "Access" +description: "Smart contract access utilities and implementations" +--- + +This directory provides ways to restrict who can access the functions of a contract or when they can do it. + +* [`AccessManager`](#AccessManager) is a full-fledged access control solution for smart contract systems. Allows creating and assigning multiple hierarchical roles with execution delays for each account across various contracts. +* [`AccessManaged`](#AccessManaged) delegates its access control to an authority that dictates the permissions of the managed contract. It’s compatible with an AccessManager as an authority. +* [`AccessControl`](#AccessControl) provides a per-contract role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts within the same instance. +* [`Ownable`](#Ownable) is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. + +## Core + +[`Ownable`](#Ownable) + +[`Ownable2Step`](#Ownable2Step) + +[`IAccessControl`](#IAccessControl) + +[`AccessControl`](#AccessControl) + +## Extensions + +[`IAccessControlEnumerable`](#IAccessControlEnumerable) + +[`AccessControlEnumerable`](#AccessControlEnumerable) + +[`IAccessControlDefaultAdminRules`](#IAccessControlDefaultAdminRules) + +[`AccessControlDefaultAdminRules`](#AccessControlDefaultAdminRules) + +## AccessManager + +[`IAuthority`](#IAuthority) + +[`IAccessManager`](#IAccessManager) + +[`AccessManager`](#AccessManager) + +[`IAccessManaged`](#IAccessManaged) + +[`AccessManaged`](#AccessManaged) + +[`AuthorityUtils`](#AuthorityUtils) + + + +
+ +## `AccessControl` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/AccessControl.sol"; +``` + +Contract module that allows children to implement role-based access +control mechanisms. This is a lightweight version that doesn't allow enumerating role +members except through off-chain means by accessing the contract event logs. Some +applications may benefit from on-chain enumerability, for those cases see +[`AccessControlEnumerable`](#AccessControlEnumerable). + +Roles are referred to by their `bytes32` identifier. These should be exposed +in the external API and be unique. The best way to achieve this is by +using `public constant` hash digests: + +```solidity +bytes32 public constant MY_ROLE = keccak256("MY_ROLE"); +``` + +Roles can be used to represent a set of permissions. To restrict access to a +function call, use [`AccessControl.hasRole`](#AccessControl-hasRole-bytes32-address-): + +```solidity +function foo() public { + require(hasRole(MY_ROLE, msg.sender)); + ... +} +``` + +Roles can be granted and revoked dynamically via the [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and +[`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-) functions. Each role has an associated admin role, and only +accounts that have a role's admin role can call [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). + +By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means +that only accounts with this role will be able to grant or revoke other +roles. More complex role relationships can be created by using +[`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). + + +The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to +grant and revoke this role. Extra precautions should be taken to secure +accounts that have been granted it. We recommend using [`AccessControlDefaultAdminRules`](#AccessControlDefaultAdminRules) +to enforce additional security measures for this role. + + +
+

Modifiers

+
+- [onlyRole(role)](#AccessControl-onlyRole-bytes32-) +
+
+ +
+

Functions

+
+- [supportsInterface(interfaceId)](#AccessControl-supportsInterface-bytes4-) +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, callerConfirmation)](#AccessControl-renounceRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [_grantRole(role, account)](#AccessControl-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControl-_revokeRole-bytes32-address-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControl [!toc] +
+
+ +
+

Events

+
+#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControl [!toc] +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControl [!toc] +- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

onlyRole(bytes32 role)

+
+

internal

+# +
+
+ +
+ +Modifier that checks that an account has a specific role. Reverts +with an [`IAccessControl.AccessControlUnauthorizedAccount`](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) error including the required role. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

hasRole(bytes32 role, address account) → bool

+
+

public

+# +
+
+
+ +Returns `true` if `account` has been granted `role`. + +
+
+ + + +
+
+

_checkRole(bytes32 role)

+
+

internal

+# +
+
+
+ +Reverts with an [`IAccessControl.AccessControlUnauthorizedAccount`](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) error if `_msgSender()` +is missing `role`. Overriding this function changes the behavior of the [`AccessControl.onlyRole`](#AccessControl-onlyRole-bytes32-) modifier. + +
+
+ + + +
+
+

_checkRole(bytes32 role, address account)

+
+

internal

+# +
+
+
+ +Reverts with an [`IAccessControl.AccessControlUnauthorizedAccount`](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) error if `account` +is missing `role`. + +
+
+ + + +
+
+

getRoleAdmin(bytes32 role) → bytes32

+
+

public

+# +
+
+
+ +Returns the admin role that controls `role`. See [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and +[`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). + +To change a role's admin, use [`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). + +
+
+ + + +
+
+

grantRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

revokeRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +Requirements: + +- the caller must have ``role``'s admin role. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

renounceRole(bytes32 role, address callerConfirmation)

+
+

public

+# +
+
+
+ +Revokes `role` from the calling account. + +Roles are often managed via [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-): this function's +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been revoked `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) +event. + +Requirements: + +- the caller must be `callerConfirmation`. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

_setRoleAdmin(bytes32 role, bytes32 adminRole)

+
+

internal

+# +
+
+
+ +Sets `adminRole` as ``role``'s admin role. + +Emits a [`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) event. + +
+
+ + + +
+
+

_grantRole(bytes32 role, address account) → bool

+
+

internal

+# +
+
+
+ +Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted. + +Internal function without access restriction. + +May emit a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

_revokeRole(bytes32 role, address account) → bool

+
+

internal

+# +
+
+
+ +Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked. + +Internal function without access restriction. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

DEFAULT_ADMIN_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `IAccessControl` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/IAccessControl.sol"; +``` + +External interface of AccessControl declared to support ERC-165 detection. + +
+

Functions

+
+- [hasRole(role, account)](#IAccessControl-hasRole-bytes32-address-) +- [getRoleAdmin(role)](#IAccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#IAccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#IAccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, callerConfirmation)](#IAccessControl-renounceRole-bytes32-address-) +
+
+ +
+

Events

+
+- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

hasRole(bytes32 role, address account) → bool

+
+

external

+# +
+
+
+ +Returns `true` if `account` has been granted `role`. + +
+
+ + + +
+
+

getRoleAdmin(bytes32 role) → bytes32

+
+

external

+# +
+
+
+ +Returns the admin role that controls `role`. See [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and +[`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). + +To change a role's admin, use [`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). + +
+
+ + + +
+
+

grantRole(bytes32 role, address account)

+
+

external

+# +
+
+
+ +Grants `role` to `account`. + +If `account` had not been already granted `role`, emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) +event. + +Requirements: + +- the caller must have ``role``'s admin role. + +
+
+ + + +
+
+

revokeRole(bytes32 role, address account)

+
+

external

+# +
+
+
+ +Revokes `role` from `account`. + +If `account` had been granted `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +Requirements: + +- the caller must have ``role``'s admin role. + +
+
+ + + +
+
+

renounceRole(bytes32 role, address callerConfirmation)

+
+

external

+# +
+
+
+ +Revokes `role` from the calling account. + +Roles are often managed via [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-): this function's +purpose is to provide a mechanism for accounts to lose their privileges +if they are compromised (such as when a trusted device is misplaced). + +If the calling account had been granted `role`, emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) +event. + +Requirements: + +- the caller must be `callerConfirmation`. + +
+
+ + + +
+
+

RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole)

+
+

event

+# +
+
+ +
+ +Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole` + +`DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite +[`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) not being emitted to signal this. + +
+
+ + +
+
+

RoleGranted(bytes32 indexed role, address indexed account, address indexed sender)

+
+

event

+# +
+
+ +
+ +Emitted when `account` is granted `role`. + +`sender` is the account that originated the contract call. This account bears the admin role (for the granted role). +Expected in cases where the role was granted using the internal [`AccessControl._grantRole`](#AccessControl-_grantRole-bytes32-address-). + +
+
+ + +
+
+

RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender)

+
+

event

+# +
+
+ +
+ +Emitted when `account` is revoked `role`. + +`sender` is the account that originated the contract call: + - if using `revokeRole`, it is the admin role bearer + - if using `renounceRole`, it is the role bearer (i.e. `account`) + +
+
+ + + +
+
+

AccessControlUnauthorizedAccount(address account, bytes32 neededRole)

+
+

error

+# +
+
+
+ +The `account` is missing a role. + +
+
+ + + +
+
+

AccessControlBadConfirmation()

+
+

error

+# +
+
+
+ +The caller of a function is not the expected one. + + +Don't confuse with [`IAccessControl.AccessControlUnauthorizedAccount`](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-). + + +
+
+ + + +
+ +## `Ownable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/Ownable.sol"; +``` + +Contract module which provides a basic access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +The initial owner is set to the address provided by the deployer. This can +later be changed with [`Ownable.transferOwnership`](#Ownable-transferOwnership-address-). + +This module is used through inheritance. It will make available the modifier +`onlyOwner`, which can be applied to your functions to restrict their use to +the owner. + +
+

Modifiers

+
+- [onlyOwner()](#Ownable-onlyOwner--) +
+
+ +
+

Functions

+
+- [constructor(initialOwner)](#Ownable-constructor-address-) +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ +
+

Errors

+
+- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +
+
+ + + +
+
+

onlyOwner()

+
+

internal

+# +
+
+ +
+ +Throws if called by any account other than the owner. + +
+
+ + + +
+
+

constructor(address initialOwner)

+
+

internal

+# +
+
+
+ +Initializes the contract setting the address provided by the deployer as the initial owner. + +
+
+ + + +
+
+

owner() → address

+
+

public

+# +
+
+
+ +Returns the address of the current owner. + +
+
+ + + +
+
+

_checkOwner()

+
+

internal

+# +
+
+
+ +Throws if the sender is not the owner. + +
+
+ + + +
+
+

renounceOwnership()

+
+

public

+# +
+
+
+ +Leaves the contract without owner. It will not be possible to call +`onlyOwner` functions. Can only be called by the current owner. + + +Renouncing ownership will leave the contract without an owner, +thereby disabling any functionality that is only available to the owner. + + +
+
+ + + +
+
+

transferOwnership(address newOwner)

+
+

public

+# +
+
+
+ +Transfers ownership of the contract to a new account (`newOwner`). +Can only be called by the current owner. + +
+
+ + + +
+
+

_transferOwnership(address newOwner)

+
+

internal

+# +
+
+
+ +Transfers ownership of the contract to a new account (`newOwner`). +Internal function without access restriction. + +
+
+ + + +
+
+

OwnershipTransferred(address indexed previousOwner, address indexed newOwner)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

OwnableUnauthorizedAccount(address account)

+
+

error

+# +
+
+
+ +The caller account is not authorized to perform an operation. + +
+
+ + + +
+
+

OwnableInvalidOwner(address owner)

+
+

error

+# +
+
+
+ +The owner is not a valid owner account. (eg. `address(0)`) + +
+
+ + + +
+ +## `Ownable2Step` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +``` + +Contract module which provides access control mechanism, where +there is an account (an owner) that can be granted exclusive access to +specific functions. + +This extension of the [`Ownable`](#Ownable) contract includes a two-step mechanism to transfer +ownership, where the new owner must call [`Ownable2Step.acceptOwnership`](#Ownable2Step-acceptOwnership--) in order to replace the +old one. This can help prevent common mistakes, such as transfers of ownership to +incorrect accounts, or to contracts that are unable to interact with the +permission system. + +The initial owner is specified at deployment time in the constructor for `Ownable`. This +can later be changed with [`Ownable.transferOwnership`](#Ownable-transferOwnership-address-) and [`Ownable2Step.acceptOwnership`](#Ownable2Step-acceptOwnership--). + +This module is used through inheritance. It will make available all functions +from parent (Ownable). + +
+

Functions

+
+- [pendingOwner()](#Ownable2Step-pendingOwner--) +- [transferOwnership(newOwner)](#Ownable2Step-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable2Step-_transferOwnership-address-) +- [acceptOwnership()](#Ownable2Step-acceptOwnership--) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +
+
+ +
+

Events

+
+- [OwnershipTransferStarted(previousOwner, newOwner)](#Ownable2Step-OwnershipTransferStarted-address-address-) +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ +
+

Errors

+
+#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +
+
+ + + +
+
+

pendingOwner() → address

+
+

public

+# +
+
+
+ +Returns the address of the pending owner. + +
+
+ + + +
+
+

transferOwnership(address newOwner)

+
+

public

+# +
+
+
+ +Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one. +Can only be called by the current owner. + +Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer. + +
+
+ + + +
+
+

_transferOwnership(address newOwner)

+
+

internal

+# +
+
+
+ +Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner. +Internal function without access restriction. + +
+
+ + + +
+
+

acceptOwnership()

+
+

public

+# +
+
+
+ +The new owner accepts the ownership transfer. + +
+
+ + + +
+
+

OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `AccessControlDefaultAdminRules` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/extensions/AccessControlDefaultAdminRules.sol"; +``` + +Extension of [`AccessControl`](#AccessControl) that allows specifying special rules to manage +the `DEFAULT_ADMIN_ROLE` holder, which is a sensitive role with special permissions +over other roles that may potentially have privileged rights in the system. + +If a specific role doesn't have an admin role assigned, the holder of the +`DEFAULT_ADMIN_ROLE` will have the ability to grant it and revoke it. + +This contract implements the following risk mitigations on top of [`AccessControl`](#AccessControl): + +* Only one account holds the `DEFAULT_ADMIN_ROLE` since deployment until it's potentially renounced. +* Enforces a 2-step process to transfer the `DEFAULT_ADMIN_ROLE` to another account. +* Enforces a configurable delay between the two steps, with the ability to cancel before the transfer is accepted. +* The delay can be changed by scheduling, see [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). +* Role transfers must wait at least one block after scheduling before it can be accepted. +* It is not possible to use another role to manage the `DEFAULT_ADMIN_ROLE`. + +Example usage: + +```solidity +contract MyToken is AccessControlDefaultAdminRules { + constructor() AccessControlDefaultAdminRules( + 3 days, + msg.sender // Explicit initial `DEFAULT_ADMIN_ROLE` holder + ) {} +} +``` + +
+

Functions

+
+- [constructor(initialDelay, initialDefaultAdmin)](#AccessControlDefaultAdminRules-constructor-uint48-address-) +- [supportsInterface(interfaceId)](#AccessControlDefaultAdminRules-supportsInterface-bytes4-) +- [owner()](#AccessControlDefaultAdminRules-owner--) +- [grantRole(role, account)](#AccessControlDefaultAdminRules-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControlDefaultAdminRules-revokeRole-bytes32-address-) +- [renounceRole(role, account)](#AccessControlDefaultAdminRules-renounceRole-bytes32-address-) +- [_grantRole(role, account)](#AccessControlDefaultAdminRules-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlDefaultAdminRules-_revokeRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControlDefaultAdminRules-_setRoleAdmin-bytes32-bytes32-) +- [defaultAdmin()](#AccessControlDefaultAdminRules-defaultAdmin--) +- [pendingDefaultAdmin()](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) +- [defaultAdminDelay()](#AccessControlDefaultAdminRules-defaultAdminDelay--) +- [pendingDefaultAdminDelay()](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) +- [defaultAdminDelayIncreaseWait()](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--) +- [beginDefaultAdminTransfer(newAdmin)](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) +- [_beginDefaultAdminTransfer(newAdmin)](#AccessControlDefaultAdminRules-_beginDefaultAdminTransfer-address-) +- [cancelDefaultAdminTransfer()](#AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--) +- [_cancelDefaultAdminTransfer()](#AccessControlDefaultAdminRules-_cancelDefaultAdminTransfer--) +- [acceptDefaultAdminTransfer()](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--) +- [_acceptDefaultAdminTransfer()](#AccessControlDefaultAdminRules-_acceptDefaultAdminTransfer--) +- [changeDefaultAdminDelay(newDelay)](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-) +- [_changeDefaultAdminDelay(newDelay)](#AccessControlDefaultAdminRules-_changeDefaultAdminDelay-uint48-) +- [rollbackDefaultAdminDelay()](#AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--) +- [_rollbackDefaultAdminDelay()](#AccessControlDefaultAdminRules-_rollbackDefaultAdminDelay--) +- [_delayChangeWait(newDelay)](#AccessControlDefaultAdminRules-_delayChangeWait-uint48-) +#### AccessControl [!toc] +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IERC5313 [!toc] +#### IAccessControlDefaultAdminRules [!toc] +#### IAccessControl [!toc] +
+
+ +
+

Events

+
+#### AccessControl [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IERC5313 [!toc] +#### IAccessControlDefaultAdminRules [!toc] +- [DefaultAdminTransferScheduled(newAdmin, acceptSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-) +- [DefaultAdminTransferCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--) +- [DefaultAdminDelayChangeScheduled(newDelay, effectSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-) +- [DefaultAdminDelayChangeCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--) +#### IAccessControl [!toc] +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+#### AccessControl [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IERC5313 [!toc] +#### IAccessControlDefaultAdminRules [!toc] +- [AccessControlInvalidDefaultAdmin(defaultAdmin)](#IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-) +- [AccessControlEnforcedDefaultAdminRules()](#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--) +- [AccessControlEnforcedDefaultAdminDelay(schedule)](#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-) +#### IAccessControl [!toc] +- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

constructor(uint48 initialDelay, address initialDefaultAdmin)

+
+

internal

+# +
+
+
+ +Sets the initial values for [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) and [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) address. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

owner() → address

+
+

public

+# +
+
+
+ +Gets the address of the owner. + +
+
+ + + +
+
+

grantRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +See [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-). Reverts for `DEFAULT_ADMIN_ROLE`. + +
+
+ + + +
+
+

revokeRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +See [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-). Reverts for `DEFAULT_ADMIN_ROLE`. + +
+
+ + + +
+
+

renounceRole(bytes32 role, address account)

+
+

public

+# +
+
+
+ +See [`AccessControl.renounceRole`](#AccessControl-renounceRole-bytes32-address-). + +For the `DEFAULT_ADMIN_ROLE`, it only allows renouncing in two steps by first calling +[`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) to the `address(0)`, so it's required that the [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) schedule +has also passed when calling this function. + +After its execution, it will not be possible to call `onlyRole(DEFAULT_ADMIN_ROLE)` functions. + + +Renouncing `DEFAULT_ADMIN_ROLE` will leave the contract without a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--), +thereby disabling any functionality that is only available for it, and the possibility of reassigning a +non-administrated role. + + +
+
+ + + +
+
+

_grantRole(bytes32 role, address account) → bool

+
+

internal

+# +
+
+
+ +See [`AccessControl._grantRole`](#AccessControl-_grantRole-bytes32-address-). + +For `DEFAULT_ADMIN_ROLE`, it only allows granting if there isn't already a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) or if the +role has been previously renounced. + + +Exposing this function through another mechanism may make the `DEFAULT_ADMIN_ROLE` +assignable again. Make sure to guarantee this is the expected behavior in your implementation. + + +
+
+ + + +
+
+

_revokeRole(bytes32 role, address account) → bool

+
+

internal

+# +
+
+
+ +Attempts to revoke `role` from `account` and returns a boolean indicating if `role` was revoked. + +Internal function without access restriction. + +May emit a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event. + +
+
+ + + +
+
+

_setRoleAdmin(bytes32 role, bytes32 adminRole)

+
+

internal

+# +
+
+
+ +See [`AccessControl._setRoleAdmin`](#AccessControl-_setRoleAdmin-bytes32-bytes32-). Reverts for `DEFAULT_ADMIN_ROLE`. + +
+
+ + + +
+
+

defaultAdmin() → address

+
+

public

+# +
+
+
+ +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + +
+
+ + + +
+
+

pendingDefaultAdmin() → address newAdmin, uint48 schedule

+
+

public

+# +
+
+
+ +Returns a tuple of a `newAdmin` and an accept schedule. + +After the `schedule` passes, the `newAdmin` will be able to accept the [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) role +by calling [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--), completing the role transfer. + +A zero value only in `acceptSchedule` indicates no pending admin transfer. + + +A zero address `newAdmin` means that [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) is being renounced. + + +
+
+ + + +
+
+

defaultAdminDelay() → uint48

+
+

public

+# +
+
+
+ +Returns the delay required to schedule the acceptance of a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started. + +This delay will be added to the current timestamp when calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) to set +the acceptance schedule. + + +If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this +function returns the new delay. See [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). + + +
+
+ + + +
+
+

pendingDefaultAdminDelay() → uint48 newDelay, uint48 schedule

+
+

public

+# +
+
+
+ +Returns a tuple of `newDelay` and an effect schedule. + +After the `schedule` passes, the `newDelay` will get into effect immediately for every +new [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A zero value only in `effectSchedule` indicates no pending delay change. + + +A zero value only for `newDelay` means that the next [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +will be zero after the effect schedule. + + +
+
+ + + +
+
+

defaultAdminDelayIncreaseWait() → uint48

+
+

public

+# +
+
+
+ +Maximum time in seconds for an increase to [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) (that is scheduled using [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-)) +to take effect. Default to 5 days. + +When the [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) is scheduled to be increased, it goes into effect after the new delay has passed with +the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) +that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can +be overridden for a custom [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) increase scheduling. + + +Make sure to add a reasonable amount of time while overriding this value, otherwise, +there's a risk of setting a high new delay that goes into effect almost immediately without the +possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + + +
+
+ + + +
+
+

beginDefaultAdminTransfer(address newAdmin)

+
+

public

+# +
+
+
+ +Starts a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) scheduled for acceptance +after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminRoleChangeStarted event. + +
+
+ + + +
+
+

_beginDefaultAdminTransfer(address newAdmin)

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +Internal function without access restriction. + +
+
+ + + +
+
+

cancelDefaultAdminTransfer()

+
+

public

+# +
+
+
+ +Cancels a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminTransferCanceled event. + +
+
+ + + +
+
+

_cancelDefaultAdminTransfer()

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.cancelDefaultAdminTransfer`](#AccessControlDefaultAdminRules-cancelDefaultAdminTransfer--). + +Internal function without access restriction. + +
+
+ + + +
+
+

acceptDefaultAdminTransfer()

+
+

public

+# +
+
+
+ +Completes a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` should be granted to the caller. +- `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. +- [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) should be reset to zero values. + +Requirements: + +- Only can be called by the [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `newAdmin`. +- The [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `acceptSchedule` should've passed. + +
+
+ + + +
+
+

_acceptDefaultAdminTransfer()

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--). + +Internal function without access restriction. + +
+
+ + + +
+
+

changeDefaultAdminDelay(uint48 newDelay)

+
+

public

+# +
+
+
+ +Initiates a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) update by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) scheduled for getting +into effect after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +This function guarantees that any call to [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) done between the timestamp this +method is called and the [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) effect schedule will use the current [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +set before calling. + +The [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--)'s effect schedule is defined in a way that waiting until the schedule and then +calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) with the new delay will take at least the same as another [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) +complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by +[`AccessControlDefaultAdminRules.defaultAdminDelayIncreaseWait`](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--). +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

_changeDefaultAdminDelay(uint48 newDelay)

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). + +Internal function without access restriction. + +
+
+ + + +
+
+

rollbackDefaultAdminDelay()

+
+

public

+# +
+
+
+ +Cancels a scheduled [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

_rollbackDefaultAdminDelay()

+
+

internal

+# +
+
+
+ +See [`AccessControlDefaultAdminRules.rollbackDefaultAdminDelay`](#AccessControlDefaultAdminRules-rollbackDefaultAdminDelay--). + +Internal function without access restriction. + +
+
+ + + +
+
+

_delayChangeWait(uint48 newDelay) → uint48

+
+

internal

+# +
+
+
+ +Returns the amount of seconds to wait after the `newDelay` will +become the new [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +The value returned guarantees that if the delay is reduced, it will go into effect +after a wait that honors the previously set delay. + +See [`AccessControlDefaultAdminRules.defaultAdminDelayIncreaseWait`](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--). + +
+
+ + + +
+ +## `AccessControlEnumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol"; +``` + +Extension of [`AccessControl`](#AccessControl) that allows enumerating the members of each role. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#AccessControlEnumerable-supportsInterface-bytes4-) +- [getRoleMember(role, index)](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#AccessControlEnumerable-getRoleMemberCount-bytes32-) +- [getRoleMembers(role)](#AccessControlEnumerable-getRoleMembers-bytes32-) +- [_grantRole(role, account)](#AccessControlEnumerable-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControlEnumerable-_revokeRole-bytes32-address-) +#### AccessControl [!toc] +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, callerConfirmation)](#AccessControl-renounceRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControlEnumerable [!toc] +#### IAccessControl [!toc] +
+
+ +
+

Events

+
+#### AccessControl [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControlEnumerable [!toc] +#### IAccessControl [!toc] +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+#### AccessControl [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControlEnumerable [!toc] +#### IAccessControl [!toc] +- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

getRoleMember(bytes32 role, uint256 index) → address

+
+

public

+# +
+
+
+ +Returns one of the accounts that have `role`. `index` must be a +value between 0 and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + + +When using [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), make sure +you perform all queries on the same block. See the following +[forum post](https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296) +for more information. + + +
+
+ + + +
+
+

getRoleMemberCount(bytes32 role) → uint256

+
+

public

+# +
+
+
+ +Returns the number of accounts that have `role`. Can be used +together with [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) to enumerate all bearers of a role. + +
+
+ + + +
+
+

getRoleMembers(bytes32 role) → address[]

+
+

public

+# +
+
+
+ +Return all accounts that have `role` + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

_grantRole(bytes32 role, address account) → bool

+
+

internal

+# +
+
+
+ +Overload [`AccessControl._grantRole`](#AccessControl-_grantRole-bytes32-address-) to track enumerable memberships + +
+
+ + + +
+
+

_revokeRole(bytes32 role, address account) → bool

+
+

internal

+# +
+
+
+ +Overload [`AccessControl._revokeRole`](#AccessControl-_revokeRole-bytes32-address-) to track enumerable memberships + +
+
+ + + +
+ +## `IAccessControlDefaultAdminRules` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/extensions/IAccessControlDefaultAdminRules.sol"; +``` + +External interface of AccessControlDefaultAdminRules declared to support ERC-165 detection. + +
+

Functions

+
+- [defaultAdmin()](#IAccessControlDefaultAdminRules-defaultAdmin--) +- [pendingDefaultAdmin()](#IAccessControlDefaultAdminRules-pendingDefaultAdmin--) +- [defaultAdminDelay()](#IAccessControlDefaultAdminRules-defaultAdminDelay--) +- [pendingDefaultAdminDelay()](#IAccessControlDefaultAdminRules-pendingDefaultAdminDelay--) +- [beginDefaultAdminTransfer(newAdmin)](#IAccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) +- [cancelDefaultAdminTransfer()](#IAccessControlDefaultAdminRules-cancelDefaultAdminTransfer--) +- [acceptDefaultAdminTransfer()](#IAccessControlDefaultAdminRules-acceptDefaultAdminTransfer--) +- [changeDefaultAdminDelay(newDelay)](#IAccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-) +- [rollbackDefaultAdminDelay()](#IAccessControlDefaultAdminRules-rollbackDefaultAdminDelay--) +- [defaultAdminDelayIncreaseWait()](#IAccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--) +#### IAccessControl [!toc] +- [hasRole(role, account)](#IAccessControl-hasRole-bytes32-address-) +- [getRoleAdmin(role)](#IAccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#IAccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#IAccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, callerConfirmation)](#IAccessControl-renounceRole-bytes32-address-) +
+
+ +
+

Events

+
+- [DefaultAdminTransferScheduled(newAdmin, acceptSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminTransferScheduled-address-uint48-) +- [DefaultAdminTransferCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminTransferCanceled--) +- [DefaultAdminDelayChangeScheduled(newDelay, effectSchedule)](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeScheduled-uint48-uint48-) +- [DefaultAdminDelayChangeCanceled()](#IAccessControlDefaultAdminRules-DefaultAdminDelayChangeCanceled--) +#### IAccessControl [!toc] +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+- [AccessControlInvalidDefaultAdmin(defaultAdmin)](#IAccessControlDefaultAdminRules-AccessControlInvalidDefaultAdmin-address-) +- [AccessControlEnforcedDefaultAdminRules()](#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminRules--) +- [AccessControlEnforcedDefaultAdminDelay(schedule)](#IAccessControlDefaultAdminRules-AccessControlEnforcedDefaultAdminDelay-uint48-) +#### IAccessControl [!toc] +- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

defaultAdmin() → address

+
+

external

+# +
+
+
+ +Returns the address of the current `DEFAULT_ADMIN_ROLE` holder. + +
+
+ + + +
+
+

pendingDefaultAdmin() → address newAdmin, uint48 acceptSchedule

+
+

external

+# +
+
+
+ +Returns a tuple of a `newAdmin` and an accept schedule. + +After the `schedule` passes, the `newAdmin` will be able to accept the [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) role +by calling [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--), completing the role transfer. + +A zero value only in `acceptSchedule` indicates no pending admin transfer. + + +A zero address `newAdmin` means that [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) is being renounced. + + +
+
+ + + +
+
+

defaultAdminDelay() → uint48

+
+

external

+# +
+
+
+ +Returns the delay required to schedule the acceptance of a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started. + +This delay will be added to the current timestamp when calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) to set +the acceptance schedule. + + +If a delay change has been scheduled, it will take effect as soon as the schedule passes, making this +function returns the new delay. See [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-). + + +
+
+ + + +
+
+

pendingDefaultAdminDelay() → uint48 newDelay, uint48 effectSchedule

+
+

external

+# +
+
+
+ +Returns a tuple of `newDelay` and an effect schedule. + +After the `schedule` passes, the `newDelay` will get into effect immediately for every +new [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A zero value only in `effectSchedule` indicates no pending delay change. + + +A zero value only for `newDelay` means that the next [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +will be zero after the effect schedule. + + +
+
+ + + +
+
+

beginDefaultAdminTransfer(address newAdmin)

+
+

external

+# +
+
+
+ +Starts a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) scheduled for acceptance +after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminRoleChangeStarted event. + +
+
+ + + +
+
+

cancelDefaultAdminTransfer()

+
+

external

+# +
+
+
+ +Cancels a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +A [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) not yet accepted can also be cancelled with this function. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminTransferCanceled event. + +
+
+ + + +
+
+

acceptDefaultAdminTransfer()

+
+

external

+# +
+
+
+ +Completes a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer previously started with [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-). + +After calling the function: + +- `DEFAULT_ADMIN_ROLE` should be granted to the caller. +- `DEFAULT_ADMIN_ROLE` should be revoked from the previous holder. +- [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) should be reset to zero values. + +Requirements: + +- Only can be called by the [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `newAdmin`. +- The [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--)'s `acceptSchedule` should've passed. + +
+
+ + + +
+
+

changeDefaultAdminDelay(uint48 newDelay)

+
+

external

+# +
+
+
+ +Initiates a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) update by setting a [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) scheduled for getting +into effect after the current timestamp plus a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--). + +This function guarantees that any call to [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) done between the timestamp this +method is called and the [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) effect schedule will use the current [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) +set before calling. + +The [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--)'s effect schedule is defined in a way that waiting until the schedule and then +calling [`AccessControlDefaultAdminRules.beginDefaultAdminTransfer`](#AccessControlDefaultAdminRules-beginDefaultAdminTransfer-address-) with the new delay will take at least the same as another [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) +complete transfer (including acceptance). + +The schedule is designed for two scenarios: + +- When the delay is changed for a larger one the schedule is `block.timestamp + newDelay` capped by +[`AccessControlDefaultAdminRules.defaultAdminDelayIncreaseWait`](#AccessControlDefaultAdminRules-defaultAdminDelayIncreaseWait--). +- When the delay is changed for a shorter one, the schedule is `block.timestamp + (current delay - new delay)`. + +A [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) that never got into effect will be canceled in favor of a new scheduled change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +Emits a DefaultAdminDelayChangeScheduled event and may emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

rollbackDefaultAdminDelay()

+
+

external

+# +
+
+
+ +Cancels a scheduled [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) change. + +Requirements: + +- Only can be called by the current [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--). + +May emit a DefaultAdminDelayChangeCanceled event. + +
+
+ + + +
+
+

defaultAdminDelayIncreaseWait() → uint48

+
+

external

+# +
+
+
+ +Maximum time in seconds for an increase to [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) (that is scheduled using [`AccessControlDefaultAdminRules.changeDefaultAdminDelay`](#AccessControlDefaultAdminRules-changeDefaultAdminDelay-uint48-)) +to take effect. Default to 5 days. + +When the [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) is scheduled to be increased, it goes into effect after the new delay has passed with +the purpose of giving enough time for reverting any accidental change (i.e. using milliseconds instead of seconds) +that may lock the contract. However, to avoid excessive schedules, the wait is capped by this function and it can +be overridden for a custom [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) increase scheduling. + + +Make sure to add a reasonable amount of time while overriding this value, otherwise, +there's a risk of setting a high new delay that goes into effect almost immediately without the +possibility of human intervention in the case of an input error (eg. set milliseconds instead of seconds). + + +
+
+ + + +
+
+

DefaultAdminTransferScheduled(address indexed newAdmin, uint48 acceptSchedule)

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) transfer is started, setting `newAdmin` as the next +address to become the [`AccessControlDefaultAdminRules.defaultAdmin`](#AccessControlDefaultAdminRules-defaultAdmin--) by calling [`AccessControlDefaultAdminRules.acceptDefaultAdminTransfer`](#AccessControlDefaultAdminRules-acceptDefaultAdminTransfer--) only after `acceptSchedule` +passes. + +
+
+ + +
+
+

DefaultAdminTransferCanceled()

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.pendingDefaultAdmin`](#AccessControlDefaultAdminRules-pendingDefaultAdmin--) is reset if it was never accepted, regardless of its schedule. + +
+
+ + +
+
+

DefaultAdminDelayChangeScheduled(uint48 newDelay, uint48 effectSchedule)

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.defaultAdminDelay`](#AccessControlDefaultAdminRules-defaultAdminDelay--) change is started, setting `newDelay` as the next +delay to be applied between default admin transfer after `effectSchedule` has passed. + +
+
+ + +
+
+

DefaultAdminDelayChangeCanceled()

+
+

event

+# +
+
+ +
+ +Emitted when a [`AccessControlDefaultAdminRules.pendingDefaultAdminDelay`](#AccessControlDefaultAdminRules-pendingDefaultAdminDelay--) is reset if its schedule didn't pass. + +
+
+ + + +
+
+

AccessControlInvalidDefaultAdmin(address defaultAdmin)

+
+

error

+# +
+
+
+ +The new default admin is not a valid default admin. + +
+
+ + + +
+
+

AccessControlEnforcedDefaultAdminRules()

+
+

error

+# +
+
+
+ +At least one of the following rules was violated: + +- The `DEFAULT_ADMIN_ROLE` must only be managed by itself. +- The `DEFAULT_ADMIN_ROLE` must only be held by one account at the time. +- Any `DEFAULT_ADMIN_ROLE` transfer must be in two delayed steps. + +
+
+ + + +
+
+

AccessControlEnforcedDefaultAdminDelay(uint48 schedule)

+
+

error

+# +
+
+
+ +The delay for transferring the default admin delay is enforced and +the operation must wait until `schedule`. + + +`schedule` can be 0 indicating there's no transfer scheduled. + + +
+
+ + + +
+ +## `IAccessControlEnumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/extensions/IAccessControlEnumerable.sol"; +``` + +External interface of AccessControlEnumerable declared to support ERC-165 detection. + +
+

Functions

+
+- [getRoleMember(role, index)](#IAccessControlEnumerable-getRoleMember-bytes32-uint256-) +- [getRoleMemberCount(role)](#IAccessControlEnumerable-getRoleMemberCount-bytes32-) +#### IAccessControl [!toc] +- [hasRole(role, account)](#IAccessControl-hasRole-bytes32-address-) +- [getRoleAdmin(role)](#IAccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#IAccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#IAccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, callerConfirmation)](#IAccessControl-renounceRole-bytes32-address-) +
+
+ +
+

Events

+
+#### IAccessControl [!toc] +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+#### IAccessControl [!toc] +- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

getRoleMember(bytes32 role, uint256 index) → address

+
+

external

+# +
+
+
+ +Returns one of the accounts that have `role`. `index` must be a +value between 0 and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), non-inclusive. + +Role bearers are not sorted in any particular way, and their ordering may +change at any point. + + +When using [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) and [`AccessControlEnumerable.getRoleMemberCount`](#AccessControlEnumerable-getRoleMemberCount-bytes32-), make sure +you perform all queries on the same block. See the following +[forum post](https://forum.openzeppelin.com/t/iterating-over-elements-on-enumerableset-in-openzeppelin-contracts/2296) +for more information. + + +
+
+ + + +
+
+

getRoleMemberCount(bytes32 role) → uint256

+
+

external

+# +
+
+
+ +Returns the number of accounts that have `role`. Can be used +together with [`AccessControlEnumerable.getRoleMember`](#AccessControlEnumerable-getRoleMember-bytes32-uint256-) to enumerate all bearers of a role. + +
+
+ + + +
+ +## `AccessManaged` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +``` + +This contract module makes available a [`AccessManaged.restricted`](#AccessManaged-restricted--) modifier. Functions decorated with this modifier will be +permissioned according to an "authority": a contract like [`AccessManager`](#AccessManager) that follows the [`IAuthority`](#IAuthority) interface, +implementing a policy that allows certain callers to access certain functions. + + +The `restricted` modifier should never be used on `internal` functions, judiciously used in `public` +functions, and ideally only used in `external` functions. See [`AccessManaged.restricted`](#AccessManaged-restricted--). + + +
+

Modifiers

+
+- [restricted()](#AccessManaged-restricted--) +
+
+ +
+

Functions

+
+- [constructor(initialAuthority)](#AccessManaged-constructor-address-) +- [authority()](#AccessManaged-authority--) +- [setAuthority(newAuthority)](#AccessManaged-setAuthority-address-) +- [isConsumingScheduledOp()](#AccessManaged-isConsumingScheduledOp--) +- [_setAuthority(newAuthority)](#AccessManaged-_setAuthority-address-) +- [_checkCanCall(caller, data)](#AccessManaged-_checkCanCall-address-bytes-) +#### IAccessManaged [!toc] +
+
+ +
+

Events

+
+#### IAccessManaged [!toc] +- [AuthorityUpdated(authority)](#IAccessManaged-AuthorityUpdated-address-) +
+
+ +
+

Errors

+
+#### IAccessManaged [!toc] +- [AccessManagedUnauthorized(caller)](#IAccessManaged-AccessManagedUnauthorized-address-) +- [AccessManagedRequiredDelay(caller, delay)](#IAccessManaged-AccessManagedRequiredDelay-address-uint32-) +- [AccessManagedInvalidAuthority(authority)](#IAccessManaged-AccessManagedInvalidAuthority-address-) +
+
+ + + +
+
+

restricted()

+
+

internal

+# +
+
+ +
+ +Restricts access to a function as defined by the connected Authority for this contract and the +caller and selector of the function that entered the contract. + + +In general, this modifier should only be used on `external` functions. It is okay to use it on `public` +functions that are used as external entry points and are not called internally. Unless you know what you're +doing, it should never be used on `internal` functions. Failure to follow these rules can have critical security +implications! This is because the permissions are determined by the function that entered the contract, i.e. the +function at the bottom of the call stack, and not the function where the modifier is visible in the source code. + + +Avoid adding this modifier to the [`receive()`](https://docs.soliditylang.org/en/v0.8.20/contracts.html#receive-ether-function) +function or the [`fallback()`](https://docs.soliditylang.org/en/v0.8.20/contracts.html#fallback-function). These +functions are the only execution paths where a function selector cannot be unambiguously determined from the calldata +since the selector defaults to `0x00000000` in the `receive()` function and similarly in the `fallback()` function +if no calldata is provided. (See [`AccessManaged._checkCanCall`](#AccessManaged-_checkCanCall-address-bytes-)). + +The `receive()` function will always panic whereas the `fallback()` may panic depending on the calldata length. + + +
+
+ + + +
+
+

constructor(address initialAuthority)

+
+

internal

+# +
+
+
+ +Initializes the contract connected to an initial authority. + +
+
+ + + +
+
+

authority() → address

+
+

public

+# +
+
+
+ +Returns the current authority. + +
+
+ + + +
+
+

setAuthority(address newAuthority)

+
+

public

+# +
+
+
+ +Transfers control to a new authority. The caller must be the current authority. + +
+
+ + + +
+
+

isConsumingScheduledOp() → bytes4

+
+

public

+# +
+
+
+ +Returns true only in the context of a delayed restricted call, at the moment that the scheduled operation is +being consumed. Prevents denial of service for delayed restricted calls in the case that the contract performs +attacker controlled calls. + +
+
+ + + +
+
+

_setAuthority(address newAuthority)

+
+

internal

+# +
+
+
+ +Transfers control to a new authority. Internal function with no access restriction. Allows bypassing the +permissions set by the current authority. + +
+
+ + + +
+
+

_checkCanCall(address caller, bytes data)

+
+

internal

+# +
+
+
+ +Reverts if the caller is not allowed to call the function identified by a selector. Panics if the calldata +is less than 4 bytes long. + +
+
+ + + +
+ +## `AccessManager` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/manager/AccessManager.sol"; +``` + +AccessManager is a central contract to store the permissions of a system. + +A smart contract under the control of an AccessManager instance is known as a target, and will inherit from the +[`AccessManaged`](#AccessManaged) contract, be connected to this contract as its manager and implement the [`AccessManaged.restricted`](#AccessManaged-restricted--) +modifier on a set of functions selected to be permissioned. Note that any function without this setup won't be +effectively restricted. + +The restriction rules for such functions are defined in terms of "roles" identified by an `uint64` and scoped +by target (`address`) and function selectors (`bytes4`). These roles are stored in this contract and can be +configured by admins (`ADMIN_ROLE` members) after a delay (see [`AccessManager.getTargetAdminDelay`](#AccessManager-getTargetAdminDelay-address-)). + +For each target contract, admins can configure the following without any delay: + +* The target's [`AccessManaged.authority`](#AccessManaged-authority--) via [`AccessManager.updateAuthority`](#AccessManager-updateAuthority-address-address-). +* Close or open a target via [`AccessManager.setTargetClosed`](#AccessManager-setTargetClosed-address-bool-) keeping the permissions intact. +* The roles that are allowed (or disallowed) to call a given function (identified by its selector) through [`AccessManager.setTargetFunctionRole`](#AccessManager-setTargetFunctionRole-address-bytes4---uint64-). + +By default every address is member of the `PUBLIC_ROLE` and every target function is restricted to the `ADMIN_ROLE` until configured otherwise. +Additionally, each role has the following configuration options restricted to this manager's admins: + +* A role's admin role via [`AccessManager.setRoleAdmin`](#AccessManager-setRoleAdmin-uint64-uint64-) who can grant or revoke roles. +* A role's guardian role via [`AccessManager.setRoleGuardian`](#AccessManager-setRoleGuardian-uint64-uint64-) who's allowed to cancel operations. +* A delay in which a role takes effect after being granted through [`AccessManager.setGrantDelay`](#AccessManager-setGrantDelay-uint64-uint32-). +* A delay of any target's admin action via [`AccessManager.setTargetAdminDelay`](#AccessManager-setTargetAdminDelay-address-uint32-). +* A role label for discoverability purposes with [`AccessManager.labelRole`](#AccessManager-labelRole-uint64-string-). + +Any account can be added and removed into any number of these roles by using the [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) and [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-) functions +restricted to each role's admin (see [`AccessControl.getRoleAdmin`](#AccessControl-getRoleAdmin-bytes32-)). + +Since all the permissions of the managed system can be modified by the admins of this instance, it is expected that +they will be highly secured (e.g., a multisig or a well-configured DAO). + + +This contract implements a form of the [`IAuthority`](#IAuthority) interface, but [`AccessManager.canCall`](#AccessManager-canCall-address-address-bytes4-) has additional return data so it +doesn't inherit `IAuthority`. It is however compatible with the `IAuthority` interface since the first 32 bytes of +the return data are a boolean as expected by that interface. + + + +Systems that implement other access control mechanisms (for example using [`Ownable`](#Ownable)) can be paired with an +[`AccessManager`](#AccessManager) by transferring permissions (ownership in the case of [`Ownable`](#Ownable)) directly to the [`AccessManager`](#AccessManager). +Users will be able to interact with these contracts through the [`AccessManager.execute`](#AccessManager-execute-address-bytes-) function, following the access rules +registered in the [`AccessManager`](#AccessManager). Keep in mind that in that context, the msg.sender seen by restricted functions +will be [`AccessManager`](#AccessManager) itself. + + + +When granting permissions over an [`Ownable`](#Ownable) or [`AccessControl`](#AccessControl) contract to an [`AccessManager`](#AccessManager), be very +mindful of the danger associated with functions such as [`Ownable.renounceOwnership`](#Ownable-renounceOwnership--) or +[`AccessControl.renounceRole`](#AccessControl-renounceRole-bytes32-address-). + + +
+

Modifiers

+
+- [onlyAuthorized()](#AccessManager-onlyAuthorized--) +
+
+ +
+

Functions

+
+- [constructor(initialAdmin)](#AccessManager-constructor-address-) +- [canCall(caller, target, selector)](#AccessManager-canCall-address-address-bytes4-) +- [expiration()](#AccessManager-expiration--) +- [minSetback()](#AccessManager-minSetback--) +- [isTargetClosed(target)](#AccessManager-isTargetClosed-address-) +- [getTargetFunctionRole(target, selector)](#AccessManager-getTargetFunctionRole-address-bytes4-) +- [getTargetAdminDelay(target)](#AccessManager-getTargetAdminDelay-address-) +- [getRoleAdmin(roleId)](#AccessManager-getRoleAdmin-uint64-) +- [getRoleGuardian(roleId)](#AccessManager-getRoleGuardian-uint64-) +- [getRoleGrantDelay(roleId)](#AccessManager-getRoleGrantDelay-uint64-) +- [getAccess(roleId, account)](#AccessManager-getAccess-uint64-address-) +- [hasRole(roleId, account)](#AccessManager-hasRole-uint64-address-) +- [labelRole(roleId, label)](#AccessManager-labelRole-uint64-string-) +- [grantRole(roleId, account, executionDelay)](#AccessManager-grantRole-uint64-address-uint32-) +- [revokeRole(roleId, account)](#AccessManager-revokeRole-uint64-address-) +- [renounceRole(roleId, callerConfirmation)](#AccessManager-renounceRole-uint64-address-) +- [setRoleAdmin(roleId, admin)](#AccessManager-setRoleAdmin-uint64-uint64-) +- [setRoleGuardian(roleId, guardian)](#AccessManager-setRoleGuardian-uint64-uint64-) +- [setGrantDelay(roleId, newDelay)](#AccessManager-setGrantDelay-uint64-uint32-) +- [_grantRole(roleId, account, grantDelay, executionDelay)](#AccessManager-_grantRole-uint64-address-uint32-uint32-) +- [_revokeRole(roleId, account)](#AccessManager-_revokeRole-uint64-address-) +- [_setRoleAdmin(roleId, admin)](#AccessManager-_setRoleAdmin-uint64-uint64-) +- [_setRoleGuardian(roleId, guardian)](#AccessManager-_setRoleGuardian-uint64-uint64-) +- [_setGrantDelay(roleId, newDelay)](#AccessManager-_setGrantDelay-uint64-uint32-) +- [setTargetFunctionRole(target, selectors, roleId)](#AccessManager-setTargetFunctionRole-address-bytes4---uint64-) +- [_setTargetFunctionRole(target, selector, roleId)](#AccessManager-_setTargetFunctionRole-address-bytes4-uint64-) +- [setTargetAdminDelay(target, newDelay)](#AccessManager-setTargetAdminDelay-address-uint32-) +- [_setTargetAdminDelay(target, newDelay)](#AccessManager-_setTargetAdminDelay-address-uint32-) +- [setTargetClosed(target, closed)](#AccessManager-setTargetClosed-address-bool-) +- [_setTargetClosed(target, closed)](#AccessManager-_setTargetClosed-address-bool-) +- [getSchedule(id)](#AccessManager-getSchedule-bytes32-) +- [getNonce(id)](#AccessManager-getNonce-bytes32-) +- [schedule(target, data, when)](#AccessManager-schedule-address-bytes-uint48-) +- [execute(target, data)](#AccessManager-execute-address-bytes-) +- [cancel(caller, target, data)](#AccessManager-cancel-address-address-bytes-) +- [consumeScheduledOp(caller, data)](#AccessManager-consumeScheduledOp-address-bytes-) +- [_consumeScheduledOp(operationId)](#AccessManager-_consumeScheduledOp-bytes32-) +- [hashOperation(caller, target, data)](#AccessManager-hashOperation-address-address-bytes-) +- [updateAuthority(target, newAuthority)](#AccessManager-updateAuthority-address-address-) +- [ADMIN_ROLE()](#AccessManager-ADMIN_ROLE-uint64) +- [PUBLIC_ROLE()](#AccessManager-PUBLIC_ROLE-uint64) +#### IAccessManager [!toc] +#### Multicall [!toc] +- [multicall(data)](#Multicall-multicall-bytes---) +
+
+ +
+

Events

+
+#### IAccessManager [!toc] +- [OperationScheduled(operationId, nonce, schedule, caller, target, data)](#IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes-) +- [OperationExecuted(operationId, nonce)](#IAccessManager-OperationExecuted-bytes32-uint32-) +- [OperationCanceled(operationId, nonce)](#IAccessManager-OperationCanceled-bytes32-uint32-) +- [RoleLabel(roleId, label)](#IAccessManager-RoleLabel-uint64-string-) +- [RoleGranted(roleId, account, delay, since, newMember)](#IAccessManager-RoleGranted-uint64-address-uint32-uint48-bool-) +- [RoleRevoked(roleId, account)](#IAccessManager-RoleRevoked-uint64-address-) +- [RoleAdminChanged(roleId, admin)](#IAccessManager-RoleAdminChanged-uint64-uint64-) +- [RoleGuardianChanged(roleId, guardian)](#IAccessManager-RoleGuardianChanged-uint64-uint64-) +- [RoleGrantDelayChanged(roleId, delay, since)](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) +- [TargetClosed(target, closed)](#IAccessManager-TargetClosed-address-bool-) +- [TargetFunctionRoleUpdated(target, selector, roleId)](#IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-) +- [TargetAdminDelayUpdated(target, delay, since)](#IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-) +#### Multicall [!toc] +
+
+ +
+

Errors

+
+#### IAccessManager [!toc] +- [AccessManagerAlreadyScheduled(operationId)](#IAccessManager-AccessManagerAlreadyScheduled-bytes32-) +- [AccessManagerNotScheduled(operationId)](#IAccessManager-AccessManagerNotScheduled-bytes32-) +- [AccessManagerNotReady(operationId)](#IAccessManager-AccessManagerNotReady-bytes32-) +- [AccessManagerExpired(operationId)](#IAccessManager-AccessManagerExpired-bytes32-) +- [AccessManagerLockedRole(roleId)](#IAccessManager-AccessManagerLockedRole-uint64-) +- [AccessManagerBadConfirmation()](#IAccessManager-AccessManagerBadConfirmation--) +- [AccessManagerUnauthorizedAccount(msgsender, roleId)](#IAccessManager-AccessManagerUnauthorizedAccount-address-uint64-) +- [AccessManagerUnauthorizedCall(caller, target, selector)](#IAccessManager-AccessManagerUnauthorizedCall-address-address-bytes4-) +- [AccessManagerUnauthorizedConsume(target)](#IAccessManager-AccessManagerUnauthorizedConsume-address-) +- [AccessManagerUnauthorizedCancel(msgsender, caller, target, selector)](#IAccessManager-AccessManagerUnauthorizedCancel-address-address-address-bytes4-) +- [AccessManagerInvalidInitialAdmin(initialAdmin)](#IAccessManager-AccessManagerInvalidInitialAdmin-address-) +#### Multicall [!toc] +
+
+ + + +
+
+

onlyAuthorized()

+
+

internal

+# +
+
+ +
+ +Check that the caller is authorized to perform the operation. +See [`AccessManager`](#AccessManager) description for a detailed breakdown of the authorization logic. + +
+
+ + + +
+
+

constructor(address initialAdmin)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

canCall(address caller, address target, bytes4 selector) → bool immediate, uint32 delay

+
+

public

+# +
+
+
+ +Check if an address (`caller`) is authorised to call a given function on a given contract directly (with +no restriction). Additionally, it returns the delay needed to perform the call indirectly through the [`AccessManager.schedule`](#AccessManager-schedule-address-bytes-uint48-) +& [`AccessManager.execute`](#AccessManager-execute-address-bytes-) workflow. + +This function is usually called by the targeted contract to control immediate execution of restricted functions. +Therefore we only return true if the call can be performed without any delay. If the call is subject to a +previously set delay (not zero), then the function should return false and the caller should schedule the operation +for future execution. + +If `allowed` is true, the delay can be disregarded and the operation can be immediately executed, otherwise +the operation can be executed if and only if delay is greater than 0. + + +The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that +is backward compatible. Some contracts may thus ignore the second return argument. In that case they will fail +to identify the indirect workflow, and will consider calls that require a delay to be forbidden. + + + +This function does not report the permissions of the admin functions in the manager itself. These are defined by the +[`AccessManager`](#AccessManager) documentation. + + +
+
+ + + +
+
+

expiration() → uint32

+
+

public

+# +
+
+
+ +Expiration delay for scheduled proposals. Defaults to 1 week. + + +Avoid overriding the expiration with 0. Otherwise every contract proposal will be expired immediately, +disabling any scheduling usage. + + +
+
+ + + +
+
+

minSetback() → uint32

+
+

public

+# +
+
+
+ +Minimum setback for all delay updates, with the exception of execution delays. It +can be increased without setback (and reset via [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-) in the event of an +accidental increase). Defaults to 5 days. + +
+
+ + + +
+
+

isTargetClosed(address target) → bool

+
+

public

+# +
+
+
+ +Get whether the contract is closed disabling any access. Otherwise role permissions are applied. + + +When the manager itself is closed, admin functions are still accessible to avoid locking the contract. + + +
+
+ + + +
+
+

getTargetFunctionRole(address target, bytes4 selector) → uint64

+
+

public

+# +
+
+
+ +Get the role required to call a function. + +
+
+ + + +
+
+

getTargetAdminDelay(address target) → uint32

+
+

public

+# +
+
+
+ +Get the admin delay for a target contract. Changes to contract configuration are subject to this delay. + +
+
+ + + +
+
+

getRoleAdmin(uint64 roleId) → uint64

+
+

public

+# +
+
+
+ +Get the id of the role that acts as an admin for the given role. + +The admin permission is required to grant the role, revoke the role and update the execution delay to execute +an operation that is restricted to this role. + +
+
+ + + +
+
+

getRoleGuardian(uint64 roleId) → uint64

+
+

public

+# +
+
+
+ +Get the role that acts as a guardian for a given role. + +The guardian permission allows canceling operations that have been scheduled under the role. + +
+
+ + + +
+
+

getRoleGrantDelay(uint64 roleId) → uint32

+
+

public

+# +
+
+
+ +Get the role current grant delay. + +Its value may change at any point without an event emitted following a call to [`AccessManager.setGrantDelay`](#AccessManager-setGrantDelay-uint64-uint32-). +Changes to this value, including effect timepoint are notified in advance by the [`IAccessManager.RoleGrantDelayChanged`](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) event. + +
+
+ + + +
+
+

getAccess(uint64 roleId, address account) → uint48 since, uint32 currentDelay, uint32 pendingDelay, uint48 effect

+
+

public

+# +
+
+
+ +Get the access details for a given account for a given role. These details include the timepoint at which +membership becomes active, and the delay applied to all operations by this user that requires this permission +level. + +Returns: +[0] Timestamp at which the account membership becomes valid. 0 means role is not granted. +[1] Current execution delay for the account. +[2] Pending execution delay for the account. +[3] Timestamp at which the pending execution delay will become active. 0 means no delay update is scheduled. + +
+
+ + + +
+
+

hasRole(uint64 roleId, address account) → bool isMember, uint32 executionDelay

+
+

public

+# +
+
+
+ +Check if a given account currently has the permission level corresponding to a given role. Note that this +permission might be associated with an execution delay. [`AccessManager.getAccess`](#AccessManager-getAccess-uint64-address-) can provide more details. + +
+
+ + + +
+
+

labelRole(uint64 roleId, string label)

+
+

public

+# +
+
+
+ +Give a label to a role, for improved role discoverability by UIs. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.RoleLabel`](#IAccessManager-RoleLabel-uint64-string-) event. + +
+
+ + + +
+
+

grantRole(uint64 roleId, address account, uint32 executionDelay)

+
+

public

+# +
+
+
+ +Add `account` to `roleId`, or change its execution delay. + +This gives the account the authorization to call any function that is restricted to this role. An optional +execution delay (in seconds) can be set. If that delay is non 0, the user is required to schedule any operation +that is restricted to members of this role. The user will only be able to execute the operation after the delay has +passed, before it has expired. During this period, admin and guardians can cancel the operation (see [`AccessManager.cancel`](#AccessManager-cancel-address-address-bytes-)). + +If the account has already been granted this role, the execution delay will be updated. This update is not +immediate and follows the delay rules. For example, if a user currently has a delay of 3 hours, and this is +called to reduce that delay to 1 hour, the new delay will take some time to take effect, enforcing that any +operation executed in the 3 hours that follows this update was indeed scheduled before this update. + +Requirements: + +- the caller must be an admin for the role (see [`AccessControl.getRoleAdmin`](#AccessControl-getRoleAdmin-bytes32-)) +- granted role must not be the `PUBLIC_ROLE` + +Emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

revokeRole(uint64 roleId, address account)

+
+

public

+# +
+
+
+ +Remove an account from a role, with immediate effect. If the account does not have the role, this call has +no effect. + +Requirements: + +- the caller must be an admin for the role (see [`AccessControl.getRoleAdmin`](#AccessControl-getRoleAdmin-bytes32-)) +- revoked role must not be the `PUBLIC_ROLE` + +Emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event if the account had the role. + +
+
+ + + +
+
+

renounceRole(uint64 roleId, address callerConfirmation)

+
+

public

+# +
+
+
+ +Renounce role permissions for the calling account with immediate effect. If the sender is not in +the role this call has no effect. + +Requirements: + +- the caller must be `callerConfirmation`. + +Emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event if the account had the role. + +
+
+ + + +
+
+

setRoleAdmin(uint64 roleId, uint64 admin)

+
+

public

+# +
+
+
+ +Change admin role for a given role. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) event + +
+
+ + + +
+
+

setRoleGuardian(uint64 roleId, uint64 guardian)

+
+

public

+# +
+
+
+ +Change guardian role for a given role. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.RoleGuardianChanged`](#IAccessManager-RoleGuardianChanged-uint64-uint64-) event + +
+
+ + + +
+
+

setGrantDelay(uint64 roleId, uint32 newDelay)

+
+

public

+# +
+
+
+ +Update the delay for granting a `roleId`. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.RoleGrantDelayChanged`](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) event. + +
+
+ + + +
+
+

_grantRole(uint64 roleId, address account, uint32 grantDelay, uint32 executionDelay) → bool

+
+

internal

+# +
+
+
+ +Internal version of [`AccessControl.grantRole`](#AccessControl-grantRole-bytes32-address-) without access control. Returns true if the role was newly granted. + +Emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

_revokeRole(uint64 roleId, address account) → bool

+
+

internal

+# +
+
+
+ +Internal version of [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-) without access control. This logic is also used by [`AccessControl.renounceRole`](#AccessControl-renounceRole-bytes32-address-). +Returns true if the role was previously granted. + +Emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event if the account had the role. + +
+
+ + + +
+
+

_setRoleAdmin(uint64 roleId, uint64 admin)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManager.setRoleAdmin`](#AccessManager-setRoleAdmin-uint64-uint64-) without access control. + +Emits a [`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) event. + + +Setting the admin role as the `PUBLIC_ROLE` is allowed, but it will effectively allow +anyone to set grant or revoke such role. + + +
+
+ + + +
+
+

_setRoleGuardian(uint64 roleId, uint64 guardian)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManager.setRoleGuardian`](#AccessManager-setRoleGuardian-uint64-uint64-) without access control. + +Emits a [`IAccessManager.RoleGuardianChanged`](#IAccessManager-RoleGuardianChanged-uint64-uint64-) event. + + +Setting the guardian role as the `PUBLIC_ROLE` is allowed, but it will effectively allow +anyone to cancel any scheduled operation for such role. + + +
+
+ + + +
+
+

_setGrantDelay(uint64 roleId, uint32 newDelay)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManager.setGrantDelay`](#AccessManager-setGrantDelay-uint64-uint32-) without access control. + +Emits a [`IAccessManager.RoleGrantDelayChanged`](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) event. + +
+
+ + + +
+
+

setTargetFunctionRole(address target, bytes4[] selectors, uint64 roleId)

+
+

public

+# +
+
+
+ +Set the role required to call functions identified by the `selectors` in the `target` contract. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.TargetFunctionRoleUpdated`](#IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-) event per selector. + +
+
+ + + +
+
+

_setTargetFunctionRole(address target, bytes4 selector, uint64 roleId)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManager.setTargetFunctionRole`](#AccessManager-setTargetFunctionRole-address-bytes4---uint64-) without access control. + +Emits a [`IAccessManager.TargetFunctionRoleUpdated`](#IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-) event. + +
+
+ + + +
+
+

setTargetAdminDelay(address target, uint32 newDelay)

+
+

public

+# +
+
+
+ +Set the delay for changing the configuration of a given target contract. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.TargetAdminDelayUpdated`](#IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-) event. + +
+
+ + + +
+
+

_setTargetAdminDelay(address target, uint32 newDelay)

+
+

internal

+# +
+
+
+ +Internal version of [`AccessManager.setTargetAdminDelay`](#AccessManager-setTargetAdminDelay-address-uint32-) without access control. + +Emits a [`IAccessManager.TargetAdminDelayUpdated`](#IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-) event. + +
+
+ + + +
+
+

setTargetClosed(address target, bool closed)

+
+

public

+# +
+
+
+ +Set the closed flag for a contract. + +Closing the manager itself won't disable access to admin methods to avoid locking the contract. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.TargetClosed`](#IAccessManager-TargetClosed-address-bool-) event. + +
+
+ + + +
+
+

_setTargetClosed(address target, bool closed)

+
+

internal

+# +
+
+
+ +Set the closed flag for a contract. This is an internal setter with no access restrictions. + +Emits a [`IAccessManager.TargetClosed`](#IAccessManager-TargetClosed-address-bool-) event. + +
+
+ + + +
+
+

getSchedule(bytes32 id) → uint48

+
+

public

+# +
+
+
+ +Return the timepoint at which a scheduled operation will be ready for execution. This returns 0 if the +operation is not yet scheduled, has expired, was executed, or was canceled. + +
+
+ + + +
+
+

getNonce(bytes32 id) → uint32

+
+

public

+# +
+
+
+ +Return the nonce for the latest scheduled operation with a given id. Returns 0 if the operation has never +been scheduled. + +
+
+ + + +
+
+

schedule(address target, bytes data, uint48 when) → bytes32 operationId, uint32 nonce

+
+

public

+# +
+
+
+ +Schedule a delayed operation for future execution, and return the operation identifier. It is possible to +choose the timestamp at which the operation becomes executable as long as it satisfies the execution delays +required for the caller. The special value zero will automatically set the earliest possible time. + +Returns the `operationId` that was scheduled. Since this value is a hash of the parameters, it can reoccur when +the same parameters are used; if this is relevant, the returned `nonce` can be used to uniquely identify this +scheduled operation from other occurrences of the same `operationId` in invocations of [`AccessManager.execute`](#AccessManager-execute-address-bytes-) and [`AccessManager.cancel`](#AccessManager-cancel-address-address-bytes-). + +Emits a [`IAccessManager.OperationScheduled`](#IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes-) event. + + +It is not possible to concurrently schedule more than one operation with the same `target` and `data`. If +this is necessary, a random byte can be appended to `data` to act as a salt that will be ignored by the target +contract if it is using standard Solidity ABI encoding. + + +
+
+ + + +
+
+

execute(address target, bytes data) → uint32

+
+

public

+# +
+
+
+ +Execute a function that is delay restricted, provided it was properly scheduled beforehand, or the +execution delay is 0. + +Returns the nonce that identifies the previously scheduled operation that is executed, or 0 if the +operation wasn't previously scheduled (if the caller doesn't have an execution delay). + +Emits an [`IAccessManager.OperationExecuted`](#IAccessManager-OperationExecuted-bytes32-uint32-) event only if the call was scheduled and delayed. + +
+
+ + + +
+
+

cancel(address caller, address target, bytes data) → uint32

+
+

public

+# +
+
+
+ +Cancel a scheduled (delayed) operation. Returns the nonce that identifies the previously scheduled +operation that is cancelled. + +Requirements: + +- the caller must be the proposer, a guardian of the targeted function, or a global admin + +Emits a [`IAccessManager.OperationCanceled`](#IAccessManager-OperationCanceled-bytes32-uint32-) event. + +
+
+ + + +
+
+

consumeScheduledOp(address caller, bytes data)

+
+

public

+# +
+
+
+ +Consume a scheduled operation targeting the caller. If such an operation exists, mark it as consumed +(emit an [`IAccessManager.OperationExecuted`](#IAccessManager-OperationExecuted-bytes32-uint32-) event and clean the state). Otherwise, throw an error. + +This is useful for contracts that want to enforce that calls targeting them were scheduled on the manager, +with all the verifications that it implies. + +Emit a [`IAccessManager.OperationExecuted`](#IAccessManager-OperationExecuted-bytes32-uint32-) event. + +
+
+ + + +
+
+

_consumeScheduledOp(bytes32 operationId) → uint32

+
+

internal

+# +
+
+
+ +Internal variant of [`AccessManager.consumeScheduledOp`](#AccessManager-consumeScheduledOp-address-bytes-) that operates on bytes32 operationId. + +Returns the nonce of the scheduled operation that is consumed. + +
+
+ + + +
+
+

hashOperation(address caller, address target, bytes data) → bytes32

+
+

public

+# +
+
+
+ +Hashing function for delayed operations. + +
+
+ + + +
+
+

updateAuthority(address target, address newAuthority)

+
+

public

+# +
+
+
+ +Changes the authority of a target managed by this manager instance. + +Requirements: + +- the caller must be a global admin + +
+
+ + + +
+
+

ADMIN_ROLE() → uint64

+
+

public

+# +
+
+
+ +The identifier of the admin role. Required to perform most configuration operations including +other roles' management and target restrictions. + +
+
+ + + +
+
+

PUBLIC_ROLE() → uint64

+
+

public

+# +
+
+
+ +The identifier of the public role. Automatically granted to all addresses with no delay. + +
+
+ + + +
+ +## `AuthorityUtils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/manager/AuthorityUtils.sol"; +``` + +
+

Functions

+
+- [canCallWithDelay(authority, caller, target, selector)](#AuthorityUtils-canCallWithDelay-address-address-address-bytes4-) +
+
+ + + +
+
+

canCallWithDelay(address authority, address caller, address target, bytes4 selector) → bool immediate, uint32 delay

+
+

internal

+# +
+
+
+ +Since `AccessManager` implements an extended IAuthority interface, invoking `canCall` with backwards compatibility +for the preexisting `IAuthority` interface requires special care to avoid reverting on insufficient return data. +This helper function takes care of invoking `canCall` in a backwards compatible way without reverting. + +
+
+ + + +
+ +## `IAccessManaged` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/manager/IAccessManaged.sol"; +``` + +
+

Functions

+
+- [authority()](#IAccessManaged-authority--) +- [setAuthority()](#IAccessManaged-setAuthority-address-) +- [isConsumingScheduledOp()](#IAccessManaged-isConsumingScheduledOp--) +
+
+ +
+

Events

+
+- [AuthorityUpdated(authority)](#IAccessManaged-AuthorityUpdated-address-) +
+
+ +
+

Errors

+
+- [AccessManagedUnauthorized(caller)](#IAccessManaged-AccessManagedUnauthorized-address-) +- [AccessManagedRequiredDelay(caller, delay)](#IAccessManaged-AccessManagedRequiredDelay-address-uint32-) +- [AccessManagedInvalidAuthority(authority)](#IAccessManaged-AccessManagedInvalidAuthority-address-) +
+
+ + + +
+
+

authority() → address

+
+

external

+# +
+
+
+ +Returns the current authority. + +
+
+ + + +
+
+

setAuthority(address)

+
+

external

+# +
+
+
+ +Transfers control to a new authority. The caller must be the current authority. + +
+
+ + + +
+
+

isConsumingScheduledOp() → bytes4

+
+

external

+# +
+
+
+ +Returns true only in the context of a delayed restricted call, at the moment that the scheduled operation is +being consumed. Prevents denial of service for delayed restricted calls in the case that the contract performs +attacker controlled calls. + +
+
+ + + +
+
+

AuthorityUpdated(address authority)

+
+

event

+# +
+
+ +
+ +Authority that manages this contract was updated. + +
+
+ + + +
+
+

AccessManagedUnauthorized(address caller)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagedRequiredDelay(address caller, uint32 delay)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagedInvalidAuthority(address authority)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `IAccessManager` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/manager/IAccessManager.sol"; +``` + +
+

Functions

+
+- [canCall(caller, target, selector)](#IAccessManager-canCall-address-address-bytes4-) +- [expiration()](#IAccessManager-expiration--) +- [minSetback()](#IAccessManager-minSetback--) +- [isTargetClosed(target)](#IAccessManager-isTargetClosed-address-) +- [getTargetFunctionRole(target, selector)](#IAccessManager-getTargetFunctionRole-address-bytes4-) +- [getTargetAdminDelay(target)](#IAccessManager-getTargetAdminDelay-address-) +- [getRoleAdmin(roleId)](#IAccessManager-getRoleAdmin-uint64-) +- [getRoleGuardian(roleId)](#IAccessManager-getRoleGuardian-uint64-) +- [getRoleGrantDelay(roleId)](#IAccessManager-getRoleGrantDelay-uint64-) +- [getAccess(roleId, account)](#IAccessManager-getAccess-uint64-address-) +- [hasRole(roleId, account)](#IAccessManager-hasRole-uint64-address-) +- [labelRole(roleId, label)](#IAccessManager-labelRole-uint64-string-) +- [grantRole(roleId, account, executionDelay)](#IAccessManager-grantRole-uint64-address-uint32-) +- [revokeRole(roleId, account)](#IAccessManager-revokeRole-uint64-address-) +- [renounceRole(roleId, callerConfirmation)](#IAccessManager-renounceRole-uint64-address-) +- [setRoleAdmin(roleId, admin)](#IAccessManager-setRoleAdmin-uint64-uint64-) +- [setRoleGuardian(roleId, guardian)](#IAccessManager-setRoleGuardian-uint64-uint64-) +- [setGrantDelay(roleId, newDelay)](#IAccessManager-setGrantDelay-uint64-uint32-) +- [setTargetFunctionRole(target, selectors, roleId)](#IAccessManager-setTargetFunctionRole-address-bytes4---uint64-) +- [setTargetAdminDelay(target, newDelay)](#IAccessManager-setTargetAdminDelay-address-uint32-) +- [setTargetClosed(target, closed)](#IAccessManager-setTargetClosed-address-bool-) +- [getSchedule(id)](#IAccessManager-getSchedule-bytes32-) +- [getNonce(id)](#IAccessManager-getNonce-bytes32-) +- [schedule(target, data, when)](#IAccessManager-schedule-address-bytes-uint48-) +- [execute(target, data)](#IAccessManager-execute-address-bytes-) +- [cancel(caller, target, data)](#IAccessManager-cancel-address-address-bytes-) +- [consumeScheduledOp(caller, data)](#IAccessManager-consumeScheduledOp-address-bytes-) +- [hashOperation(caller, target, data)](#IAccessManager-hashOperation-address-address-bytes-) +- [updateAuthority(target, newAuthority)](#IAccessManager-updateAuthority-address-address-) +
+
+ +
+

Events

+
+- [OperationScheduled(operationId, nonce, schedule, caller, target, data)](#IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes-) +- [OperationExecuted(operationId, nonce)](#IAccessManager-OperationExecuted-bytes32-uint32-) +- [OperationCanceled(operationId, nonce)](#IAccessManager-OperationCanceled-bytes32-uint32-) +- [RoleLabel(roleId, label)](#IAccessManager-RoleLabel-uint64-string-) +- [RoleGranted(roleId, account, delay, since, newMember)](#IAccessManager-RoleGranted-uint64-address-uint32-uint48-bool-) +- [RoleRevoked(roleId, account)](#IAccessManager-RoleRevoked-uint64-address-) +- [RoleAdminChanged(roleId, admin)](#IAccessManager-RoleAdminChanged-uint64-uint64-) +- [RoleGuardianChanged(roleId, guardian)](#IAccessManager-RoleGuardianChanged-uint64-uint64-) +- [RoleGrantDelayChanged(roleId, delay, since)](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) +- [TargetClosed(target, closed)](#IAccessManager-TargetClosed-address-bool-) +- [TargetFunctionRoleUpdated(target, selector, roleId)](#IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-) +- [TargetAdminDelayUpdated(target, delay, since)](#IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-) +
+
+ +
+

Errors

+
+- [AccessManagerAlreadyScheduled(operationId)](#IAccessManager-AccessManagerAlreadyScheduled-bytes32-) +- [AccessManagerNotScheduled(operationId)](#IAccessManager-AccessManagerNotScheduled-bytes32-) +- [AccessManagerNotReady(operationId)](#IAccessManager-AccessManagerNotReady-bytes32-) +- [AccessManagerExpired(operationId)](#IAccessManager-AccessManagerExpired-bytes32-) +- [AccessManagerLockedRole(roleId)](#IAccessManager-AccessManagerLockedRole-uint64-) +- [AccessManagerBadConfirmation()](#IAccessManager-AccessManagerBadConfirmation--) +- [AccessManagerUnauthorizedAccount(msgsender, roleId)](#IAccessManager-AccessManagerUnauthorizedAccount-address-uint64-) +- [AccessManagerUnauthorizedCall(caller, target, selector)](#IAccessManager-AccessManagerUnauthorizedCall-address-address-bytes4-) +- [AccessManagerUnauthorizedConsume(target)](#IAccessManager-AccessManagerUnauthorizedConsume-address-) +- [AccessManagerUnauthorizedCancel(msgsender, caller, target, selector)](#IAccessManager-AccessManagerUnauthorizedCancel-address-address-address-bytes4-) +- [AccessManagerInvalidInitialAdmin(initialAdmin)](#IAccessManager-AccessManagerInvalidInitialAdmin-address-) +
+
+ + + +
+
+

canCall(address caller, address target, bytes4 selector) → bool allowed, uint32 delay

+
+

external

+# +
+
+
+ +Check if an address (`caller`) is authorised to call a given function on a given contract directly (with +no restriction). Additionally, it returns the delay needed to perform the call indirectly through the [`AccessManager.schedule`](#AccessManager-schedule-address-bytes-uint48-) +& [`AccessManager.execute`](#AccessManager-execute-address-bytes-) workflow. + +This function is usually called by the targeted contract to control immediate execution of restricted functions. +Therefore we only return true if the call can be performed without any delay. If the call is subject to a +previously set delay (not zero), then the function should return false and the caller should schedule the operation +for future execution. + +If `allowed` is true, the delay can be disregarded and the operation can be immediately executed, otherwise +the operation can be executed if and only if delay is greater than 0. + + +The IAuthority interface does not include the `uint32` delay. This is an extension of that interface that +is backward compatible. Some contracts may thus ignore the second return argument. In that case they will fail +to identify the indirect workflow, and will consider calls that require a delay to be forbidden. + + + +This function does not report the permissions of the admin functions in the manager itself. These are defined by the +[`AccessManager`](#AccessManager) documentation. + + +
+
+ + + +
+
+

expiration() → uint32

+
+

external

+# +
+
+
+ +Expiration delay for scheduled proposals. Defaults to 1 week. + + +Avoid overriding the expiration with 0. Otherwise every contract proposal will be expired immediately, +disabling any scheduling usage. + + +
+
+ + + +
+
+

minSetback() → uint32

+
+

external

+# +
+
+
+ +Minimum setback for all delay updates, with the exception of execution delays. It +can be increased without setback (and reset via [`AccessControl.revokeRole`](#AccessControl-revokeRole-bytes32-address-) in the event of an +accidental increase). Defaults to 5 days. + +
+
+ + + +
+
+

isTargetClosed(address target) → bool

+
+

external

+# +
+
+
+ +Get whether the contract is closed disabling any access. Otherwise role permissions are applied. + + +When the manager itself is closed, admin functions are still accessible to avoid locking the contract. + + +
+
+ + + +
+
+

getTargetFunctionRole(address target, bytes4 selector) → uint64

+
+

external

+# +
+
+
+ +Get the role required to call a function. + +
+
+ + + +
+
+

getTargetAdminDelay(address target) → uint32

+
+

external

+# +
+
+
+ +Get the admin delay for a target contract. Changes to contract configuration are subject to this delay. + +
+
+ + + +
+
+

getRoleAdmin(uint64 roleId) → uint64

+
+

external

+# +
+
+
+ +Get the id of the role that acts as an admin for the given role. + +The admin permission is required to grant the role, revoke the role and update the execution delay to execute +an operation that is restricted to this role. + +
+
+ + + +
+
+

getRoleGuardian(uint64 roleId) → uint64

+
+

external

+# +
+
+
+ +Get the role that acts as a guardian for a given role. + +The guardian permission allows canceling operations that have been scheduled under the role. + +
+
+ + + +
+
+

getRoleGrantDelay(uint64 roleId) → uint32

+
+

external

+# +
+
+
+ +Get the role current grant delay. + +Its value may change at any point without an event emitted following a call to [`AccessManager.setGrantDelay`](#AccessManager-setGrantDelay-uint64-uint32-). +Changes to this value, including effect timepoint are notified in advance by the [`IAccessManager.RoleGrantDelayChanged`](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) event. + +
+
+ + + +
+
+

getAccess(uint64 roleId, address account) → uint48 since, uint32 currentDelay, uint32 pendingDelay, uint48 effect

+
+

external

+# +
+
+
+ +Get the access details for a given account for a given role. These details include the timepoint at which +membership becomes active, and the delay applied to all operations by this user that requires this permission +level. + +Returns: +[0] Timestamp at which the account membership becomes valid. 0 means role is not granted. +[1] Current execution delay for the account. +[2] Pending execution delay for the account. +[3] Timestamp at which the pending execution delay will become active. 0 means no delay update is scheduled. + +
+
+ + + +
+
+

hasRole(uint64 roleId, address account) → bool isMember, uint32 executionDelay

+
+

external

+# +
+
+
+ +Check if a given account currently has the permission level corresponding to a given role. Note that this +permission might be associated with an execution delay. [`AccessManager.getAccess`](#AccessManager-getAccess-uint64-address-) can provide more details. + +
+
+ + + +
+
+

labelRole(uint64 roleId, string label)

+
+

external

+# +
+
+
+ +Give a label to a role, for improved role discoverability by UIs. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.RoleLabel`](#IAccessManager-RoleLabel-uint64-string-) event. + +
+
+ + + +
+
+

grantRole(uint64 roleId, address account, uint32 executionDelay)

+
+

external

+# +
+
+
+ +Add `account` to `roleId`, or change its execution delay. + +This gives the account the authorization to call any function that is restricted to this role. An optional +execution delay (in seconds) can be set. If that delay is non 0, the user is required to schedule any operation +that is restricted to members of this role. The user will only be able to execute the operation after the delay has +passed, before it has expired. During this period, admin and guardians can cancel the operation (see [`AccessManager.cancel`](#AccessManager-cancel-address-address-bytes-)). + +If the account has already been granted this role, the execution delay will be updated. This update is not +immediate and follows the delay rules. For example, if a user currently has a delay of 3 hours, and this is +called to reduce that delay to 1 hour, the new delay will take some time to take effect, enforcing that any +operation executed in the 3 hours that follows this update was indeed scheduled before this update. + +Requirements: + +- the caller must be an admin for the role (see [`AccessControl.getRoleAdmin`](#AccessControl-getRoleAdmin-bytes32-)) +- granted role must not be the `PUBLIC_ROLE` + +Emits a [`IAccessControl.RoleGranted`](#IAccessControl-RoleGranted-bytes32-address-address-) event. + +
+
+ + + +
+
+

revokeRole(uint64 roleId, address account)

+
+

external

+# +
+
+
+ +Remove an account from a role, with immediate effect. If the account does not have the role, this call has +no effect. + +Requirements: + +- the caller must be an admin for the role (see [`AccessControl.getRoleAdmin`](#AccessControl-getRoleAdmin-bytes32-)) +- revoked role must not be the `PUBLIC_ROLE` + +Emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event if the account had the role. + +
+
+ + + +
+
+

renounceRole(uint64 roleId, address callerConfirmation)

+
+

external

+# +
+
+
+ +Renounce role permissions for the calling account with immediate effect. If the sender is not in +the role this call has no effect. + +Requirements: + +- the caller must be `callerConfirmation`. + +Emits a [`IAccessControl.RoleRevoked`](#IAccessControl-RoleRevoked-bytes32-address-address-) event if the account had the role. + +
+
+ + + +
+
+

setRoleAdmin(uint64 roleId, uint64 admin)

+
+

external

+# +
+
+
+ +Change admin role for a given role. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessControl.RoleAdminChanged`](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) event + +
+
+ + + +
+
+

setRoleGuardian(uint64 roleId, uint64 guardian)

+
+

external

+# +
+
+
+ +Change guardian role for a given role. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.RoleGuardianChanged`](#IAccessManager-RoleGuardianChanged-uint64-uint64-) event + +
+
+ + + +
+
+

setGrantDelay(uint64 roleId, uint32 newDelay)

+
+

external

+# +
+
+
+ +Update the delay for granting a `roleId`. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.RoleGrantDelayChanged`](#IAccessManager-RoleGrantDelayChanged-uint64-uint32-uint48-) event. + +
+
+ + + +
+
+

setTargetFunctionRole(address target, bytes4[] selectors, uint64 roleId)

+
+

external

+# +
+
+
+ +Set the role required to call functions identified by the `selectors` in the `target` contract. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.TargetFunctionRoleUpdated`](#IAccessManager-TargetFunctionRoleUpdated-address-bytes4-uint64-) event per selector. + +
+
+ + + +
+
+

setTargetAdminDelay(address target, uint32 newDelay)

+
+

external

+# +
+
+
+ +Set the delay for changing the configuration of a given target contract. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.TargetAdminDelayUpdated`](#IAccessManager-TargetAdminDelayUpdated-address-uint32-uint48-) event. + +
+
+ + + +
+
+

setTargetClosed(address target, bool closed)

+
+

external

+# +
+
+
+ +Set the closed flag for a contract. + +Closing the manager itself won't disable access to admin methods to avoid locking the contract. + +Requirements: + +- the caller must be a global admin + +Emits a [`IAccessManager.TargetClosed`](#IAccessManager-TargetClosed-address-bool-) event. + +
+
+ + + +
+
+

getSchedule(bytes32 id) → uint48

+
+

external

+# +
+
+
+ +Return the timepoint at which a scheduled operation will be ready for execution. This returns 0 if the +operation is not yet scheduled, has expired, was executed, or was canceled. + +
+
+ + + +
+
+

getNonce(bytes32 id) → uint32

+
+

external

+# +
+
+
+ +Return the nonce for the latest scheduled operation with a given id. Returns 0 if the operation has never +been scheduled. + +
+
+ + + +
+
+

schedule(address target, bytes data, uint48 when) → bytes32 operationId, uint32 nonce

+
+

external

+# +
+
+
+ +Schedule a delayed operation for future execution, and return the operation identifier. It is possible to +choose the timestamp at which the operation becomes executable as long as it satisfies the execution delays +required for the caller. The special value zero will automatically set the earliest possible time. + +Returns the `operationId` that was scheduled. Since this value is a hash of the parameters, it can reoccur when +the same parameters are used; if this is relevant, the returned `nonce` can be used to uniquely identify this +scheduled operation from other occurrences of the same `operationId` in invocations of [`AccessManager.execute`](#AccessManager-execute-address-bytes-) and [`AccessManager.cancel`](#AccessManager-cancel-address-address-bytes-). + +Emits a [`IAccessManager.OperationScheduled`](#IAccessManager-OperationScheduled-bytes32-uint32-uint48-address-address-bytes-) event. + + +It is not possible to concurrently schedule more than one operation with the same `target` and `data`. If +this is necessary, a random byte can be appended to `data` to act as a salt that will be ignored by the target +contract if it is using standard Solidity ABI encoding. + + +
+
+ + + +
+
+

execute(address target, bytes data) → uint32

+
+

external

+# +
+
+
+ +Execute a function that is delay restricted, provided it was properly scheduled beforehand, or the +execution delay is 0. + +Returns the nonce that identifies the previously scheduled operation that is executed, or 0 if the +operation wasn't previously scheduled (if the caller doesn't have an execution delay). + +Emits an [`IAccessManager.OperationExecuted`](#IAccessManager-OperationExecuted-bytes32-uint32-) event only if the call was scheduled and delayed. + +
+
+ + + +
+
+

cancel(address caller, address target, bytes data) → uint32

+
+

external

+# +
+
+
+ +Cancel a scheduled (delayed) operation. Returns the nonce that identifies the previously scheduled +operation that is cancelled. + +Requirements: + +- the caller must be the proposer, a guardian of the targeted function, or a global admin + +Emits a [`IAccessManager.OperationCanceled`](#IAccessManager-OperationCanceled-bytes32-uint32-) event. + +
+
+ + + +
+
+

consumeScheduledOp(address caller, bytes data)

+
+

external

+# +
+
+
+ +Consume a scheduled operation targeting the caller. If such an operation exists, mark it as consumed +(emit an [`IAccessManager.OperationExecuted`](#IAccessManager-OperationExecuted-bytes32-uint32-) event and clean the state). Otherwise, throw an error. + +This is useful for contracts that want to enforce that calls targeting them were scheduled on the manager, +with all the verifications that it implies. + +Emit a [`IAccessManager.OperationExecuted`](#IAccessManager-OperationExecuted-bytes32-uint32-) event. + +
+
+ + + +
+
+

hashOperation(address caller, address target, bytes data) → bytes32

+
+

external

+# +
+
+
+ +Hashing function for delayed operations. + +
+
+ + + +
+
+

updateAuthority(address target, address newAuthority)

+
+

external

+# +
+
+
+ +Changes the authority of a target managed by this manager instance. + +Requirements: + +- the caller must be a global admin + +
+
+ + + +
+
+

OperationScheduled(bytes32 indexed operationId, uint32 indexed nonce, uint48 schedule, address caller, address target, bytes data)

+
+

event

+# +
+
+ +
+ +A delayed operation was scheduled. + +
+
+ + +
+
+

OperationExecuted(bytes32 indexed operationId, uint32 indexed nonce)

+
+

event

+# +
+
+ +
+ +A scheduled operation was executed. + +
+
+ + +
+
+

OperationCanceled(bytes32 indexed operationId, uint32 indexed nonce)

+
+

event

+# +
+
+ +
+ +A scheduled operation was canceled. + +
+
+ + +
+
+

RoleLabel(uint64 indexed roleId, string label)

+
+

event

+# +
+
+ +
+ +Informational labelling for a roleId. + +
+
+ + +
+
+

RoleGranted(uint64 indexed roleId, address indexed account, uint32 delay, uint48 since, bool newMember)

+
+

event

+# +
+
+ +
+ +Emitted when `account` is granted `roleId`. + + +The meaning of the `since` argument depends on the `newMember` argument. +If the role is granted to a new member, the `since` argument indicates when the account becomes a member of the role, +otherwise it indicates the execution delay for this account and roleId is updated. + + +
+
+ + +
+
+

RoleRevoked(uint64 indexed roleId, address indexed account)

+
+

event

+# +
+
+ +
+ +Emitted when `account` membership or `roleId` is revoked. Unlike granting, revoking is instantaneous. + +
+
+ + +
+
+

RoleAdminChanged(uint64 indexed roleId, uint64 indexed admin)

+
+

event

+# +
+
+ +
+ +Role acting as admin over a given `roleId` is updated. + +
+
+ + +
+
+

RoleGuardianChanged(uint64 indexed roleId, uint64 indexed guardian)

+
+

event

+# +
+
+ +
+ +Role acting as guardian over a given `roleId` is updated. + +
+
+ + +
+
+

RoleGrantDelayChanged(uint64 indexed roleId, uint32 delay, uint48 since)

+
+

event

+# +
+
+ +
+ +Grant delay for a given `roleId` will be updated to `delay` when `since` is reached. + +
+
+ + +
+
+

TargetClosed(address indexed target, bool closed)

+
+

event

+# +
+
+ +
+ +Target mode is updated (true = closed, false = open). + +
+
+ + +
+
+

TargetFunctionRoleUpdated(address indexed target, bytes4 selector, uint64 indexed roleId)

+
+

event

+# +
+
+ +
+ +Role required to invoke `selector` on `target` is updated to `roleId`. + +
+
+ + +
+
+

TargetAdminDelayUpdated(address indexed target, uint32 delay, uint48 since)

+
+

event

+# +
+
+ +
+ +Admin delay for a given `target` will be updated to `delay` when `since` is reached. + +
+
+ + + +
+
+

AccessManagerAlreadyScheduled(bytes32 operationId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerNotScheduled(bytes32 operationId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerNotReady(bytes32 operationId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerExpired(bytes32 operationId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerLockedRole(uint64 roleId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerBadConfirmation()

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerUnauthorizedAccount(address msgsender, uint64 roleId)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerUnauthorizedCall(address caller, address target, bytes4 selector)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerUnauthorizedConsume(address target)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerUnauthorizedCancel(address msgsender, address caller, address target, bytes4 selector)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

AccessManagerInvalidInitialAdmin(address initialAdmin)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `IAuthority` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/access/manager/IAuthority.sol"; +``` + +Standard interface for permissioning originally defined in Dappsys. + +
+

Functions

+
+- [canCall(caller, target, selector)](#IAuthority-canCall-address-address-bytes4-) +
+
+ + + +
+
+

canCall(address caller, address target, bytes4 selector) → bool allowed

+
+

external

+# +
+
+
+ +Returns true if the caller can invoke on a target the function identified by a function selector. + +
+
+ diff --git a/docs/content/contracts/5.x/api/account.mdx b/docs/content/contracts/5.x/api/account.mdx new file mode 100644 index 00000000..4946701b --- /dev/null +++ b/docs/content/contracts/5.x/api/account.mdx @@ -0,0 +1,2422 @@ +--- +title: "Account" +description: "Smart contract account utilities and implementations" +--- + +This directory includes contracts to build accounts for ERC-4337. These include: + +* [`Account`](#Account): An ERC-4337 smart account implementation that includes the core logic to process user operations. +* [`AccountERC7579`](#AccountERC7579): An extension of `Account` that implements support for ERC-7579 modules. +* [`AccountERC7579Hooked`](#AccountERC7579Hooked): An extension of `AccountERC7579` with support for a single hook module (type 4). +* [`ERC7821`](#ERC7821): Minimal batch executor implementation contracts. Useful to enable easy batch execution for smart contracts. +* [`ERC4337Utils`](#ERC4337Utils): Utility functions for working with ERC-4337 user operations. +* [`ERC7579Utils`](#ERC7579Utils): Utility functions for working with ERC-7579 modules and account modularity. + +## Core + +[`Account`](#Account) + +## Extensions + +[`AccountERC7579`](#AccountERC7579) + +[`AccountERC7579Hooked`](#AccountERC7579Hooked) + +[`ERC7821`](#ERC7821) + +## Utilities + +[`ERC4337Utils`](#ERC4337Utils) + +[`ERC7579Utils`](#ERC7579Utils) + + + +
+ +## `Account` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/Account.sol"; +``` + +A simple ERC4337 account implementation. This base implementation only includes the minimal logic to process +user operations. + +Developers must implement the [`AbstractSigner._rawSignatureValidation`](/contracts/5.x/api/utils/cryptography#AbstractSigner-_rawSignatureValidation-bytes32-bytes-) function to define the account's validation logic. + + +This core account doesn't include any mechanism for performing arbitrary external calls. This is an essential +feature that all Account should have. We leave it up to the developers to implement the mechanism of their choice. +Common choices include ERC-6900, ERC-7579 and ERC-7821 (among others). + + + +Implementing a mechanism to validate signatures is a security-sensitive operation as it may allow an +attacker to bypass the account's security measures. Check out [`SignerECDSA`](/contracts/5.x/api/utils/cryptography#SignerECDSA), [`SignerP256`](/contracts/5.x/api/utils/cryptography#SignerP256), or [`SignerRSA`](/contracts/5.x/api/utils/cryptography#SignerRSA) for +digital signature validation implementations. + + +@custom:stateless + +
+

Modifiers

+
+- [onlyEntryPointOrSelf()](#Account-onlyEntryPointOrSelf--) +- [onlyEntryPoint()](#Account-onlyEntryPoint--) +
+
+ +
+

Functions

+
+- [entryPoint()](#Account-entryPoint--) +- [getNonce()](#Account-getNonce--) +- [getNonce(key)](#Account-getNonce-uint192-) +- [validateUserOp(userOp, userOpHash, missingAccountFunds)](#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_validateUserOp(userOp, userOpHash, signature)](#Account-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-) +- [_signableUserOpHash(, userOpHash)](#Account-_signableUserOpHash-struct-PackedUserOperation-bytes32-) +- [_payPrefund(missingAccountFunds)](#Account-_payPrefund-uint256-) +- [_checkEntryPoint()](#Account-_checkEntryPoint--) +- [_checkEntryPointOrSelf()](#Account-_checkEntryPointOrSelf--) +- [receive()](#Account-receive--) +#### IAccount [!toc] +#### AbstractSigner [!toc] +- [_rawSignatureValidation(hash, signature)](#AbstractSigner-_rawSignatureValidation-bytes32-bytes-) +
+
+ +
+

Errors

+
+- [AccountUnauthorized(sender)](#Account-AccountUnauthorized-address-) +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ + + +
+
+

onlyEntryPointOrSelf()

+
+

internal

+# +
+
+ +
+ +Revert if the caller is not the entry point or the account itself. + +
+
+ + + +
+
+

onlyEntryPoint()

+
+

internal

+# +
+
+ +
+ +Revert if the caller is not the entry point. + +
+
+ + + +
+
+

entryPoint() → contract IEntryPoint

+
+

public

+# +
+
+
+ +Canonical entry point for the account that forwards and validates user operations. + +
+
+ + + +
+
+

getNonce() → uint256

+
+

public

+# +
+
+
+ +Return the account nonce for the canonical sequence. + +
+
+ + + +
+
+

getNonce(uint192 key) → uint256

+
+

public

+# +
+
+
+ +Return the account nonce for a given sequence (key). + +
+
+ + + +
+
+

validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 missingAccountFunds) → uint256

+
+

public

+# +
+
+
+ +Validates a user operation. + +* MUST validate the caller is a trusted EntryPoint +* MUST validate that the signature is a valid signature of the userOpHash, and SHOULD + return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. +* MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might + be zero, in case the current account’s deposit is high enough) + +Returns an encoded packed validation data that is composed of the following elements: + +- `authorizer` (`address`): 0 for success, 1 for failure, otherwise the address of an authorizer contract +- `validUntil` (`uint48`): The UserOp is valid only up to this time. Zero for “infinite”. +- `validAfter` (`uint48`): The UserOp is valid only after this time. + +
+
+ + + +
+
+

_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, bytes signature) → uint256

+
+

internal

+# +
+
+
+ +Returns the validationData for a given user operation. By default, this checks the signature of the +signable hash (produced by [`Account._signableUserOpHash`](#Account-_signableUserOpHash-struct-PackedUserOperation-bytes32-)) using the abstract signer ([`AbstractSigner._rawSignatureValidation`](/contracts/5.x/api/utils/cryptography#AbstractSigner-_rawSignatureValidation-bytes32-bytes-)). + +The `signature` parameter is taken directly from the user operation's `signature` field. +This design enables derived contracts to implement custom signature handling logic, +such as embedding additional data within the signature and processing it by overriding this function +and optionally invoking `super`. + + +The userOpHash is assumed to be correct. Calling this function with a userOpHash that does not match the +userOp will result in undefined behavior. + + +
+
+ + + +
+
+

_signableUserOpHash(struct PackedUserOperation, bytes32 userOpHash) → bytes32

+
+

internal

+# +
+
+
+ +Virtual function that returns the signable hash for a user operations. Since v0.8.0 of the entrypoint, +`userOpHash` is an EIP-712 hash that can be signed directly. + +
+
+ + + +
+
+

_payPrefund(uint256 missingAccountFunds)

+
+

internal

+# +
+
+
+ +Sends the missing funds for executing the user operation to the `entrypoint`. +The `missingAccountFunds` must be defined by the entrypoint when calling [`Account.validateUserOp`](#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-). + +
+
+ + + +
+
+

_checkEntryPoint()

+
+

internal

+# +
+
+
+ +Ensures the caller is the `entrypoint`. + +
+
+ + + +
+
+

_checkEntryPointOrSelf()

+
+

internal

+# +
+
+
+ +Ensures the caller is the `entrypoint` or the account itself. + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +Receive Ether. + +
+
+ + + +
+
+

AccountUnauthorized(address sender)

+
+

error

+# +
+
+
+ +Unauthorized call to the account. + +
+
+ + + +
+ +## `AccountERC7579` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol"; +``` + +Extension of [`Account`](#Account) that implements support for ERC-7579 modules. + +To comply with the ERC-1271 support requirement, this contract defers signature validation to +installed validator modules by calling [`IERC7579Validator.isValidSignatureWithSender`](/contracts/5.x/api/interfaces#IERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-). + +This contract does not implement validation logic for user operations since this functionality +is often delegated to self-contained validation modules. Developers must install a validator module +upon initialization (or any other mechanism to enable execution from the account): + +```solidity +contract MyAccountERC7579 is AccountERC7579, Initializable { + function initializeAccount(address validator, bytes calldata validatorData) public initializer { + _installModule(MODULE_TYPE_VALIDATOR, validator, validatorData); + } +} +``` + + +* Hook support is not included. See [`AccountERC7579Hooked`](#AccountERC7579Hooked) for a version that hooks to execution. +* Validator selection, when verifying either ERC-1271 signature or ERC-4337 UserOperation is implemented in + internal virtual functions [`AccountERC7579._extractUserOpValidator`](#AccountERC7579-_extractUserOpValidator-struct-PackedUserOperation-) and [`AccountERC7579._extractSignatureValidator`](#AccountERC7579-_extractSignatureValidator-bytes-). Both are implemented + following common practices. However, this part is not standardized in ERC-7579 (or in any follow-up ERC). Some + accounts may want to override these internal functions. +* When combined with [`ERC7739`](/contracts/5.x/api/utils/cryptography#ERC7739), resolution ordering of [`AccountERC7579.isValidSignature`](#AccountERC7579-isValidSignature-bytes32-bytes-) may have an impact ([`ERC7739`](/contracts/5.x/api/utils/cryptography#ERC7739) does not + call super). Manual resolution might be necessary. +* Static calls (using callType `0xfe`) are currently NOT supported. + + +Removing all validator modules will render the account inoperable, as no user operations can be validated thereafter. + + +
+

Modifiers

+
+- [onlyModule(moduleTypeId, additionalContext)](#AccountERC7579-onlyModule-uint256-bytes-) +
+
+ +
+

Functions

+
+- [fallback()](#AccountERC7579-fallback-bytes-) +- [accountId()](#AccountERC7579-accountId--) +- [supportsExecutionMode(encodedMode)](#AccountERC7579-supportsExecutionMode-bytes32-) +- [supportsModule(moduleTypeId)](#AccountERC7579-supportsModule-uint256-) +- [installModule(moduleTypeId, module, initData)](#AccountERC7579-installModule-uint256-address-bytes-) +- [uninstallModule(moduleTypeId, module, deInitData)](#AccountERC7579-uninstallModule-uint256-address-bytes-) +- [isModuleInstalled(moduleTypeId, module, additionalContext)](#AccountERC7579-isModuleInstalled-uint256-address-bytes-) +- [execute(mode, executionCalldata)](#AccountERC7579-execute-bytes32-bytes-) +- [executeFromExecutor(mode, executionCalldata)](#AccountERC7579-executeFromExecutor-bytes32-bytes-) +- [isValidSignature(hash, signature)](#AccountERC7579-isValidSignature-bytes32-bytes-) +- [_validateUserOp(userOp, userOpHash, signature)](#AccountERC7579-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-) +- [_execute(mode, executionCalldata)](#AccountERC7579-_execute-Mode-bytes-) +- [_installModule(moduleTypeId, module, initData)](#AccountERC7579-_installModule-uint256-address-bytes-) +- [_uninstallModule(moduleTypeId, module, deInitData)](#AccountERC7579-_uninstallModule-uint256-address-bytes-) +- [_fallback()](#AccountERC7579-_fallback--) +- [_fallbackHandler(selector)](#AccountERC7579-_fallbackHandler-bytes4-) +- [_checkModule(moduleTypeId, module, additionalContext)](#AccountERC7579-_checkModule-uint256-address-bytes-) +- [_extractUserOpValidator(userOp)](#AccountERC7579-_extractUserOpValidator-struct-PackedUserOperation-) +- [_extractSignatureValidator(signature)](#AccountERC7579-_extractSignatureValidator-bytes-) +- [_decodeFallbackData(data)](#AccountERC7579-_decodeFallbackData-bytes-) +- [_rawSignatureValidation(, )](#AccountERC7579-_rawSignatureValidation-bytes32-bytes-) +#### IERC7579ModuleConfig [!toc] +#### IERC7579AccountConfig [!toc] +#### IERC7579Execution [!toc] +#### IERC1271 [!toc] +#### Account [!toc] +- [entryPoint()](#Account-entryPoint--) +- [getNonce()](#Account-getNonce--) +- [getNonce(key)](#Account-getNonce-uint192-) +- [validateUserOp(userOp, userOpHash, missingAccountFunds)](#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_signableUserOpHash(, userOpHash)](#Account-_signableUserOpHash-struct-PackedUserOperation-bytes32-) +- [_payPrefund(missingAccountFunds)](#Account-_payPrefund-uint256-) +- [_checkEntryPoint()](#Account-_checkEntryPoint--) +- [_checkEntryPointOrSelf()](#Account-_checkEntryPointOrSelf--) +- [receive()](#Account-receive--) +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ +
+

Events

+
+#### IERC7579ModuleConfig [!toc] +- [ModuleInstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleInstalled-uint256-address-) +- [ModuleUninstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleUninstalled-uint256-address-) +#### IERC7579AccountConfig [!toc] +#### IERC7579Execution [!toc] +#### IERC1271 [!toc] +#### Account [!toc] +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+- [ERC7579MissingFallbackHandler(selector)](#AccountERC7579-ERC7579MissingFallbackHandler-bytes4-) +- [ERC7579CannotDecodeFallbackData()](#AccountERC7579-ERC7579CannotDecodeFallbackData--) +#### IERC7579ModuleConfig [!toc] +#### IERC7579AccountConfig [!toc] +#### IERC7579Execution [!toc] +#### IERC1271 [!toc] +#### Account [!toc] +- [AccountUnauthorized(sender)](#Account-AccountUnauthorized-address-) +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ + + +
+
+

onlyModule(uint256 moduleTypeId, bytes additionalContext)

+
+

internal

+# +
+
+ +
+ +Modifier that checks if the caller is an installed module of the given type. + +
+
+ + + +
+
+

fallback(bytes) → bytes

+
+

external

+# +
+
+
+ +See [`AccountERC7579._fallback`](#AccountERC7579-_fallback--). + +
+
+ + + +
+
+

accountId() → string

+
+

public

+# +
+
+
+ +Returns the account id of the smart account + +
+
+ + + +
+
+

supportsExecutionMode(bytes32 encodedMode) → bool

+
+

public

+# +
+
+
+ +Supported call types: +* Single (`0x00`): A single transaction execution. +* Batch (`0x01`): A batch of transactions execution. +* Delegate (`0xff`): A delegate call execution. + +Supported exec types: +* Default (`0x00`): Default execution type (revert on failure). +* Try (`0x01`): Try execution type (emits ERC7579TryExecuteFail on failure). + +
+
+ + + +
+
+

supportsModule(uint256 moduleTypeId) → bool

+
+

public

+# +
+
+
+ +Supported module types: + +* Validator: A module used during the validation phase to determine if a transaction is valid and +should be executed on the account. +* Executor: A module that can execute transactions on behalf of the smart account via a callback. +* Fallback Handler: A module that can extend the fallback functionality of a smart account. + +
+
+ + + +
+
+

installModule(uint256 moduleTypeId, address module, bytes initData)

+
+

public

+# +
+
+
+ +Installs a Module of a certain type on the smart account + +
+
+ + + +
+
+

uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)

+
+

public

+# +
+
+
+ +Uninstalls a Module of a certain type on the smart account + +
+
+ + + +
+
+

isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) → bool

+
+

public

+# +
+
+
+ +Returns whether a module is installed on the smart account + +
+
+ + + +
+
+

execute(bytes32 mode, bytes executionCalldata)

+
+

public

+# +
+
+
+ +Executes a transaction on behalf of the account. + +
+
+ + + +
+
+

executeFromExecutor(bytes32 mode, bytes executionCalldata) → bytes[] returnData

+
+

public

+# +
+
+
+ +Executes a transaction on behalf of the account. + This function is intended to be called by Executor Modules + +
+
+ + + +
+
+

isValidSignature(bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +Implement ERC-1271 through IERC7579Validator modules. If module based validation fails, fallback to +"native" validation by the abstract signer. + + +when combined with [`ERC7739`](/contracts/5.x/api/utils/cryptography#ERC7739), resolution ordering may have an impact ([`ERC7739`](/contracts/5.x/api/utils/cryptography#ERC7739) does not call super). +Manual resolution might be necessary. + + +
+
+ + + +
+
+

_validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, bytes signature) → uint256

+
+

internal

+# +
+
+
+ +Validates a user operation with [`Account._signableUserOpHash`](#Account-_signableUserOpHash-struct-PackedUserOperation-bytes32-) and returns the validation data +if the module specified by the first 20 bytes of the nonce key is installed. Falls back to +[`Account._validateUserOp`](#Account-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-) otherwise. + +See [`AccountERC7579._extractUserOpValidator`](#AccountERC7579-_extractUserOpValidator-struct-PackedUserOperation-) for the module extraction logic. + +
+
+ + + +
+
+

_execute(Mode mode, bytes executionCalldata) → bytes[] returnData

+
+

internal

+# +
+
+
+ +ERC-7579 execution logic. See [`AccountERC7579.supportsExecutionMode`](#AccountERC7579-supportsExecutionMode-bytes32-) for supported modes. + +Reverts if the call type is not supported. + +
+
+ + + +
+
+

_installModule(uint256 moduleTypeId, address module, bytes initData)

+
+

internal

+# +
+
+
+ +Installs a module of the given type with the given initialization data. + +For the fallback module type, the `initData` is expected to be the (packed) concatenation of a 4-byte +selector and the rest of the data to be sent to the handler when calling [`IERC7579Module.onInstall`](/contracts/5.x/api/interfaces#IERC7579Module-onInstall-bytes-). + +Requirements: + +* Module type must be supported. See [`AccountERC7579.supportsModule`](#AccountERC7579-supportsModule-uint256-). Reverts with [`ERC7579Utils.ERC7579UnsupportedModuleType`](#ERC7579Utils-ERC7579UnsupportedModuleType-uint256-). +* Module must be of the given type. Reverts with [`ERC7579Utils.ERC7579MismatchedModuleTypeId`](#ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address-). +* Module must not be already installed. Reverts with [`ERC7579Utils.ERC7579AlreadyInstalledModule`](#ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address-). + +Emits a [`IERC7579ModuleConfig.ModuleInstalled`](/contracts/5.x/api/interfaces#IERC7579ModuleConfig-ModuleInstalled-uint256-address-) event. + +
+
+ + + +
+
+

_uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)

+
+

internal

+# +
+
+
+ +Uninstalls a module of the given type with the given de-initialization data. + +For the fallback module type, the `deInitData` is expected to be the (packed) concatenation of a 4-byte +selector and the rest of the data to be sent to the handler when calling [`IERC7579Module.onUninstall`](/contracts/5.x/api/interfaces#IERC7579Module-onUninstall-bytes-). + +Requirements: + +* Module must be already installed. Reverts with [`ERC7579Utils.ERC7579UninstalledModule`](#ERC7579Utils-ERC7579UninstalledModule-uint256-address-) otherwise. + +
+
+ + + +
+
+

_fallback() → bytes

+
+

internal

+# +
+
+
+ +Fallback function that delegates the call to the installed handler for the given selector. + +Reverts with [`AccountERC7579.ERC7579MissingFallbackHandler`](#AccountERC7579-ERC7579MissingFallbackHandler-bytes4-) if the handler is not installed. + +Calls the handler with the original `msg.sender` appended at the end of the calldata following +the ERC-2771 format. + +
+
+ + + +
+
+

_fallbackHandler(bytes4 selector) → address

+
+

internal

+# +
+
+
+ +Returns the fallback handler for the given selector. Returns `address(0)` if not installed. + +
+
+ + + +
+
+

_checkModule(uint256 moduleTypeId, address module, bytes additionalContext)

+
+

internal

+# +
+
+
+ +Checks if the module is installed. Reverts if the module is not installed. + +
+
+ + + +
+
+

_extractUserOpValidator(struct PackedUserOperation userOp) → address

+
+

internal

+# +
+
+
+ +Extracts the nonce validator from the user operation. + +To construct a nonce key, set nonce as follows: + +``` + | | +``` + +The default behavior of this function replicates the behavior of +[Safe adapter](https://github.com/rhinestonewtf/safe7579/blob/bb29e8b1a66658790c4169e72608e27d220f79be/src/Safe7579.sol#L266), +[Etherspot's Prime Account](https://github.com/etherspot/etherspot-prime-contracts/blob/cfcdb48c4172cea0d66038324c0bae3288aa8caa/src/modular-etherspot-wallet/wallet/ModularEtherspotWallet.sol#L227), and +[ERC7579 reference implementation](https://github.com/erc7579/erc7579-implementation/blob/16138d1afd4e9711f6c1425133538837bd7787b5/src/MSAAdvanced.sol#L247). + + +This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions. + +For example, [Biconomy's Nexus](https://github.com/bcnmy/nexus/blob/54f4e19baaff96081a8843672977caf712ef19f4/contracts/lib/NonceLib.sol#L17) +uses a similar yet incompatible approach (the validator address is also part of the nonce, but not at the same location) + +
+
+ + + +
+
+

_extractSignatureValidator(bytes signature) → address module, bytes innerSignature

+
+

internal

+# +
+
+
+ +Extracts the signature validator from the signature. + +To construct a signature, set the first 20 bytes as the module address and the remaining bytes as the +signature data: + +``` + | +``` + + +The default behavior of this function replicates the behavior of +[Safe adapter](https://github.com/rhinestonewtf/safe7579/blob/bb29e8b1a66658790c4169e72608e27d220f79be/src/Safe7579.sol#L350), +[Biconomy's Nexus](https://github.com/bcnmy/nexus/blob/54f4e19baaff96081a8843672977caf712ef19f4/contracts/Nexus.sol#L239), +[Etherspot's Prime Account](https://github.com/etherspot/etherspot-prime-contracts/blob/cfcdb48c4172cea0d66038324c0bae3288aa8caa/src/modular-etherspot-wallet/wallet/ModularEtherspotWallet.sol#L252), and +[ERC7579 reference implementation](https://github.com/erc7579/erc7579-implementation/blob/16138d1afd4e9711f6c1425133538837bd7787b5/src/MSAAdvanced.sol#L296). + + +This is not standardized in ERC-7579 (or in any follow-up ERC). Some accounts may want to override these internal functions. + + +This function expects the signature to be at least 20 bytes long. Panics with [`Panic.ARRAY_OUT_OF_BOUNDS`](/contracts/5.x/api/utils#Panic-ARRAY_OUT_OF_BOUNDS-uint256) (0x32) otherwise. + + +
+
+ + + +
+
+

_decodeFallbackData(bytes data) → bytes4 selector, bytes remaining

+
+

internal

+# +
+
+
+ +Extract the function selector from initData/deInitData for MODULE_TYPE_FALLBACK + + +If we had calldata here, we could use calldata slice which are cheaper to manipulate and don't require +actual copy. However, this would require `_installModule` to get a calldata bytes object instead of a memory +bytes object. This would prevent calling `_installModule` from a contract constructor and would force the use +of external initializers. That may change in the future, as most accounts will probably be deployed as +clones/proxy/ERC-7702 delegates and therefore rely on initializers anyway. + + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32, bytes) → bool

+
+

internal

+# +
+
+
+ +By default, only use the modules for validation of userOp and signature. Disable raw signatures. + +
+
+ + + +
+
+

ERC7579MissingFallbackHandler(bytes4 selector)

+
+

error

+# +
+
+
+ +The account's [`AccountERC7579.fallback`](#AccountERC7579-fallback-bytes-) was called with a selector that doesn't have an installed handler. + +
+
+ + + +
+
+

ERC7579CannotDecodeFallbackData()

+
+

error

+# +
+
+
+ +The provided initData/deInitData for a fallback module is too short to extract a selector. + +
+
+ + + +
+ +## `AccountERC7579Hooked` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/extensions/draft-AccountERC7579Hooked.sol"; +``` + +Extension of [`AccountERC7579`](#AccountERC7579) with support for a single hook module (type 4). + +If installed, this extension will call the hook module's [`IERC7579Hook.preCheck`](/contracts/5.x/api/interfaces#IERC7579Hook-preCheck-address-uint256-bytes-) before executing any operation +with [`AccountERC7579._execute`](#AccountERC7579-_execute-Mode-bytes-) (including [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) and [`AccountERC7579.executeFromExecutor`](#AccountERC7579-executeFromExecutor-bytes32-bytes-) by default) and [`IERC7579Hook.postCheck`](/contracts/5.x/api/interfaces#IERC7579Hook-postCheck-bytes-) thereafter. + + +Hook modules break the check-effect-interaction pattern. In particular, the [`IERC7579Hook.preCheck`](/contracts/5.x/api/interfaces#IERC7579Hook-preCheck-address-uint256-bytes-) hook can +lead to potentially dangerous reentrancy. Using the `withHook()` modifier is safe if no effect is performed +before the preHook or after the postHook. That is the case on all functions here, but it may not be the case if +functions that have this modifier are overridden. Developers should be extremely careful when implementing hook +modules or further overriding functions that involve hooks. + + +
+

Modifiers

+
+- [withHook()](#AccountERC7579Hooked-withHook--) +
+
+ +
+

Functions

+
+- [accountId()](#AccountERC7579Hooked-accountId--) +- [hook()](#AccountERC7579Hooked-hook--) +- [supportsModule(moduleTypeId)](#AccountERC7579Hooked-supportsModule-uint256-) +- [isModuleInstalled(moduleTypeId, module, data)](#AccountERC7579Hooked-isModuleInstalled-uint256-address-bytes-) +- [_installModule(moduleTypeId, module, initData)](#AccountERC7579Hooked-_installModule-uint256-address-bytes-) +- [_uninstallModule(moduleTypeId, module, deInitData)](#AccountERC7579Hooked-_uninstallModule-uint256-address-bytes-) +- [_execute(mode, executionCalldata)](#AccountERC7579Hooked-_execute-Mode-bytes-) +- [_fallback()](#AccountERC7579Hooked-_fallback--) +#### AccountERC7579 [!toc] +- [fallback()](#AccountERC7579-fallback-bytes-) +- [supportsExecutionMode(encodedMode)](#AccountERC7579-supportsExecutionMode-bytes32-) +- [installModule(moduleTypeId, module, initData)](#AccountERC7579-installModule-uint256-address-bytes-) +- [uninstallModule(moduleTypeId, module, deInitData)](#AccountERC7579-uninstallModule-uint256-address-bytes-) +- [execute(mode, executionCalldata)](#AccountERC7579-execute-bytes32-bytes-) +- [executeFromExecutor(mode, executionCalldata)](#AccountERC7579-executeFromExecutor-bytes32-bytes-) +- [isValidSignature(hash, signature)](#AccountERC7579-isValidSignature-bytes32-bytes-) +- [_validateUserOp(userOp, userOpHash, signature)](#AccountERC7579-_validateUserOp-struct-PackedUserOperation-bytes32-bytes-) +- [_fallbackHandler(selector)](#AccountERC7579-_fallbackHandler-bytes4-) +- [_checkModule(moduleTypeId, module, additionalContext)](#AccountERC7579-_checkModule-uint256-address-bytes-) +- [_extractUserOpValidator(userOp)](#AccountERC7579-_extractUserOpValidator-struct-PackedUserOperation-) +- [_extractSignatureValidator(signature)](#AccountERC7579-_extractSignatureValidator-bytes-) +- [_decodeFallbackData(data)](#AccountERC7579-_decodeFallbackData-bytes-) +- [_rawSignatureValidation(, )](#AccountERC7579-_rawSignatureValidation-bytes32-bytes-) +#### IERC7579ModuleConfig [!toc] +#### IERC7579AccountConfig [!toc] +#### IERC7579Execution [!toc] +#### IERC1271 [!toc] +#### Account [!toc] +- [entryPoint()](#Account-entryPoint--) +- [getNonce()](#Account-getNonce--) +- [getNonce(key)](#Account-getNonce-uint192-) +- [validateUserOp(userOp, userOpHash, missingAccountFunds)](#Account-validateUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [_signableUserOpHash(, userOpHash)](#Account-_signableUserOpHash-struct-PackedUserOperation-bytes32-) +- [_payPrefund(missingAccountFunds)](#Account-_payPrefund-uint256-) +- [_checkEntryPoint()](#Account-_checkEntryPoint--) +- [_checkEntryPointOrSelf()](#Account-_checkEntryPointOrSelf--) +- [receive()](#Account-receive--) +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ +
+

Events

+
+#### AccountERC7579 [!toc] +#### IERC7579ModuleConfig [!toc] +- [ModuleInstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleInstalled-uint256-address-) +- [ModuleUninstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleUninstalled-uint256-address-) +#### IERC7579AccountConfig [!toc] +#### IERC7579Execution [!toc] +#### IERC1271 [!toc] +#### Account [!toc] +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+- [ERC7579HookModuleAlreadyPresent(hook)](#AccountERC7579Hooked-ERC7579HookModuleAlreadyPresent-address-) +#### AccountERC7579 [!toc] +- [ERC7579MissingFallbackHandler(selector)](#AccountERC7579-ERC7579MissingFallbackHandler-bytes4-) +- [ERC7579CannotDecodeFallbackData()](#AccountERC7579-ERC7579CannotDecodeFallbackData--) +#### IERC7579ModuleConfig [!toc] +#### IERC7579AccountConfig [!toc] +#### IERC7579Execution [!toc] +#### IERC1271 [!toc] +#### Account [!toc] +- [AccountUnauthorized(sender)](#Account-AccountUnauthorized-address-) +#### IAccount [!toc] +#### AbstractSigner [!toc] +
+
+ + + +
+
+

withHook()

+
+

internal

+# +
+
+ +
+ +Calls [`IERC7579Hook.preCheck`](/contracts/5.x/api/interfaces#IERC7579Hook-preCheck-address-uint256-bytes-) before executing the modified function and [`IERC7579Hook.postCheck`](/contracts/5.x/api/interfaces#IERC7579Hook-postCheck-bytes-) +thereafter. + +
+
+ + + +
+
+

accountId() → string

+
+

public

+# +
+
+
+ +Returns the account id of the smart account + +
+
+ + + +
+
+

hook() → address

+
+

public

+# +
+
+
+ +Returns the hook module address if installed, or `address(0)` otherwise. + +
+
+ + + +
+
+

supportsModule(uint256 moduleTypeId) → bool

+
+

public

+# +
+
+
+ +Supports hook modules. See [`AccountERC7579.supportsModule`](#AccountERC7579-supportsModule-uint256-) + +
+
+ + + +
+
+

isModuleInstalled(uint256 moduleTypeId, address module, bytes data) → bool

+
+

public

+# +
+
+
+ +Returns whether a module is installed on the smart account + +
+
+ + + +
+
+

_installModule(uint256 moduleTypeId, address module, bytes initData)

+
+

internal

+# +
+
+
+ +Installs a module with support for hook modules. See [`AccountERC7579._installModule`](#AccountERC7579-_installModule-uint256-address-bytes-) + +
+
+ + + +
+
+

_uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)

+
+

internal

+# +
+
+
+ +Uninstalls a module with support for hook modules. See [`AccountERC7579._uninstallModule`](#AccountERC7579-_uninstallModule-uint256-address-bytes-) + +
+
+ + + +
+
+

_execute(Mode mode, bytes executionCalldata) → bytes[]

+
+

internal

+# +
+
+
+ +Hooked version of [`AccountERC7579._execute`](#AccountERC7579-_execute-Mode-bytes-). + +
+
+ + + +
+
+

_fallback() → bytes

+
+

internal

+# +
+
+
+ +Hooked version of [`AccountERC7579._fallback`](#AccountERC7579-_fallback--). + +
+
+ + + +
+
+

ERC7579HookModuleAlreadyPresent(address hook)

+
+

error

+# +
+
+
+ +A hook module is already present. This contract only supports one hook module. + +
+
+ + + +
+ +## `ERC7821` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +``` + +Minimal batch executor following ERC-7821. + +Only supports single batch mode (`0x01000000000000000000`). Does not support optional "opData". + +@custom:stateless + +
+

Functions

+
+- [execute(mode, executionData)](#ERC7821-execute-bytes32-bytes-) +- [supportsExecutionMode(mode)](#ERC7821-supportsExecutionMode-bytes32-) +- [_erc7821AuthorizedExecutor(caller, , )](#ERC7821-_erc7821AuthorizedExecutor-address-bytes32-bytes-) +#### IERC7821 [!toc] +
+
+ +
+

Errors

+
+- [UnsupportedExecutionMode()](#ERC7821-UnsupportedExecutionMode--) +#### IERC7821 [!toc] +
+
+ + + +
+
+

execute(bytes32 mode, bytes executionData)

+
+

public

+# +
+
+
+ +Executes the calls in `executionData` with no optional `opData` support. + + +Access to this function is controlled by [`ERC7821._erc7821AuthorizedExecutor`](#ERC7821-_erc7821AuthorizedExecutor-address-bytes32-bytes-). Changing access permissions, for +example to approve calls by the ERC-4337 entrypoint, should be implemented by overriding it. + + +Reverts and bubbles up error if any call fails. + +
+
+ + + +
+
+

supportsExecutionMode(bytes32 mode) → bool result

+
+

public

+# +
+
+
+ +This function is provided for frontends to detect support. +Only returns true for: +- `bytes32(0x01000000000000000000...)`: does not support optional `opData`. +- `bytes32(0x01000000000078210001...)`: supports optional `opData`. + +
+
+ + + +
+
+

_erc7821AuthorizedExecutor(address caller, bytes32, bytes) → bool

+
+

internal

+# +
+
+
+ +Access control mechanism for the [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) function. +By default, only the contract itself is allowed to execute. + +Override this function to implement custom access control, for example to allow the +ERC-4337 entrypoint to execute. + +```solidity +function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData +) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); +} +``` + +
+
+ + + +
+
+

UnsupportedExecutionMode()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `EIP7702Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/EIP7702Utils.sol"; +``` + +Library with common EIP-7702 utility functions. + +See [ERC-7702](https://eips.ethereum.org/EIPS/eip-7702). + +
+

Functions

+
+- [fetchDelegate(account)](#EIP7702Utils-fetchDelegate-address-) +
+
+ + + +
+
+

fetchDelegate(address account) → address

+
+

internal

+# +
+
+
+ +Returns the address of the delegate if `account` has an EIP-7702 delegation setup, or address(0) otherwise. + +
+
+ + + +
+ +## `IEntryPointExtra` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +``` + +This is available on all entrypoint since v0.4.0, but is not formally part of the ERC. + +
+

Functions

+
+- [getUserOpHash(userOp)](#IEntryPointExtra-getUserOpHash-struct-PackedUserOperation-) +
+
+ + + +
+
+

getUserOpHash(struct PackedUserOperation userOp) → bytes32

+
+

external

+# +
+
+
+ +
+
+ + + +
+ +## `ERC4337Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC4337Utils.sol"; +``` + +Library with common ERC-4337 utility functions. + +See [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337). + +
+

Functions

+
+- [parseValidationData(validationData)](#ERC4337Utils-parseValidationData-uint256-) +- [packValidationData(aggregator, validAfter, validUntil)](#ERC4337Utils-packValidationData-address-uint48-uint48-) +- [packValidationData(sigSuccess, validAfter, validUntil)](#ERC4337Utils-packValidationData-bool-uint48-uint48-) +- [combineValidationData(validationData1, validationData2)](#ERC4337Utils-combineValidationData-uint256-uint256-) +- [getValidationData(validationData)](#ERC4337Utils-getValidationData-uint256-) +- [hash(self, entrypoint)](#ERC4337Utils-hash-struct-PackedUserOperation-address-) +- [factory(self)](#ERC4337Utils-factory-struct-PackedUserOperation-) +- [factoryData(self)](#ERC4337Utils-factoryData-struct-PackedUserOperation-) +- [verificationGasLimit(self)](#ERC4337Utils-verificationGasLimit-struct-PackedUserOperation-) +- [callGasLimit(self)](#ERC4337Utils-callGasLimit-struct-PackedUserOperation-) +- [maxPriorityFeePerGas(self)](#ERC4337Utils-maxPriorityFeePerGas-struct-PackedUserOperation-) +- [maxFeePerGas(self)](#ERC4337Utils-maxFeePerGas-struct-PackedUserOperation-) +- [gasPrice(self)](#ERC4337Utils-gasPrice-struct-PackedUserOperation-) +- [paymaster(self)](#ERC4337Utils-paymaster-struct-PackedUserOperation-) +- [paymasterVerificationGasLimit(self)](#ERC4337Utils-paymasterVerificationGasLimit-struct-PackedUserOperation-) +- [paymasterPostOpGasLimit(self)](#ERC4337Utils-paymasterPostOpGasLimit-struct-PackedUserOperation-) +- [paymasterData(self)](#ERC4337Utils-paymasterData-struct-PackedUserOperation-) +
+
+ + + +
+
+

parseValidationData(uint256 validationData) → address aggregator, uint48 validAfter, uint48 validUntil

+
+

internal

+# +
+
+
+ +Parses the validation data into its components. See [`ERC4337Utils.packValidationData`](#ERC4337Utils-packValidationData-bool-uint48-uint48-). + +
+
+ + + +
+
+

packValidationData(address aggregator, uint48 validAfter, uint48 validUntil) → uint256

+
+

internal

+# +
+
+
+ +Packs the validation data into a single uint256. See [`ERC4337Utils.parseValidationData`](#ERC4337Utils-parseValidationData-uint256-). + +
+
+ + + +
+
+

packValidationData(bool sigSuccess, uint48 validAfter, uint48 validUntil) → uint256

+
+

internal

+# +
+
+
+ +Same as [`ERC4337Utils.packValidationData`](#ERC4337Utils-packValidationData-bool-uint48-uint48-), but with a boolean signature success flag. + +
+
+ + + +
+
+

combineValidationData(uint256 validationData1, uint256 validationData2) → uint256

+
+

internal

+# +
+
+
+ +Combines two validation data into a single one. + +The `aggregator` is set to [`ERC4337Utils.SIG_VALIDATION_SUCCESS`](#ERC4337Utils-SIG_VALIDATION_SUCCESS-uint256) if both are successful, while +the `validAfter` is the maximum and the `validUntil` is the minimum of both. + +
+
+ + + +
+
+

getValidationData(uint256 validationData) → address aggregator, bool outOfTimeRange

+
+

internal

+# +
+
+
+ +Returns the aggregator of the `validationData` and whether it is out of time range. + +
+
+ + + +
+
+

hash(struct PackedUserOperation self, address entrypoint) → bytes32

+
+

internal

+# +
+
+
+ +Get the hash of a user operation for a given entrypoint + +
+
+ + + +
+
+

factory(struct PackedUserOperation self) → address

+
+

internal

+# +
+
+
+ +Returns `factory` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation), or address(0) if the initCode is empty or not properly formatted. + +
+
+ + + +
+
+

factoryData(struct PackedUserOperation self) → bytes

+
+

internal

+# +
+
+
+ +Returns `factoryData` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation), or empty bytes if the initCode is empty or not properly formatted. + +
+
+ + + +
+
+

verificationGasLimit(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns `verificationGasLimit` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

callGasLimit(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns `callGasLimit` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

maxPriorityFeePerGas(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns the first section of `gasFees` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

maxFeePerGas(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns the second section of `gasFees` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

gasPrice(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns the total gas price for the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation) (ie. `maxFeePerGas` or `maxPriorityFeePerGas + basefee`). + +
+
+ + + +
+
+

paymaster(struct PackedUserOperation self) → address

+
+

internal

+# +
+
+
+ +Returns the first section of `paymasterAndData` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

paymasterVerificationGasLimit(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns the second section of `paymasterAndData` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

paymasterPostOpGasLimit(struct PackedUserOperation self) → uint256

+
+

internal

+# +
+
+
+ +Returns the third section of `paymasterAndData` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+
+

paymasterData(struct PackedUserOperation self) → bytes

+
+

internal

+# +
+
+
+ +Returns the fourth section of `paymasterAndData` from the [`PackedUserOperation`](/contracts/5.x/api/interfaces#PackedUserOperation). + +
+
+ + + +
+ +## `Mode` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + + + +
+ +## `CallType` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + + + +
+ +## `ExecType` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + + + +
+ +## `ModeSelector` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + + + +
+ +## `ModePayload` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + + + +
+ +## `ERC7579Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + +Library with common ERC-7579 utility functions. + +See [ERC-7579](https://eips.ethereum.org/EIPS/eip-7579). + +
+

Functions

+
+- [execSingle(executionCalldata, execType)](#ERC7579Utils-execSingle-bytes-ExecType-) +- [execBatch(executionCalldata, execType)](#ERC7579Utils-execBatch-bytes-ExecType-) +- [execDelegateCall(executionCalldata, execType)](#ERC7579Utils-execDelegateCall-bytes-ExecType-) +- [encodeMode(callType, execType, selector, payload)](#ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload-) +- [decodeMode(mode)](#ERC7579Utils-decodeMode-Mode-) +- [encodeSingle(target, value, callData)](#ERC7579Utils-encodeSingle-address-uint256-bytes-) +- [decodeSingle(executionCalldata)](#ERC7579Utils-decodeSingle-bytes-) +- [encodeDelegate(target, callData)](#ERC7579Utils-encodeDelegate-address-bytes-) +- [decodeDelegate(executionCalldata)](#ERC7579Utils-decodeDelegate-bytes-) +- [encodeBatch(executionBatch)](#ERC7579Utils-encodeBatch-struct-Execution---) +- [decodeBatch(executionCalldata)](#ERC7579Utils-decodeBatch-bytes-) +
+
+ +
+

Events

+
+- [ERC7579TryExecuteFail(batchExecutionIndex, returndata)](#ERC7579Utils-ERC7579TryExecuteFail-uint256-bytes-) +
+
+ +
+

Errors

+
+- [ERC7579UnsupportedCallType(callType)](#ERC7579Utils-ERC7579UnsupportedCallType-CallType-) +- [ERC7579UnsupportedExecType(execType)](#ERC7579Utils-ERC7579UnsupportedExecType-ExecType-) +- [ERC7579MismatchedModuleTypeId(moduleTypeId, module)](#ERC7579Utils-ERC7579MismatchedModuleTypeId-uint256-address-) +- [ERC7579UninstalledModule(moduleTypeId, module)](#ERC7579Utils-ERC7579UninstalledModule-uint256-address-) +- [ERC7579AlreadyInstalledModule(moduleTypeId, module)](#ERC7579Utils-ERC7579AlreadyInstalledModule-uint256-address-) +- [ERC7579UnsupportedModuleType(moduleTypeId)](#ERC7579Utils-ERC7579UnsupportedModuleType-uint256-) +- [ERC7579DecodingError()](#ERC7579Utils-ERC7579DecodingError--) +
+
+ + + +
+
+

execSingle(bytes executionCalldata, ExecType execType) → bytes[] returnData

+
+

internal

+# +
+
+
+ +Executes a single call. + +
+
+ + + +
+
+

execBatch(bytes executionCalldata, ExecType execType) → bytes[] returnData

+
+

internal

+# +
+
+
+ +Executes a batch of calls. + +
+
+ + + +
+
+

execDelegateCall(bytes executionCalldata, ExecType execType) → bytes[] returnData

+
+

internal

+# +
+
+
+ +Executes a delegate call. + +
+
+ + + +
+
+

encodeMode(CallType callType, ExecType execType, ModeSelector selector, ModePayload payload) → Mode mode

+
+

internal

+# +
+
+
+ +Encodes the mode with the provided parameters. See [`ERC7579Utils.decodeMode`](#ERC7579Utils-decodeMode-Mode-). + +
+
+ + + +
+
+

decodeMode(Mode mode) → CallType callType, ExecType execType, ModeSelector selector, ModePayload payload

+
+

internal

+# +
+
+
+ +Decodes the mode into its parameters. See [`ERC7579Utils.encodeMode`](#ERC7579Utils-encodeMode-CallType-ExecType-ModeSelector-ModePayload-). + +
+
+ + + +
+
+

encodeSingle(address target, uint256 value, bytes callData) → bytes executionCalldata

+
+

internal

+# +
+
+
+ +Encodes a single call execution. See [`ERC7579Utils.decodeSingle`](#ERC7579Utils-decodeSingle-bytes-). + +
+
+ + + +
+
+

decodeSingle(bytes executionCalldata) → address target, uint256 value, bytes callData

+
+

internal

+# +
+
+
+ +Decodes a single call execution. See [`ERC7579Utils.encodeSingle`](#ERC7579Utils-encodeSingle-address-uint256-bytes-). + +
+
+ + + +
+
+

encodeDelegate(address target, bytes callData) → bytes executionCalldata

+
+

internal

+# +
+
+
+ +Encodes a delegate call execution. See [`ERC7579Utils.decodeDelegate`](#ERC7579Utils-decodeDelegate-bytes-). + +
+
+ + + +
+
+

decodeDelegate(bytes executionCalldata) → address target, bytes callData

+
+

internal

+# +
+
+
+ +Decodes a delegate call execution. See [`ERC7579Utils.encodeDelegate`](#ERC7579Utils-encodeDelegate-address-bytes-). + +
+
+ + + +
+
+

encodeBatch(struct Execution[] executionBatch) → bytes executionCalldata

+
+

internal

+# +
+
+
+ +Encodes a batch of executions. See [`ERC7579Utils.decodeBatch`](#ERC7579Utils-decodeBatch-bytes-). + +
+
+ + + +
+
+

decodeBatch(bytes executionCalldata) → struct Execution[] executionBatch

+
+

internal

+# +
+
+
+ +Decodes a batch of executions. See [`ERC7579Utils.encodeBatch`](#ERC7579Utils-encodeBatch-struct-Execution---). + + +This function runs some checks and will throw a [`ERC7579Utils.ERC7579DecodingError`](#ERC7579Utils-ERC7579DecodingError--) if the input is not properly formatted. + + +
+
+ + + +
+
+

ERC7579TryExecuteFail(uint256 batchExecutionIndex, bytes returndata)

+
+

event

+# +
+
+ +
+ +Emits when an [`ERC7579Utils.EXECTYPE_TRY`](#ERC7579Utils-EXECTYPE_TRY-ExecType) execution fails. + +
+
+ + + +
+
+

ERC7579UnsupportedCallType(CallType callType)

+
+

error

+# +
+
+
+ +The provided [`CallType`](#CallType) is not supported. + +
+
+ + + +
+
+

ERC7579UnsupportedExecType(ExecType execType)

+
+

error

+# +
+
+
+ +The provided [`ExecType`](#ExecType) is not supported. + +
+
+ + + +
+
+

ERC7579MismatchedModuleTypeId(uint256 moduleTypeId, address module)

+
+

error

+# +
+
+
+ +The provided module doesn't match the provided module type. + +
+
+ + + +
+
+

ERC7579UninstalledModule(uint256 moduleTypeId, address module)

+
+

error

+# +
+
+
+ +The module is not installed. + +
+
+ + + +
+
+

ERC7579AlreadyInstalledModule(uint256 moduleTypeId, address module)

+
+

error

+# +
+
+
+ +The module is already installed. + +
+
+ + + +
+
+

ERC7579UnsupportedModuleType(uint256 moduleTypeId)

+
+

error

+# +
+
+
+ +The module type is not supported. + +
+
+ + + +
+
+

ERC7579DecodingError()

+
+

error

+# +
+
+
+ +Input calldata not properly formatted and possibly malicious. + +
+
+ + + +
+ +## `eqCallType` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + +Compares two `CallType` values for equality. + + + +
+
+

eqCallType(CallType a, CallType b) → bool

+
+

internal

+# +
+
+
+ +Compares two `CallType` values for equality. + +
+
+ + + +
+ +## `eqExecType` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + +Compares two `ExecType` values for equality. + + + +
+
+

eqExecType(ExecType a, ExecType b) → bool

+
+

internal

+# +
+
+
+ +Compares two `ExecType` values for equality. + +
+
+ + + +
+ +## `eqModeSelector` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + +Compares two `ModeSelector` values for equality. + + + +
+
+

eqModeSelector(ModeSelector a, ModeSelector b) → bool

+
+

internal

+# +
+
+
+ +Compares two `ModeSelector` values for equality. + +
+
+ + + +
+ +## `eqModePayload` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/account/utils/draft-ERC7579Utils.sol"; +``` + +Compares two `ModePayload` values for equality. + + + +
+
+

eqModePayload(ModePayload a, ModePayload b) → bool

+
+

internal

+# +
+
+
+ +Compares two `ModePayload` values for equality. + +
+
diff --git a/docs/content/contracts/5.x/api/crosschain.mdx b/docs/content/contracts/5.x/api/crosschain.mdx new file mode 100644 index 00000000..d76a343d --- /dev/null +++ b/docs/content/contracts/5.x/api/crosschain.mdx @@ -0,0 +1,148 @@ +--- +title: "Crosschain" +description: "Smart contract crosschain utilities and implementations" +--- + +This directory contains contracts for sending and receiving cross chain messages that follows the ERC-7786 standard. + +* [`ERC7786Recipient`](#ERC7786Recipient): generic ERC-7786 crosschain contract that receives messages from a trusted gateway + +## Helpers + +[`ERC7786Recipient`](#ERC7786Recipient) + + + +
+ +## `ERC7786Recipient` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/crosschain/ERC7786Recipient.sol"; +``` + +Base implementation of an ERC-7786 compliant cross-chain message receiver. + +This abstract contract exposes the `receiveMessage` function that is used for communication with (one or multiple) +destination gateways. This contract leaves two functions unimplemented: + +* [`ERC7786Recipient._isAuthorizedGateway`](#ERC7786Recipient-_isAuthorizedGateway-address-bytes-), an internal getter used to verify whether an address is recognised by the contract as a +valid ERC-7786 destination gateway. One or multiple gateway can be supported. Note that any malicious address for +which this function returns true would be able to impersonate any account on any other chain sending any message. + +* [`ERC7786Recipient._processMessage`](#ERC7786Recipient-_processMessage-address-bytes32-bytes-bytes-), the internal function that will be called with any message that has been validated. + +This contract implements replay protection, meaning that if two messages are received from the same gateway with the +same `receiveId`, then the second one will NOT be executed, regardless of the result of [`ERC7786Recipient._isAuthorizedGateway`](#ERC7786Recipient-_isAuthorizedGateway-address-bytes-). + +
+

Functions

+
+- [receiveMessage(receiveId, sender, payload)](#ERC7786Recipient-receiveMessage-bytes32-bytes-bytes-) +- [_isAuthorizedGateway(gateway, sender)](#ERC7786Recipient-_isAuthorizedGateway-address-bytes-) +- [_processMessage(gateway, receiveId, sender, payload)](#ERC7786Recipient-_processMessage-address-bytes32-bytes-bytes-) +#### IERC7786Recipient [!toc] +
+
+ +
+

Errors

+
+- [ERC7786RecipientUnauthorizedGateway(gateway, sender)](#ERC7786Recipient-ERC7786RecipientUnauthorizedGateway-address-bytes-) +- [ERC7786RecipientMessageAlreadyProcessed(gateway, receiveId)](#ERC7786Recipient-ERC7786RecipientMessageAlreadyProcessed-address-bytes32-) +#### IERC7786Recipient [!toc] +
+
+ + + +
+
+

receiveMessage(bytes32 receiveId, bytes sender, bytes payload) → bytes4

+
+

external

+# +
+
+
+ +Endpoint for receiving cross-chain message. + +This function may be called directly by the gateway. + +
+
+ + + +
+
+

_isAuthorizedGateway(address gateway, bytes sender) → bool

+
+

internal

+# +
+
+
+ +Virtual getter that returns whether an address is a valid ERC-7786 gateway for a given sender. + +The `sender` parameter is an interoperable address that include the source chain. The chain part can be +extracted using the [`InteroperableAddress`](/contracts/5.x/api/utils#InteroperableAddress) library to selectively authorize gateways based on the origin chain +of a message. + +
+
+ + + +
+
+

_processMessage(address gateway, bytes32 receiveId, bytes sender, bytes payload)

+
+

internal

+# +
+
+
+ +Virtual function that should contain the logic to execute when a cross-chain message is received. + +
+
+ + + +
+
+

ERC7786RecipientUnauthorizedGateway(address gateway, bytes sender)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC7786RecipientMessageAlreadyProcessed(address gateway, bytes32 receiveId)

+
+

error

+# +
+
+
+ +
+
diff --git a/docs/content/contracts/5.x/api/finance.mdx b/docs/content/contracts/5.x/api/finance.mdx new file mode 100644 index 00000000..2f8eb13d --- /dev/null +++ b/docs/content/contracts/5.x/api/finance.mdx @@ -0,0 +1,526 @@ +--- +title: "Finance" +description: "Smart contract finance utilities and implementations" +--- + +This directory includes primitives for financial systems: + +* [`VestingWallet`](#VestingWallet) handles the vesting of Ether and ERC-20 tokens for a given beneficiary. Custody of multiple tokens can + be given to this contract, which will release the token to the beneficiary following a given, customizable, vesting + schedule. + +## Contracts + +[`VestingWallet`](#VestingWallet) + + + +
+ +## `VestingWallet` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/finance/VestingWallet.sol"; +``` + +A vesting wallet is an ownable contract that can receive native currency and ERC-20 tokens, and release these +assets to the wallet owner, also referred to as "beneficiary", according to a vesting schedule. + +Any assets transferred to this contract will follow the vesting schedule as if they were locked from the beginning. +Consequently, if the vesting has already started, any amount of tokens sent to this contract will (at least partly) +be immediately releasable. + +By setting the duration to 0, one can configure this contract to behave like an asset timelock that holds tokens for +a beneficiary until a specified time. + + +Since the wallet is [`Ownable`](/contracts/5.x/api/access#Ownable), and ownership can be transferred, it is possible to sell unvested tokens. +Preventing this in a smart contract is difficult, considering that: 1) a beneficiary address could be a +counterfactually deployed contract, 2) there is likely to be a migration path for EOAs to become contracts in the +near future. + + + +When using this contract with any token whose balance is adjusted automatically (i.e. a rebase token), make +sure to account the supply/balance adjustment in the vesting schedule to ensure the vested amount is as intended. + + + +Chains with support for native ERC20s may allow the vesting wallet to withdraw the underlying asset as both an +ERC20 and as native currency. For example, if chain C supports token A and the wallet gets deposited 100 A, then +at 50% of the vesting period, the beneficiary can withdraw 50 A as ERC20 and 25 A as native currency (totaling 75 A). +Consider disabling one of the withdrawal methods. + + +
+

Functions

+
+- [constructor(beneficiary, startTimestamp, durationSeconds)](#VestingWallet-constructor-address-uint64-uint64-) +- [receive()](#VestingWallet-receive--) +- [start()](#VestingWallet-start--) +- [duration()](#VestingWallet-duration--) +- [end()](#VestingWallet-end--) +- [released()](#VestingWallet-released--) +- [released(token)](#VestingWallet-released-address-) +- [releasable()](#VestingWallet-releasable--) +- [releasable(token)](#VestingWallet-releasable-address-) +- [release()](#VestingWallet-release--) +- [release(token)](#VestingWallet-release-address-) +- [vestedAmount(timestamp)](#VestingWallet-vestedAmount-uint64-) +- [vestedAmount(token, timestamp)](#VestingWallet-vestedAmount-address-uint64-) +- [_vestingSchedule(totalAllocation, timestamp)](#VestingWallet-_vestingSchedule-uint256-uint64-) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+- [EtherReleased(amount)](#VestingWallet-EtherReleased-uint256-) +- [ERC20Released(token, amount)](#VestingWallet-ERC20Released-address-uint256-) +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ +
+

Errors

+
+#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +
+
+ + + +
+
+

constructor(address beneficiary, uint64 startTimestamp, uint64 durationSeconds)

+
+

public

+# +
+
+
+ +Sets the beneficiary (owner), the start timestamp and the vesting duration (in seconds) of the vesting +wallet. + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +The contract should be able to receive Eth. + +
+
+ + + +
+
+

start() → uint256

+
+

public

+# +
+
+
+ +Getter for the start timestamp. + +
+
+ + + +
+
+

duration() → uint256

+
+

public

+# +
+
+
+ +Getter for the vesting duration. + +
+
+ + + +
+
+

end() → uint256

+
+

public

+# +
+
+
+ +Getter for the end timestamp. + +
+
+ + + +
+
+

released() → uint256

+
+

public

+# +
+
+
+ +Amount of eth already released + +
+
+ + + +
+
+

released(address token) → uint256

+
+

public

+# +
+
+
+ +Amount of token already released + +
+
+ + + +
+
+

releasable() → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of releasable eth. + +
+
+ + + +
+
+

releasable(address token) → uint256

+
+

public

+# +
+
+
+ +Getter for the amount of releasable `token` tokens. `token` should be the address of an +[`IERC20`](/contracts/5.x/api/token/ERC20#IERC20) contract. + +
+
+ + + +
+
+

release()

+
+

public

+# +
+
+
+ +Release the native tokens (ether) that have already vested. + +Emits a [`VestingWallet.EtherReleased`](#VestingWallet-EtherReleased-uint256-) event. + +
+
+ + + +
+
+

release(address token)

+
+

public

+# +
+
+
+ +Release the tokens that have already vested. + +Emits a [`VestingWallet.ERC20Released`](#VestingWallet-ERC20Released-address-uint256-) event. + +
+
+ + + +
+
+

vestedAmount(uint64 timestamp) → uint256

+
+

public

+# +
+
+
+ +Calculates the amount of ether that has already vested. Default implementation is a linear vesting curve. + +
+
+ + + +
+
+

vestedAmount(address token, uint64 timestamp) → uint256

+
+

public

+# +
+
+
+ +Calculates the amount of tokens that has already vested. Default implementation is a linear vesting curve. + +
+
+ + + +
+
+

_vestingSchedule(uint256 totalAllocation, uint64 timestamp) → uint256

+
+

internal

+# +
+
+
+ +Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for +an asset given its total historical allocation. + +
+
+ + + +
+
+

EtherReleased(uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ERC20Released(address indexed token, uint256 amount)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `VestingWalletCliff` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/finance/VestingWalletCliff.sol"; +``` + +Extension of [`VestingWallet`](#VestingWallet) that adds a cliff to the vesting schedule. + +_Available since v5.1._ + +
+

Functions

+
+- [constructor(cliffSeconds)](#VestingWalletCliff-constructor-uint64-) +- [cliff()](#VestingWalletCliff-cliff--) +- [_vestingSchedule(totalAllocation, timestamp)](#VestingWalletCliff-_vestingSchedule-uint256-uint64-) +#### VestingWallet [!toc] +- [receive()](#VestingWallet-receive--) +- [start()](#VestingWallet-start--) +- [duration()](#VestingWallet-duration--) +- [end()](#VestingWallet-end--) +- [released()](#VestingWallet-released--) +- [released(token)](#VestingWallet-released-address-) +- [releasable()](#VestingWallet-releasable--) +- [releasable(token)](#VestingWallet-releasable-address-) +- [release()](#VestingWallet-release--) +- [release(token)](#VestingWallet-release-address-) +- [vestedAmount(timestamp)](#VestingWallet-vestedAmount-uint64-) +- [vestedAmount(token, timestamp)](#VestingWallet-vestedAmount-address-uint64-) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+#### VestingWallet [!toc] +- [EtherReleased(amount)](#VestingWallet-EtherReleased-uint256-) +- [ERC20Released(token, amount)](#VestingWallet-ERC20Released-address-uint256-) +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ +
+

Errors

+
+- [InvalidCliffDuration(cliffSeconds, durationSeconds)](#VestingWalletCliff-InvalidCliffDuration-uint64-uint64-) +#### VestingWallet [!toc] +#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +
+
+ + + +
+
+

constructor(uint64 cliffSeconds)

+
+

internal

+# +
+
+
+ +Set the duration of the cliff, in seconds. The cliff starts vesting schedule (see [`VestingWallet`](#VestingWallet)'s +constructor) and ends `cliffSeconds` later. + +
+
+ + + +
+
+

cliff() → uint256

+
+

public

+# +
+
+
+ +Getter for the cliff timestamp. + +
+
+ + + +
+
+

_vestingSchedule(uint256 totalAllocation, uint64 timestamp) → uint256

+
+

internal

+# +
+
+
+ +Virtual implementation of the vesting formula. This returns the amount vested, as a function of time, for +an asset given its total historical allocation. Returns 0 if the [`VestingWalletCliff.cliff`](#VestingWalletCliff-cliff--) timestamp is not met. + + +The cliff not only makes the schedule return 0, but it also ignores every possible side +effect from calling the inherited implementation (i.e. `super._vestingSchedule`). Carefully consider +this caveat if the overridden implementation of this function has any (e.g. writing to memory or reverting). + + +
+
+ + + +
+
+

InvalidCliffDuration(uint64 cliffSeconds, uint64 durationSeconds)

+
+

error

+# +
+
+
+ +The specified cliff duration is larger than the vesting duration. + +
+
diff --git a/docs/content/contracts/5.x/api/governance.mdx b/docs/content/contracts/5.x/api/governance.mdx new file mode 100644 index 00000000..28e861ad --- /dev/null +++ b/docs/content/contracts/5.x/api/governance.mdx @@ -0,0 +1,9398 @@ +--- +title: "Governance" +description: "Smart contract governance utilities and implementations" +--- + +This directory includes primitives for on-chain governance. + +## Governor + +This modular system of Governor contracts allows the deployment on-chain voting protocols similar to [Compound’s Governor Alpha & Bravo](https://compound.finance/docs/governance) and beyond, through the ability to easily customize multiple aspects of the protocol. + + +For a guided experience, set up your Governor contract using [Contracts Wizard](https://wizard.openzeppelin.com/#governor). + +For a written walkthrough, check out our guide on [How to set up on-chain governance](/contracts/5.x/governance). + + +* [`Governor`](#Governor): The core contract that contains all the logic and primitives. It is abstract and requires choosing one of each of the modules below, or custom ones. + +Votes modules determine the source of voting power, and sometimes quorum number. + +* [`GovernorVotes`](#GovernorVotes): Extracts voting weight from an [`IVotes`](#IVotes) contract. +* [`GovernorVotesQuorumFraction`](#GovernorVotesQuorumFraction): Combines with `GovernorVotes` to set the quorum as a fraction of the total token supply. +* [`GovernorVotesSuperQuorumFraction`](#GovernorVotesSuperQuorumFraction): Combines `GovernorSuperQuorum` with `GovernorVotesQuorumFraction` to set the super quorum as a fraction of the total token supply. + +Counting modules determine valid voting options. + +* [`GovernorCountingSimple`](#GovernorCountingSimple): Simple voting mechanism with 3 voting options: Against, For and Abstain. +* [`GovernorCountingFractional`](#GovernorCountingFractional): A more modular voting system that allows a user to vote with only part of its voting power, and to split that weight arbitrarily between the 3 different options (Against, For and Abstain). +* [`GovernorCountingOverridable`](#GovernorCountingOverridable): An extended version of `GovernorCountingSimple` which allows delegatees to override their delegates while the vote is live. Must be used in conjunction with [`VotesExtended`](#VotesExtended). + +Timelock extensions add a delay for governance decisions to be executed. The workflow is extended to require a `queue` step before execution. With these modules, proposals are executed by the external timelock contract, thus it is the timelock that has to hold the assets that are being governed. + +* [`GovernorTimelockAccess`](#GovernorTimelockAccess): Connects with an instance of an [`AccessManager`](/contracts/5.x/api/access#AccessManager). This allows restrictions (and delays) enforced by the manager to be considered by the Governor and integrated into the AccessManager’s "schedule + execute" workflow. +* [`GovernorTimelockControl`](#GovernorTimelockControl): Connects with an instance of [`TimelockController`](#TimelockController). Allows multiple proposers and executors, in addition to the Governor itself. +* [`GovernorTimelockCompound`](#GovernorTimelockCompound): Connects with an instance of Compound’s [`Timelock`](https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol) contract. + +Other extensions can customize the behavior or interface in multiple ways. + +* [`GovernorStorage`](#GovernorStorage): Stores the proposal details onchain and provides enumerability of the proposals. This can be useful for some L2 chains where storage is cheap compared to calldata. +* [`GovernorSettings`](#GovernorSettings): Manages some of the settings (voting delay, voting period duration, and proposal threshold) in a way that can be updated through a governance proposal, without requiring an upgrade. +* [`GovernorPreventLateQuorum`](#GovernorPreventLateQuorum): Ensures there is a minimum voting period after quorum is reached as a security protection against large voters. +* [`GovernorProposalGuardian`](#GovernorProposalGuardian): Adds a proposal guardian that can cancel proposals at any stage in their lifecycle--this permission is passed on to the proposers if the guardian is not set. +* [`GovernorSuperQuorum`](#GovernorSuperQuorum): Extension of [`Governor`](#Governor) with a super quorum. Proposals that meet the super quorum (and have a majority of for votes) advance to the `Succeeded` state before the proposal deadline. +* [`GovernorNoncesKeyed`](#GovernorNoncesKeyed): An extension of [`Governor`](#Governor) with support for keyed nonces in addition to traditional nonces when voting by signature. + +In addition to modules and extensions, the core contract requires a few virtual functions to be implemented to your particular specifications: + +* [`votingDelay()`](#Governor-votingDelay-): Delay (in ERC-6372 clock) since the proposal is submitted until voting power is fixed and voting starts. This can be used to enforce a delay after a proposal is published for users to buy tokens, or delegate their votes. +* [`votingPeriod()`](#Governor-votingPeriod-): Delay (in ERC-6372 clock) since the proposal starts until voting ends. +* [`quorum(uint256 timepoint)`](#Governor-quorum-uint256-): Quorum required for a proposal to be successful. This function includes a `timepoint` argument (see ERC-6372) so the quorum can adapt through time, for example, to follow a token’s `totalSupply`. + + +Functions of the `Governor` contract do not include access control. If you want to restrict access, you should add these checks by overloading the particular functions. Among these, [`Governor._cancel`](#Governor-_cancel-address---uint256---bytes---bytes32-) is internal by default, and you will have to expose it (with the right access control mechanism) yourself if this function is needed. + + +### Core + +[`IGovernor`](#IGovernor) + +[`Governor`](#Governor) + +### Modules + +[`GovernorCountingSimple`](#GovernorCountingSimple) + +[`GovernorCountingFractional`](#GovernorCountingFractional) + +[`GovernorCountingOverridable`](#GovernorCountingOverridable) + +[`GovernorVotes`](#GovernorVotes) + +[`GovernorVotesQuorumFraction`](#GovernorVotesQuorumFraction) + +[`GovernorVotesSuperQuorumFraction`](#GovernorVotesSuperQuorumFraction) + +### Extensions + +[`GovernorTimelockAccess`](#GovernorTimelockAccess) + +[`GovernorTimelockControl`](#GovernorTimelockControl) + +[`GovernorTimelockCompound`](#GovernorTimelockCompound) + +[`GovernorSettings`](#GovernorSettings) + +[`GovernorPreventLateQuorum`](#GovernorPreventLateQuorum) + +[`GovernorStorage`](#GovernorStorage) + +[`GovernorProposalGuardian`](#GovernorProposalGuardian) + +[`GovernorSuperQuorum`](#GovernorSuperQuorum) + +[`GovernorNoncesKeyed`](#GovernorNoncesKeyed) + +## Utils + +[`Votes`](#Votes) + +[`VotesExtended`](#VotesExtended) + +## Timelock + +In a governance system, the [`TimelockController`](#TimelockController) contract is in charge of introducing a delay between a proposal and its execution. It can be used with or without a [`Governor`](#Governor). + +[`TimelockController`](#TimelockController) + +#### Terminology + +* **Operation:** A transaction (or a set of transactions) that is the subject of the timelock. It has to be scheduled by a proposer and executed by an executor. The timelock enforces a minimum delay between the proposition and the execution. If the operation contains multiple transactions (batch mode), they are executed atomically. Operations are identified by the hash of their content. +* **Operation status:** + * **Unset:** An operation that is not part of the timelock mechanism. + * **Waiting:** An operation that has been scheduled, before the timer expires. + * **Ready:** An operation that has been scheduled, after the timer expires. + * **Pending:** An operation that is either waiting or ready. + * **Done:** An operation that has been executed. +* **Predecessor**: An (optional) dependency between operations. An operation can depend on another operation (its predecessor), forcing the execution order of these two operations. +* **Role**: + * **Admin:** An address (smart contract or EOA) that is in charge of granting the roles of Proposer and Executor. + * **Proposer:** An address (smart contract or EOA) that is in charge of scheduling (and cancelling) operations. + * **Executor:** An address (smart contract or EOA) that is in charge of executing operations once the timelock has expired. This role can be given to the zero address to allow anyone to execute operations. + +#### Operation structure + +Operation executed by the [`TimelockController`](contracts/5.x/api/governance#TimelockController) can contain one or multiple subsequent calls. Depending on whether you need to multiple calls to be executed atomically, you can either use simple or batched operations. + +Both operations contain: + +* **Target**, the address of the smart contract that the timelock should operate on. +* **Value**, in wei, that should be sent with the transaction. Most of the time this will be 0. Ether can be deposited before-end or passed along when executing the transaction. +* **Data**, containing the encoded function selector and parameters of the call. This can be produced using a number of tools. For example, a maintenance operation granting role `ROLE` to `ACCOUNT` can be encoded using web3js as follows: + +```javascript +const data = timelock.contract.methods.grantRole(ROLE, ACCOUNT).encodeABI() +``` + +* **Predecessor**, that specifies a dependency between operations. This dependency is optional. Use `bytes32(0)` if the operation does not have any dependency. +* **Salt**, used to disambiguate two otherwise identical operations. This can be any random value. + +In the case of batched operations, `target`, `value` and `data` are specified as arrays, which must be of the same length. + +#### Operation lifecycle + +Timelocked operations are identified by a unique id (their hash) and follow a specific lifecycle: + +`Unset` -> `Pending` -> `Pending` + `Ready` -> `Done` + +* By calling [`schedule`](contracts/5.x/api/governance#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-) (or [`scheduleBatch`](contracts/5.x/api/governance#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-)), a proposer moves the operation from the `Unset` to the `Pending` state. This starts a timer that must be longer than the minimum delay. The timer expires at a timestamp accessible through the [`getTimestamp`](contracts/5.x/api/governance#TimelockController-getTimestamp-bytes32-) method. +* Once the timer expires, the operation automatically gets the `Ready` state. At this point, it can be executed. +* By calling [`execute`](contracts/5.x/api/governance#TimelockController-TimelockController-execute-address-uint256-bytes-bytes32-bytes32-) (or [`executeBatch`](contracts/5.x/api/governance#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-)), an executor triggers the operation’s underlying transactions and moves it to the `Done` state. If the operation has a predecessor, it has to be in the `Done` state for this transition to succeed. +* [`cancel`](contracts/5.x/api/governance#TimelockController-TimelockController-cancel-bytes32-) allows proposers to cancel any `Pending` operation. This resets the operation to the `Unset` state. It is thus possible for a proposer to re-schedule an operation that has been cancelled. In this case, the timer restarts when the operation is rescheduled. + +Operations status can be queried using the functions: + +* [`isOperationPending(bytes32)`](contracts/5.x/api/governance#TimelockController-isOperationPending-bytes32-) +* [`isOperationReady(bytes32)`](contracts/5.x/api/governance#TimelockController-isOperationReady-bytes32-) +* [`isOperationDone(bytes32)`](contracts/5.x/api/governance#TimelockController-isOperationDone-bytes32-) + +#### Roles + +##### Admin + +The admins are in charge of managing proposers and executors. For the timelock to be self-governed, this role should only be given to the timelock itself. Upon deployment, the admin role can be granted to any address (in addition to the timelock itself). After further configuration and testing, this optional admin should renounce its role such that all further maintenance operations have to go through the timelock process. + +##### Proposer + +The proposers are in charge of scheduling (and cancelling) operations. This is a critical role, that should be given to governing entities. This could be an EOA, a multisig, or a DAO. + + +**Proposer fight:** Having multiple proposers, while providing redundancy in case one becomes unavailable, can be dangerous. As proposer have their say on all operations, they could cancel operations they disagree with, including operations to remove them for the proposers. + + +This role is identified by the **PROPOSER_ROLE** value: `0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1` + +##### Executor + +The executors are in charge of executing the operations scheduled by the proposers once the timelock expires. Logic dictates that multisig or DAO that are proposers should also be executors in order to guarantee operations that have been scheduled will eventually be executed. However, having additional executors can reduce the cost (the executing transaction does not require validation by the multisig or DAO that proposed it), while ensuring whoever is in charge of execution cannot trigger actions that have not been scheduled by the proposers. Alternatively, it is possible to allow _any_ address to execute a proposal once the timelock has expired by granting the executor role to the zero address. + +This role is identified by the **EXECUTOR_ROLE** value: `0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63` + + +A live contract without at least one proposer and one executor is locked. Make sure these roles are filled by reliable entities before the deployer renounces its administrative rights in favour of the timelock contract itself. See the [`AccessControl`](/contracts/5.x/api/access#AccessControl) documentation to learn more about role management. + + + + +
+ +## `Governor` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/Governor.sol"; +``` + +Core of the governance system, designed to be extended through various modules. + +This contract is abstract and requires several functions to be implemented in various modules: + +- A counting module must implement [`Governor._quorumReached`](#Governor-_quorumReached-uint256-), [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-) and [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- A voting module must implement [`Governor._getVotes`](#Governor-_getVotes-address-uint256-bytes-) +- Additionally, [`Governor.votingPeriod`](#Governor-votingPeriod--), [`Governor.votingDelay`](#Governor-votingDelay--), and [`Governor.quorum`](#Governor-quorum-uint256-) must also be implemented + +
+

Modifiers

+
+- [onlyGovernance()](#Governor-onlyGovernance--) +
+
+ +
+

Functions

+
+- [constructor(name_)](#Governor-constructor-string-) +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

onlyGovernance()

+
+

internal

+# +
+
+ +
+ +Restricts a function so it can only be executed through governance proposals. For example, governance +parameter setters in [`GovernorSettings`](#GovernorSettings) are protected using this modifier. + +The governance executing address may be different from the Governor's own address, for example it could be a +timelock. This can be customized by modules by overriding [`Governor._executor`](#Governor-_executor--). The executor is only able to invoke these +functions during the execution of the governor's [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) function, and not under any other circumstances. Thus, +for example, additional timelock proposers are not able to change governance parameters without going through the +governance protocol (since v4.6). + +
+
+ + + +
+
+

constructor(string name_)

+
+

internal

+# +
+
+
+ +Sets the value for [`Governor.name`](#Governor-name--) and [`Governor.version`](#Governor-version--) + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +Function to receive ETH that will be handled by the governor (disabled if executor is a third party contract) + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +Name of the governor instance (used in building the EIP-712 domain separator). + +
+
+ + + +
+
+

version() → string

+
+

public

+# +
+
+
+ +Version of the governor instance (used in building the EIP-712 domain separator). Default: "1" + +
+
+ + + +
+
+

hashProposal(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.hashProposal`](#IGovernor-hashProposal-address---uint256---bytes---bytes32-). + +The proposal id is produced by hashing the ABI encoded `targets` array, the `values` array, the `calldatas` array +and the descriptionHash (bytes32 which itself is the keccak256 hash of the description string). This proposal id +can be produced from the proposal data which is part of the [`IGovernor.ProposalCreated`](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) event. It can even be computed in +advance, before the proposal is submitted. + +Note that the chainId and the governor address are not part of the proposal id computation. Consequently, the +same proposal (with same operation and same description) will have the same id if submitted on multiple governors +across multiple networks. This also means that in order to execute the same operation twice (on the same +governor) the proposer will have to change the description in order to avoid proposal id conflicts. + +
+
+ + + +
+
+

getProposalId(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Function used to get the proposal id from the proposal details. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Current state of a proposal, following Compound's convention + +
+
+ + + +
+
+

proposalThreshold() → uint256

+
+

public

+# +
+
+
+ +The number of votes required in order for a voter to become a proposer. + +
+
+ + + +
+
+

proposalSnapshot(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Timepoint used to retrieve user's votes and quorum. If using block number (as per Compound's Comp), the +snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the +following block. + +
+
+ + + +
+
+

proposalDeadline(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is +possible to cast a vote during this block. + +
+
+ + + +
+
+

proposalProposer(uint256 proposalId) → address

+
+

public

+# +
+
+
+ +The account that created a proposal. + +
+
+ + + +
+
+

proposalEta(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +The time when a queued proposal becomes executable ("ETA"). Unlike [`Governor.proposalSnapshot`](#Governor-proposalSnapshot-uint256-) and +[`Governor.proposalDeadline`](#Governor-proposalDeadline-uint256-), this doesn't use the governor clock, and instead relies on the executor's clock which may be +different. In most cases this will be a timestamp. + +
+
+ + + +
+
+

proposalNeedsQueuing(uint256) → bool

+
+

public

+# +
+
+
+ +Whether a proposal needs to be queued before execution. + +
+
+ + + +
+
+

_checkGovernance()

+
+

internal

+# +
+
+
+ +Reverts if the `msg.sender` is not the executor. In case the executor is not this contract +itself, the function reverts if `msg.data` is not whitelisted as a result of an [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) +operation. See [`Governor.onlyGovernance`](#Governor-onlyGovernance--). + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Amount of votes already cast passes the threshold limit. + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Is the proposal successful or not. + +
+
+ + + +
+
+

_getVotes(address account, uint256 timepoint, bytes params) → uint256

+
+

internal

+# +
+
+
+ +Get the voting weight of `account` at a specific `timepoint`, for a vote as described by `params`. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 totalWeight, bytes params) → uint256

+
+

internal

+# +
+
+
+ +Register a vote for `proposalId` by `account` with a given `support`, voting `weight` and voting `params`. + +Note: Support is generic and can represent various things depending on the voting system used. + +
+
+ + + +
+
+

_tallyUpdated(uint256 proposalId)

+
+

internal

+# +
+
+
+ +Hook that should be called every time the tally for a proposal is updated. + +Note: This function must run successfully. Reverts will result in the bricking of governance + +
+
+ + + +
+
+

_defaultParams() → bytes

+
+

internal

+# +
+
+
+ +Default additional encoded parameters used by castVote methods that don't include them + +Note: Should be overridden by specific implementations to use an appropriate value, the +meaning of the additional params, in the context of that implementation + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +See [`IGovernor.propose`](#IGovernor-propose-address---uint256---bytes---string-). This function has opt-in frontrunning protection, described in [`Governor._isValidDescriptionForProposer`](#Governor-_isValidDescriptionForProposer-address-string-). + +
+
+ + + +
+
+

_propose(address[] targets, uint256[] values, bytes[] calldatas, string description, address proposer) → uint256 proposalId

+
+

internal

+# +
+
+
+ +Internal propose mechanism. Can be overridden to add more logic on proposal creation. + +Emits a [`IGovernor.ProposalCreated`](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) event. + +
+
+ + + +
+
+

queue(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Queue a proposal. Some governors require this step to be performed before execution can happen. If queuing +is not necessary, this function may revert. +Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + +Emits a [`IGovernor.ProposalQueued`](#IGovernor-ProposalQueued-uint256-uint256-) event. + +
+
+ + + +
+
+

_queueOperations(uint256, address[], uint256[], bytes[], bytes32) → uint48

+
+

internal

+# +
+
+
+ +Internal queuing mechanism. Can be overridden (without a super call) to modify the way queuing is +performed (for example adding a vault/timelock). + +This is empty by default, and must be overridden to implement queuing. + +This function returns a timestamp that describes the expected ETA for execution. If the returned value is 0 +(which is the default value), the core will consider queueing did not succeed, and the public [`Governor.queue`](#Governor-queue-address---uint256---bytes---bytes32-) function +will revert. + + +Calling this function directly will NOT check the current state of the proposal, or emit the +`ProposalQueued` event. Queuing a proposal should be done using [`Governor.queue`](#Governor-queue-address---uint256---bytes---bytes32-). + + +
+
+ + + +
+
+

execute(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the +deadline to be reached. Depending on the governor it might also be required that the proposal was queued and +that some delay passed. + +Emits a [`IGovernor.ProposalExecuted`](#IGovernor-ProposalExecuted-uint256-) event. + + +Some modules can modify the requirements for execution, for example by adding an additional timelock. + + +
+
+ + + +
+
+

_executeOperations(uint256, address[] targets, uint256[] values, bytes[] calldatas, bytes32)

+
+

internal

+# +
+
+
+ +Internal execution mechanism. Can be overridden (without a super call) to modify the way execution is +performed (for example adding a vault/timelock). + + +Calling this function directly will NOT check the current state of the proposal, set the executed flag to +true or emit the `ProposalExecuted` event. Executing a proposal should be done using [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-). + + +
+
+ + + +
+
+

cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. +before the vote starts. + +Emits a [`IGovernor.ProposalCanceled`](#IGovernor-ProposalCanceled-uint256-) event. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Internal cancel mechanism with minimal restrictions. A proposal can be cancelled in any state other than +Canceled, Expired, or Executed. Once cancelled a proposal can't be re-submitted. + +Emits a [`IGovernor.ProposalCanceled`](#IGovernor-ProposalCanceled-uint256-) event. + +
+
+ + + +
+
+

getVotes(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Voting power of an `account` at a specific `timepoint`. + +Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or +multiple), [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes) tokens. + +
+
+ + + +
+
+

getVotesWithParams(address account, uint256 timepoint, bytes params) → uint256

+
+

public

+# +
+
+
+ +Voting power of an `account` at a specific `timepoint` given additional encoded parameters. + +
+
+ + + +
+
+

castVote(uint256 proposalId, uint8 support) → uint256

+
+

public

+# +
+
+
+ +Cast a vote + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReason(uint256 proposalId, uint8 support, string reason) → uint256

+
+

public

+# +
+
+
+ +Cast a vote with a reason + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReasonAndParams(uint256 proposalId, uint8 support, string reason, bytes params) → uint256

+
+

public

+# +
+
+
+ +Cast a vote with a reason and additional encoded parameters + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) event depending on the length of params. + +
+
+ + + +
+
+

castVoteBySig(uint256 proposalId, uint8 support, address voter, bytes signature) → uint256

+
+

public

+# +
+
+
+ +Cast a vote using the voter's signature, including ERC-1271 signature support. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReasonAndParamsBySig(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature) → uint256

+
+

public

+# +
+
+
+ +Cast a vote with a reason and additional encoded parameters using the voter's signature, +including ERC-1271 signature support. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) event depending on the length of params. + +
+
+ + + +
+
+

_validateVoteSig(uint256 proposalId, uint8 support, address voter, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Validate the `signature` used in [`Governor.castVoteBySig`](#Governor-castVoteBySig-uint256-uint8-address-bytes-) function. + +
+
+ + + +
+
+

_validateExtendedVoteSig(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Validate the `signature` used in [`Governor.castVoteWithReasonAndParamsBySig`](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) function. + +
+
+ + + +
+
+

_castVote(uint256 proposalId, address account, uint8 support, string reason) → uint256

+
+

internal

+# +
+
+
+ +Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve +voting weight using [`IGovernor.getVotes`](#IGovernor-getVotes-address-uint256-) and call the [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) internal function. Uses the _defaultParams(). + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

_castVote(uint256 proposalId, address account, uint8 support, string reason, bytes params) → uint256

+
+

internal

+# +
+
+
+ +Internal vote casting mechanism: Check that the vote is pending, that it has not been cast yet, retrieve +voting weight using [`IGovernor.getVotes`](#IGovernor-getVotes-address-uint256-) and call the [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) internal function. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

relay(address target, uint256 value, bytes data)

+
+

public

+# +
+
+
+ +Relays a transaction or function call to an arbitrary target. In cases where the governance executor +is some contract other than the governor itself, like when using a timelock, this function can be invoked +in a governance proposal to recover tokens or Ether that was sent to the governor contract by mistake. +Note that if the executor is simply the governor itself, use of `relay` is redundant. + +
+
+ + + +
+
+

_executor() → address

+
+

internal

+# +
+
+
+ +Address through which the governor executes action. Will be overloaded by module that executes actions +through another contract such as a timelock. + +
+
+ + + +
+
+

onERC721Received(address, address, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC721Receiver.onERC721Received`](/contracts/5.x/api/token/ERC721#IERC721Receiver-onERC721Received-address-address-uint256-bytes-). +Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + +
+
+ + + +
+
+

onERC1155Received(address, address, uint256, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC1155Receiver.onERC1155Received`](/contracts/5.x/api/token/ERC1155#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-). +Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + +
+
+ + + +
+
+

onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC1155Receiver.onERC1155BatchReceived`](/contracts/5.x/api/token/ERC1155#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-). +Receiving tokens is disabled if the governance executor is other than the governor itself (eg. when using with a timelock). + +
+
+ + + +
+
+

_encodeStateBitmap(enum IGovernor.ProposalState proposalState) → bytes32

+
+

internal

+# +
+
+
+ +Encodes a `ProposalState` into a `bytes32` representation where each bit enabled corresponds to +the underlying position in the `ProposalState` enum. For example: + +0x000...10000 + ^^^^^^------ ... + ^----- Succeeded + ^---- Defeated + ^--- Canceled + ^-- Active + ^- Pending + +
+
+ + + +
+
+

_validateStateBitmap(uint256 proposalId, bytes32 allowedStates) → enum IGovernor.ProposalState

+
+

internal

+# +
+
+
+ +Check that the current state of a proposal matches the requirements described by the `allowedStates` bitmap. +This bitmap should be built using `_encodeStateBitmap`. + +If requirements are not met, reverts with a [`IGovernor.GovernorUnexpectedProposalState`](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) error. + +
+
+ + + +
+
+

_isValidDescriptionForProposer(address proposer, string description) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_validateCancel(uint256 proposalId, address caller) → bool

+
+

internal

+# +
+
+
+ +Check if the `caller` can cancel the proposal with the given `proposalId`. + +The default implementation allows the proposal proposer to cancel the proposal during the pending state. + +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Description of the clock + +
+
+ + + +
+
+

votingDelay() → uint256

+
+

public

+# +
+
+
+ +Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends +on the clock (see ERC-6372) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a +proposal starts. + + +While this interface returns a uint256, timepoints are stored as uint48 following the ERC-6372 clock type. +Consequently this value must fit in a uint48 (when added to the current clock). See [`IERC6372.clock`](/contracts/5.x/api/interfaces#IERC6372-clock--). + + +
+
+ + + +
+
+

votingPeriod() → uint256

+
+

public

+# +
+
+
+ +Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock +(see ERC-6372) this contract uses. + + +The [`Governor.votingDelay`](#Governor-votingDelay--) can delay the start of the vote. This must be considered when setting the voting +duration compared to the voting delay. + + + +This value is stored when the proposal is submitted so that possible changes to the value do not affect +proposals that have already been submitted. The type used to save it is a uint32. Consequently, while this +interface returns a uint256, the value it returns should fit in a uint32. + + +
+
+ + + +
+
+

quorum(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Minimum number of cast voted required for a proposal to be successful. + + +The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows to scale the +quorum depending on values such as the totalSupply of a token at this timepoint (see [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes)). + + +
+
+ + + +
+
+

BALLOT_TYPEHASH() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

EXTENDED_BALLOT_TYPEHASH() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `IGovernor` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/IGovernor.sol"; +``` + +Interface of the [`Governor`](#Governor) core. + + +Event parameters lack the `indexed` keyword for compatibility with GovernorBravo events. +Making event parameters `indexed` affects how events are decoded, potentially breaking existing indexers. + + +
+

Functions

+
+- [name()](#IGovernor-name--) +- [version()](#IGovernor-version--) +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#IGovernor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#IGovernor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#IGovernor-state-uint256-) +- [proposalThreshold()](#IGovernor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#IGovernor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#IGovernor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#IGovernor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#IGovernor-proposalEta-uint256-) +- [proposalNeedsQueuing(proposalId)](#IGovernor-proposalNeedsQueuing-uint256-) +- [votingDelay()](#IGovernor-votingDelay--) +- [votingPeriod()](#IGovernor-votingPeriod--) +- [quorum(timepoint)](#IGovernor-quorum-uint256-) +- [getVotes(account, timepoint)](#IGovernor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#IGovernor-getVotesWithParams-address-uint256-bytes-) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +- [propose(targets, values, calldatas, description)](#IGovernor-propose-address---uint256---bytes---string-) +- [queue(targets, values, calldatas, descriptionHash)](#IGovernor-queue-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#IGovernor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#IGovernor-cancel-address---uint256---bytes---bytes32-) +- [castVote(proposalId, support)](#IGovernor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#IGovernor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#IGovernor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#IGovernor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#IGovernor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +#### IERC6372 [!toc] +- [clock()](#IERC6372-clock--) +- [CLOCK_MODE()](#IERC6372-CLOCK_MODE--) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Name of the governor instance (used in building the EIP-712 domain separator). + +
+
+ + + +
+
+

version() → string

+
+

external

+# +
+
+
+ +Version of the governor instance (used in building the EIP-712 domain separator). Default: "1" + +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

external

+# +
+
+
+ +A description of the possible `support` values for [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + + +The string can be decoded by the standard +[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +JavaScript class. + + +
+
+ + + +
+
+

hashProposal(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

external

+# +
+
+
+ +Hashing function used to (re)build the proposal id from the proposal details. + + +For all off-chain and external calls, use [`Governor.getProposalId`](#Governor-getProposalId-address---uint256---bytes---bytes32-). + + +
+
+ + + +
+
+

getProposalId(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

external

+# +
+
+
+ +Function used to get the proposal id from the proposal details. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

external

+# +
+
+
+ +Current state of a proposal, following Compound's convention + +
+
+ + + +
+
+

proposalThreshold() → uint256

+
+

external

+# +
+
+
+ +The number of votes required in order for a voter to become a proposer. + +
+
+ + + +
+
+

proposalSnapshot(uint256 proposalId) → uint256

+
+

external

+# +
+
+
+ +Timepoint used to retrieve user's votes and quorum. If using block number (as per Compound's Comp), the +snapshot is performed at the end of this block. Hence, voting for this proposal starts at the beginning of the +following block. + +
+
+ + + +
+
+

proposalDeadline(uint256 proposalId) → uint256

+
+

external

+# +
+
+
+ +Timepoint at which votes close. If using block number, votes close at the end of this block, so it is +possible to cast a vote during this block. + +
+
+ + + +
+
+

proposalProposer(uint256 proposalId) → address

+
+

external

+# +
+
+
+ +The account that created a proposal. + +
+
+ + + +
+
+

proposalEta(uint256 proposalId) → uint256

+
+

external

+# +
+
+
+ +The time when a queued proposal becomes executable ("ETA"). Unlike [`Governor.proposalSnapshot`](#Governor-proposalSnapshot-uint256-) and +[`Governor.proposalDeadline`](#Governor-proposalDeadline-uint256-), this doesn't use the governor clock, and instead relies on the executor's clock which may be +different. In most cases this will be a timestamp. + +
+
+ + + +
+
+

proposalNeedsQueuing(uint256 proposalId) → bool

+
+

external

+# +
+
+
+ +Whether a proposal needs to be queued before execution. + +
+
+ + + +
+
+

votingDelay() → uint256

+
+

external

+# +
+
+
+ +Delay, between the proposal is created and the vote starts. The unit this duration is expressed in depends +on the clock (see ERC-6372) this contract uses. + +This can be increased to leave time for users to buy voting power, or delegate it, before the voting of a +proposal starts. + + +While this interface returns a uint256, timepoints are stored as uint48 following the ERC-6372 clock type. +Consequently this value must fit in a uint48 (when added to the current clock). See [`IERC6372.clock`](/contracts/5.x/api/interfaces#IERC6372-clock--). + + +
+
+ + + +
+
+

votingPeriod() → uint256

+
+

external

+# +
+
+
+ +Delay between the vote start and vote end. The unit this duration is expressed in depends on the clock +(see ERC-6372) this contract uses. + + +The [`Governor.votingDelay`](#Governor-votingDelay--) can delay the start of the vote. This must be considered when setting the voting +duration compared to the voting delay. + + + +This value is stored when the proposal is submitted so that possible changes to the value do not affect +proposals that have already been submitted. The type used to save it is a uint32. Consequently, while this +interface returns a uint256, the value it returns should fit in a uint32. + + +
+
+ + + +
+
+

quorum(uint256 timepoint) → uint256

+
+

external

+# +
+
+
+ +Minimum number of cast voted required for a proposal to be successful. + + +The `timepoint` parameter corresponds to the snapshot used for counting vote. This allows to scale the +quorum depending on values such as the totalSupply of a token at this timepoint (see [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes)). + + +
+
+ + + +
+
+

getVotes(address account, uint256 timepoint) → uint256

+
+

external

+# +
+
+
+ +Voting power of an `account` at a specific `timepoint`. + +Note: this can be implemented in a number of ways, for example by reading the delegated balance from one (or +multiple), [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes) tokens. + +
+
+ + + +
+
+

getVotesWithParams(address account, uint256 timepoint, bytes params) → uint256

+
+

external

+# +
+
+
+ +Voting power of an `account` at a specific `timepoint` given additional encoded parameters. + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

external

+# +
+
+
+ +Returns whether `account` has cast a vote on `proposalId`. + +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256 proposalId

+
+

external

+# +
+
+
+ +Create a new proposal. Vote starts after a delay specified by [`IGovernor.votingDelay`](#IGovernor-votingDelay--) and lasts for a +duration specified by [`IGovernor.votingPeriod`](#IGovernor-votingPeriod--). + +Emits a [`IGovernor.ProposalCreated`](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) event. + + +The state of the Governor and `targets` may change between the proposal creation and its execution. +This may be the result of third party actions on the targeted contracts, or other governor proposals. +For example, the balance of this contract could be updated or its access control permissions may be modified, +possibly compromising the proposal's ability to execute successfully (e.g. the governor doesn't have enough +value to cover a proposal with multiple transfers). + + +
+
+ + + +
+
+

queue(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256 proposalId

+
+

external

+# +
+
+
+ +Queue a proposal. Some governors require this step to be performed before execution can happen. If queuing +is not necessary, this function may revert. +Queuing a proposal requires the quorum to be reached, the vote to be successful, and the deadline to be reached. + +Emits a [`IGovernor.ProposalQueued`](#IGovernor-ProposalQueued-uint256-uint256-) event. + +
+
+ + + +
+
+

execute(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256 proposalId

+
+

external

+# +
+
+
+ +Execute a successful proposal. This requires the quorum to be reached, the vote to be successful, and the +deadline to be reached. Depending on the governor it might also be required that the proposal was queued and +that some delay passed. + +Emits a [`IGovernor.ProposalExecuted`](#IGovernor-ProposalExecuted-uint256-) event. + + +Some modules can modify the requirements for execution, for example by adding an additional timelock. + + +
+
+ + + +
+
+

cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256 proposalId

+
+

external

+# +
+
+
+ +Cancel a proposal. A proposal is cancellable by the proposer, but only while it is Pending state, i.e. +before the vote starts. + +Emits a [`IGovernor.ProposalCanceled`](#IGovernor-ProposalCanceled-uint256-) event. + +
+
+ + + +
+
+

castVote(uint256 proposalId, uint8 support) → uint256 balance

+
+

external

+# +
+
+
+ +Cast a vote + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReason(uint256 proposalId, uint8 support, string reason) → uint256 balance

+
+

external

+# +
+
+
+ +Cast a vote with a reason + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReasonAndParams(uint256 proposalId, uint8 support, string reason, bytes params) → uint256 balance

+
+

external

+# +
+
+
+ +Cast a vote with a reason and additional encoded parameters + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) event depending on the length of params. + +
+
+ + + +
+
+

castVoteBySig(uint256 proposalId, uint8 support, address voter, bytes signature) → uint256 balance

+
+

external

+# +
+
+
+ +Cast a vote using the voter's signature, including ERC-1271 signature support. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) event. + +
+
+ + + +
+
+

castVoteWithReasonAndParamsBySig(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature) → uint256 balance

+
+

external

+# +
+
+
+ +Cast a vote with a reason and additional encoded parameters using the voter's signature, +including ERC-1271 signature support. + +Emits a [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) event depending on the length of params. + +
+
+ + + +
+
+

ProposalCreated(uint256 proposalId, address proposer, address[] targets, uint256[] values, string[] signatures, bytes[] calldatas, uint256 voteStart, uint256 voteEnd, string description)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is created. + +
+
+ + +
+
+

ProposalQueued(uint256 proposalId, uint256 etaSeconds)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is queued. + +
+
+ + +
+
+

ProposalExecuted(uint256 proposalId)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is executed. + +
+
+ + +
+
+

ProposalCanceled(uint256 proposalId)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal is canceled. + +
+
+ + +
+
+

VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason)

+
+

event

+# +
+
+ +
+ +Emitted when a vote is cast without params. + +Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. + +
+
+ + +
+
+

VoteCastWithParams(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason, bytes params)

+
+

event

+# +
+
+ +
+ +Emitted when a vote is cast with params. + +Note: `support` values should be seen as buckets. Their interpretation depends on the voting module used. +`params` are additional encoded parameters. Their interpretation also depends on the voting module used. + +
+
+ + + +
+
+

GovernorInvalidProposalLength(uint256 targets, uint256 calldatas, uint256 values)

+
+

error

+# +
+
+
+ +Empty proposal or a mismatch between the parameters length for a proposal call. + +
+
+ + + +
+
+

GovernorAlreadyCastVote(address voter)

+
+

error

+# +
+
+
+ +The vote was already cast. + +
+
+ + + +
+
+

GovernorDisabledDeposit()

+
+

error

+# +
+
+
+ +Token deposits are disabled in this contract. + +
+
+ + + +
+
+

GovernorOnlyExecutor(address account)

+
+

error

+# +
+
+
+ +The `account` is not the governance executor. + +
+
+ + + +
+
+

GovernorNonexistentProposal(uint256 proposalId)

+
+

error

+# +
+
+
+ +The `proposalId` doesn't exist. + +
+
+ + + +
+
+

GovernorUnexpectedProposalState(uint256 proposalId, enum IGovernor.ProposalState current, bytes32 expectedStates)

+
+

error

+# +
+
+
+ +The current state of a proposal is not the required for performing an operation. +The `expectedStates` is a bitmap with the bits enabled for each ProposalState enum position +counting from right to left. + + +If `expectedState` is `bytes32(0)`, the proposal is expected to not be in any state (i.e. not exist). +This is the case when a proposal that is expected to be unset is already initiated (the proposal is duplicated). + + +See [`Governor._encodeStateBitmap`](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-). + +
+
+ + + +
+
+

GovernorInvalidVotingPeriod(uint256 votingPeriod)

+
+

error

+# +
+
+
+ +The voting period set is not a valid period. + +
+
+ + + +
+
+

GovernorInsufficientProposerVotes(address proposer, uint256 votes, uint256 threshold)

+
+

error

+# +
+
+
+ +The `proposer` does not have the required votes to create a proposal. + +
+
+ + + +
+
+

GovernorRestrictedProposer(address proposer)

+
+

error

+# +
+
+
+ +The `proposer` is not allowed to create a proposal. + +
+
+ + + +
+
+

GovernorInvalidVoteType()

+
+

error

+# +
+
+
+ +The vote type used is not valid for the corresponding counting module. + +
+
+ + + +
+
+

GovernorInvalidVoteParams()

+
+

error

+# +
+
+
+ +The provided params buffer is not supported by the counting module. + +
+
+ + + +
+
+

GovernorQueueNotImplemented()

+
+

error

+# +
+
+
+ +Queue operation is not implemented for this governor. Execute should be called directly. + +
+
+ + + +
+
+

GovernorNotQueuedProposal(uint256 proposalId)

+
+

error

+# +
+
+
+ +The proposal hasn't been queued yet. + +
+
+ + + +
+
+

GovernorAlreadyQueuedProposal(uint256 proposalId)

+
+

error

+# +
+
+
+ +The proposal has already been queued. + +
+
+ + + +
+
+

GovernorInvalidSignature(address voter)

+
+

error

+# +
+
+
+ +The provided signature is not valid for the expected `voter`. +If the `voter` is a contract, the signature is not valid using [`IERC1271.isValidSignature`](/contracts/5.x/api/interfaces#IERC1271-isValidSignature-bytes32-bytes-). + +
+
+ + + +
+
+

GovernorUnableToCancel(uint256 proposalId, address account)

+
+

error

+# +
+
+
+ +The given `account` is unable to cancel the proposal with given `proposalId`. + +
+
+ + + +
+ +## `TimelockController` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/TimelockController.sol"; +``` + +Contract module which acts as a timelocked controller. When set as the +owner of an `Ownable` smart contract, it enforces a timelock on all +`onlyOwner` maintenance operations. This gives time for users of the +controlled contract to exit before a potentially dangerous maintenance +operation is applied. + +By default, this contract is self administered, meaning administration tasks +have to go through the timelock process. The proposer (resp executor) role +is in charge of proposing (resp executing) operations. A common use case is +to position this [`TimelockController`](#TimelockController) as the owner of a smart contract, with +a multisig or a DAO as the sole proposer. + +
+

Modifiers

+
+- [onlyRoleOrOpenRole(role)](#TimelockController-onlyRoleOrOpenRole-bytes32-) +
+
+ +
+

Functions

+
+- [constructor(minDelay, proposers, executors, admin)](#TimelockController-constructor-uint256-address---address---address-) +- [receive()](#TimelockController-receive--) +- [supportsInterface(interfaceId)](#TimelockController-supportsInterface-bytes4-) +- [isOperation(id)](#TimelockController-isOperation-bytes32-) +- [isOperationPending(id)](#TimelockController-isOperationPending-bytes32-) +- [isOperationReady(id)](#TimelockController-isOperationReady-bytes32-) +- [isOperationDone(id)](#TimelockController-isOperationDone-bytes32-) +- [getTimestamp(id)](#TimelockController-getTimestamp-bytes32-) +- [getOperationState(id)](#TimelockController-getOperationState-bytes32-) +- [getMinDelay()](#TimelockController-getMinDelay--) +- [hashOperation(target, value, data, predecessor, salt)](#TimelockController-hashOperation-address-uint256-bytes-bytes32-bytes32-) +- [hashOperationBatch(targets, values, payloads, predecessor, salt)](#TimelockController-hashOperationBatch-address---uint256---bytes---bytes32-bytes32-) +- [schedule(target, value, data, predecessor, salt, delay)](#TimelockController-schedule-address-uint256-bytes-bytes32-bytes32-uint256-) +- [scheduleBatch(targets, values, payloads, predecessor, salt, delay)](#TimelockController-scheduleBatch-address---uint256---bytes---bytes32-bytes32-uint256-) +- [cancel(id)](#TimelockController-cancel-bytes32-) +- [execute(target, value, payload, predecessor, salt)](#TimelockController-execute-address-uint256-bytes-bytes32-bytes32-) +- [executeBatch(targets, values, payloads, predecessor, salt)](#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-) +- [_execute(target, value, data)](#TimelockController-_execute-address-uint256-bytes-) +- [updateDelay(newDelay)](#TimelockController-updateDelay-uint256-) +- [_encodeStateBitmap(operationState)](#TimelockController-_encodeStateBitmap-enum-TimelockController-OperationState-) +- [PROPOSER_ROLE()](#TimelockController-PROPOSER_ROLE-bytes32) +- [EXECUTOR_ROLE()](#TimelockController-EXECUTOR_ROLE-bytes32) +- [CANCELLER_ROLE()](#TimelockController-CANCELLER_ROLE-bytes32) +#### ERC1155Holder [!toc] +- [onERC1155Received(, , , , )](#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +#### IERC1155Receiver [!toc] +#### ERC721Holder [!toc] +- [onERC721Received(, , , )](#ERC721Holder-onERC721Received-address-address-uint256-bytes-) +#### IERC721Receiver [!toc] +#### AccessControl [!toc] +- [hasRole(role, account)](#AccessControl-hasRole-bytes32-address-) +- [_checkRole(role)](#AccessControl-_checkRole-bytes32-) +- [_checkRole(role, account)](#AccessControl-_checkRole-bytes32-address-) +- [getRoleAdmin(role)](#AccessControl-getRoleAdmin-bytes32-) +- [grantRole(role, account)](#AccessControl-grantRole-bytes32-address-) +- [revokeRole(role, account)](#AccessControl-revokeRole-bytes32-address-) +- [renounceRole(role, callerConfirmation)](#AccessControl-renounceRole-bytes32-address-) +- [_setRoleAdmin(role, adminRole)](#AccessControl-_setRoleAdmin-bytes32-bytes32-) +- [_grantRole(role, account)](#AccessControl-_grantRole-bytes32-address-) +- [_revokeRole(role, account)](#AccessControl-_revokeRole-bytes32-address-) +- [DEFAULT_ADMIN_ROLE()](#AccessControl-DEFAULT_ADMIN_ROLE-bytes32) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControl [!toc] +
+
+ +
+

Events

+
+- [CallScheduled(id, index, target, value, data, predecessor, delay)](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-) +- [CallExecuted(id, index, target, value, data)](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) +- [CallSalt(id, salt)](#TimelockController-CallSalt-bytes32-bytes32-) +- [Cancelled(id)](#TimelockController-Cancelled-bytes32-) +- [MinDelayChange(oldDuration, newDuration)](#TimelockController-MinDelayChange-uint256-uint256-) +#### ERC1155Holder [!toc] +#### IERC1155Receiver [!toc] +#### ERC721Holder [!toc] +#### IERC721Receiver [!toc] +#### AccessControl [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControl [!toc] +- [RoleAdminChanged(role, previousAdminRole, newAdminRole)](#IAccessControl-RoleAdminChanged-bytes32-bytes32-bytes32-) +- [RoleGranted(role, account, sender)](#IAccessControl-RoleGranted-bytes32-address-address-) +- [RoleRevoked(role, account, sender)](#IAccessControl-RoleRevoked-bytes32-address-address-) +
+
+ +
+

Errors

+
+- [TimelockInvalidOperationLength(targets, payloads, values)](#TimelockController-TimelockInvalidOperationLength-uint256-uint256-uint256-) +- [TimelockInsufficientDelay(delay, minDelay)](#TimelockController-TimelockInsufficientDelay-uint256-uint256-) +- [TimelockUnexpectedOperationState(operationId, expectedStates)](#TimelockController-TimelockUnexpectedOperationState-bytes32-bytes32-) +- [TimelockUnexecutedPredecessor(predecessorId)](#TimelockController-TimelockUnexecutedPredecessor-bytes32-) +- [TimelockUnauthorizedCaller(caller)](#TimelockController-TimelockUnauthorizedCaller-address-) +#### ERC1155Holder [!toc] +#### IERC1155Receiver [!toc] +#### ERC721Holder [!toc] +#### IERC721Receiver [!toc] +#### AccessControl [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IAccessControl [!toc] +- [AccessControlUnauthorizedAccount(account, neededRole)](#IAccessControl-AccessControlUnauthorizedAccount-address-bytes32-) +- [AccessControlBadConfirmation()](#IAccessControl-AccessControlBadConfirmation--) +
+
+ + + +
+
+

onlyRoleOrOpenRole(bytes32 role)

+
+

internal

+# +
+
+ +
+ +Modifier to make a function callable only by a certain role. In +addition to checking the sender's role, `address(0)` 's role is also +considered. Granting a role to `address(0)` is equivalent to enabling +this role for everyone. + +
+
+ + + +
+
+

constructor(uint256 minDelay, address[] proposers, address[] executors, address admin)

+
+

public

+# +
+
+
+ +Initializes the contract with the following parameters: + +- `minDelay`: initial minimum delay in seconds for operations +- `proposers`: accounts to be granted proposer and canceller roles +- `executors`: accounts to be granted executor role +- `admin`: optional account to be granted admin role; disable with zero address + + +The optional admin can aid with initial configuration of roles after deployment +without being subject to delay, but this role should be subsequently renounced in favor of +administration through timelocked proposals. Previous versions of this contract would assign +this admin to the deployer automatically and should be renounced as well. + + +
+
+ + + +
+
+

receive()

+
+

external

+# +
+
+
+ +Contract might receive/hold ETH as part of the maintenance process. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

isOperation(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an id corresponds to a registered operation. This +includes both Waiting, Ready, and Done operations. + +
+
+ + + +
+
+

isOperationPending(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an operation is pending or not. Note that a "pending" operation may also be "ready". + +
+
+ + + +
+
+

isOperationReady(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an operation is ready for execution. Note that a "ready" operation is also "pending". + +
+
+ + + +
+
+

isOperationDone(bytes32 id) → bool

+
+

public

+# +
+
+
+ +Returns whether an operation is done or not. + +
+
+ + + +
+
+

getTimestamp(bytes32 id) → uint256

+
+

public

+# +
+
+
+ +Returns the timestamp at which an operation becomes ready (0 for +unset operations, 1 for done operations). + +
+
+ + + +
+
+

getOperationState(bytes32 id) → enum TimelockController.OperationState

+
+

public

+# +
+
+
+ +Returns operation state. + +
+
+ + + +
+
+

getMinDelay() → uint256

+
+

public

+# +
+
+
+ +Returns the minimum delay in seconds for an operation to become valid. + +This value can be changed by executing an operation that calls `updateDelay`. + +
+
+ + + +
+
+

hashOperation(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt) → bytes32

+
+

public

+# +
+
+
+ +Returns the identifier of an operation containing a single +transaction. + +
+
+ + + +
+
+

hashOperationBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt) → bytes32

+
+

public

+# +
+
+
+ +Returns the identifier of an operation containing a batch of +transactions. + +
+
+ + + +
+
+

schedule(address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt, uint256 delay)

+
+

public

+# +
+
+
+ +Schedule an operation containing a single transaction. + +Emits [`TimelockController.CallSalt`](#TimelockController-CallSalt-bytes32-bytes32-) if salt is nonzero, and [`TimelockController.CallScheduled`](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-). + +Requirements: + +- the caller must have the 'proposer' role. + +
+
+ + + +
+
+

scheduleBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt, uint256 delay)

+
+

public

+# +
+
+
+ +Schedule an operation containing a batch of transactions. + +Emits [`TimelockController.CallSalt`](#TimelockController-CallSalt-bytes32-bytes32-) if salt is nonzero, and one [`TimelockController.CallScheduled`](#TimelockController-CallScheduled-bytes32-uint256-address-uint256-bytes-bytes32-uint256-) event per transaction in the batch. + +Requirements: + +- the caller must have the 'proposer' role. + +
+
+ + + +
+
+

cancel(bytes32 id)

+
+

public

+# +
+
+
+ +Cancel an operation. + +Requirements: + +- the caller must have the 'canceller' role. + +
+
+ + + +
+
+

execute(address target, uint256 value, bytes payload, bytes32 predecessor, bytes32 salt)

+
+

public

+# +
+
+
+ +Execute a ready operation containing a single transaction. + +Emits a [`TimelockController.CallExecuted`](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) event. + +Requirements: + +- the caller must have the 'executor' role. + +
+
+ + + +
+
+

executeBatch(address[] targets, uint256[] values, bytes[] payloads, bytes32 predecessor, bytes32 salt)

+
+

public

+# +
+
+
+ +Execute a ready operation containing a batch of transactions. + +Emits one [`TimelockController.CallExecuted`](#TimelockController-CallExecuted-bytes32-uint256-address-uint256-bytes-) event per transaction in the batch. + +Requirements: + +- the caller must have the 'executor' role. + +
+
+ + + +
+
+

_execute(address target, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Execute an operation's call. + +
+
+ + + +
+
+

updateDelay(uint256 newDelay)

+
+

public

+# +
+
+
+ +Changes the minimum timelock duration for future operations. + +Emits a [`TimelockController.MinDelayChange`](#TimelockController-MinDelayChange-uint256-uint256-) event. + +Requirements: + +- the caller must be the timelock itself. This can only be achieved by scheduling and later executing +an operation where the timelock is the target and the data is the ABI-encoded call to this function. + +
+
+ + + +
+
+

_encodeStateBitmap(enum TimelockController.OperationState operationState) → bytes32

+
+

internal

+# +
+
+
+ +Encodes a `OperationState` into a `bytes32` representation where each bit enabled corresponds to +the underlying position in the `OperationState` enum. For example: + +0x000...1000 + ^^^^^^----- ... + ^---- Done + ^--- Ready + ^-- Waiting + ^- Unset + +
+
+ + + +
+
+

PROPOSER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

EXECUTOR_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

CANCELLER_ROLE() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

CallScheduled(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data, bytes32 predecessor, uint256 delay)

+
+

event

+# +
+
+ +
+ +Emitted when a call is scheduled as part of operation `id`. + +
+
+ + +
+
+

CallExecuted(bytes32 indexed id, uint256 indexed index, address target, uint256 value, bytes data)

+
+

event

+# +
+
+ +
+ +Emitted when a call is performed as part of operation `id`. + +
+
+ + +
+
+

CallSalt(bytes32 indexed id, bytes32 salt)

+
+

event

+# +
+
+ +
+ +Emitted when new proposal is scheduled with non-zero salt. + +
+
+ + +
+
+

Cancelled(bytes32 indexed id)

+
+

event

+# +
+
+ +
+ +Emitted when operation `id` is cancelled. + +
+
+ + +
+
+

MinDelayChange(uint256 oldDuration, uint256 newDuration)

+
+

event

+# +
+
+ +
+ +Emitted when the minimum delay for future operations is modified. + +
+
+ + + +
+
+

TimelockInvalidOperationLength(uint256 targets, uint256 payloads, uint256 values)

+
+

error

+# +
+
+
+ +Mismatch between the parameters length for an operation call. + +
+
+ + + +
+
+

TimelockInsufficientDelay(uint256 delay, uint256 minDelay)

+
+

error

+# +
+
+
+ +The schedule operation doesn't meet the minimum delay. + +
+
+ + + +
+
+

TimelockUnexpectedOperationState(bytes32 operationId, bytes32 expectedStates)

+
+

error

+# +
+
+
+ +The current state of an operation is not as required. +The `expectedStates` is a bitmap with the bits enabled for each OperationState enum position +counting from right to left. + +See [`Governor._encodeStateBitmap`](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-). + +
+
+ + + +
+
+

TimelockUnexecutedPredecessor(bytes32 predecessorId)

+
+

error

+# +
+
+
+ +The predecessor to an operation not yet done. + +
+
+ + + +
+
+

TimelockUnauthorizedCaller(address caller)

+
+

error

+# +
+
+
+ +The caller account is not authorized. + +
+
+ + + +
+ +## `GovernorCountingFractional` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorCountingFractional.sol"; +``` + +Extension of [`Governor`](#Governor) for fractional voting. + +Similar to [`GovernorCountingSimple`](#GovernorCountingSimple), this contract is a votes counting module for [`Governor`](#Governor) that supports 3 options: +Against, For, Abstain. Additionally, it includes a fourth option: Fractional, which allows voters to split their voting +power amongst the other 3 options. + +Votes cast with the Fractional support must be accompanied by a `params` argument that is three packed `uint128` values +representing the weight the delegate assigns to Against, For, and Abstain respectively. For those votes cast for the other +3 options, the `params` argument must be empty. + +This is mostly useful when the delegate is a contract that implements its own rules for voting. These delegate-contracts +can cast fractional votes according to the preferences of multiple entities delegating their voting power. + +Some example use cases include: + +* Voting from tokens that are held by a DeFi pool +* Voting from an L2 with tokens held by a bridge +* Voting privately from a shielded pool using zero knowledge proofs. + +Based on ScopeLift's [`GovernorCountingFractional`](https://github.com/ScopeLift/flexible-voting/blob/e5de2efd1368387b840931f19f3c184c85842761/src/GovernorCountingFractional.sol) + +_Available since v5.1._ + +
+

Functions

+
+- [COUNTING_MODE()](#GovernorCountingFractional-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#GovernorCountingFractional-hasVoted-uint256-address-) +- [usedVotes(proposalId, account)](#GovernorCountingFractional-usedVotes-uint256-address-) +- [proposalVotes(proposalId)](#GovernorCountingFractional-proposalVotes-uint256-) +- [_quorumReached(proposalId)](#GovernorCountingFractional-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#GovernorCountingFractional-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, totalWeight, params)](#GovernorCountingFractional-_countVote-uint256-address-uint8-uint256-bytes-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorExceedRemainingWeight(voter, usedVotes, remainingWeight)](#GovernorCountingFractional-GovernorExceedRemainingWeight-address-uint256-uint256-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

public

+# +
+
+
+ +A description of the possible `support` values for [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + + +The string can be decoded by the standard +[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +JavaScript class. + + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +Returns whether `account` has cast a vote on `proposalId`. + +
+
+ + + +
+
+

usedVotes(uint256 proposalId, address account) → uint256

+
+

public

+# +
+
+
+ +Get the number of votes already cast by `account` for a proposal with `proposalId`. Useful for +integrations that allow delegates to cast rolling, partial votes. + +
+
+ + + +
+
+

proposalVotes(uint256 proposalId) → uint256 againstVotes, uint256 forVotes, uint256 abstainVotes

+
+

public

+# +
+
+
+ +Get current distribution of votes for a given proposal. + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Amount of votes already cast passes the threshold limit. + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-). In this module, forVotes must be > againstVotes. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 totalWeight, bytes params) → uint256

+
+

internal

+# +
+
+
+ +See [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-). Function that records the delegate's votes. + +Executing this function consumes (part of) the delegate's weight on the proposal. This weight can be +distributed amongst the 3 options (Against, For, Abstain) by specifying a fractional `support`. + +This counting module supports two vote casting modes: nominal and fractional. + +- Nominal: A nominal vote is cast by setting `support` to one of the 3 bravo options (Against, For, Abstain). +- Fractional: A fractional vote is cast by setting `support` to `type(uint8).max` (255). + +Casting a nominal vote requires `params` to be empty and consumes the delegate's full remaining weight on the +proposal for the specified `support` option. This is similar to the [`GovernorCountingSimple`](#GovernorCountingSimple) module and follows +the `VoteType` enum from Governor Bravo. As a consequence, no vote weight remains unspent so no further voting +is possible (for this `proposalId` and this `account`). + +Casting a fractional vote consumes a fraction of the delegate's remaining weight on the proposal according to the +weights the delegate assigns to each support option (Against, For, Abstain respectively). The sum total of the +three decoded vote weights _must_ be less than or equal to the delegate's remaining weight on the proposal (i.e. +their checkpointed total weight minus votes already cast on the proposal). This format can be produced using: + +`abi.encodePacked(uint128(againstVotes), uint128(forVotes), uint128(abstainVotes))` + + +Consider that fractional voting restricts the number of casted votes (in each category) to 128 bits. +Depending on how many decimals the underlying token has, a single voter may require to split their vote into +multiple vote operations. For precision higher than ~30 decimals, large token holders may require a +potentially large number of calls to cast all their votes. The voter has the possibility to cast all the +remaining votes in a single operation using the traditional "bravo" vote. + + +
+
+ + + +
+
+

GovernorExceedRemainingWeight(address voter, uint256 usedVotes, uint256 remainingWeight)

+
+

error

+# +
+
+
+ +A fractional vote params uses more votes than are available for that user. + +
+
+ + + +
+ +## `GovernorCountingOverridable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorCountingOverridable.sol"; +``` + +Extension of [`Governor`](#Governor) which enables delegators to override the vote of their delegates. This module requires a +token that inherits [`VotesExtended`](#VotesExtended). + +
+

Functions

+
+- [COUNTING_MODE()](#GovernorCountingOverridable-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#GovernorCountingOverridable-hasVoted-uint256-address-) +- [hasVotedOverride(proposalId, account)](#GovernorCountingOverridable-hasVotedOverride-uint256-address-) +- [proposalVotes(proposalId)](#GovernorCountingOverridable-proposalVotes-uint256-) +- [_quorumReached(proposalId)](#GovernorCountingOverridable-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#GovernorCountingOverridable-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, totalWeight, )](#GovernorCountingOverridable-_countVote-uint256-address-uint8-uint256-bytes-) +- [_countOverride(proposalId, account, support)](#GovernorCountingOverridable-_countOverride-uint256-address-uint8-) +- [_castOverride(proposalId, account, support, reason)](#GovernorCountingOverridable-_castOverride-uint256-address-uint8-string-) +- [castOverrideVote(proposalId, support, reason)](#GovernorCountingOverridable-castOverrideVote-uint256-uint8-string-) +- [castOverrideVoteBySig(proposalId, support, voter, reason, signature)](#GovernorCountingOverridable-castOverrideVoteBySig-uint256-uint8-address-string-bytes-) +- [OVERRIDE_BALLOT_TYPEHASH()](#GovernorCountingOverridable-OVERRIDE_BALLOT_TYPEHASH-bytes32) +#### GovernorVotes [!toc] +- [token()](#GovernorVotes-token--) +- [clock()](#GovernorVotes-clock--) +- [CLOCK_MODE()](#GovernorVotes-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotes-_getVotes-address-uint256-bytes-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [VoteReduced(delegate, proposalId, support, weight)](#GovernorCountingOverridable-VoteReduced-address-uint256-uint8-uint256-) +- [OverrideVoteCast(voter, proposalId, support, weight, reason)](#GovernorCountingOverridable-OverrideVoteCast-address-uint256-uint8-uint256-string-) +#### GovernorVotes [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorAlreadyOverriddenVote(account)](#GovernorCountingOverridable-GovernorAlreadyOverriddenVote-address-) +#### GovernorVotes [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

public

+# +
+
+
+ +A description of the possible `support` values for [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + + +The string can be decoded by the standard +[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +JavaScript class. + + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +See [`IGovernor.hasVoted`](#IGovernor-hasVoted-uint256-address-). + + +Calling [`Governor.castVote`](#Governor-castVote-uint256-uint8-) (or similar) casts a vote using the voting power that is delegated to the voter. +Conversely, calling [`GovernorCountingOverridable.castOverrideVote`](#GovernorCountingOverridable-castOverrideVote-uint256-uint8-string-) (or similar) uses the voting power of the account itself, from its asset +balances. Casting an "override vote" does not count as voting and won't be reflected by this getter. Consider +using [`GovernorCountingOverridable.hasVotedOverride`](#GovernorCountingOverridable-hasVotedOverride-uint256-address-) to check if an account has casted an "override vote" for a given proposal id. + + +
+
+ + + +
+
+

hasVotedOverride(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +Check if an `account` has overridden their delegate for a proposal. + +
+
+ + + +
+
+

proposalVotes(uint256 proposalId) → uint256 againstVotes, uint256 forVotes, uint256 abstainVotes

+
+

public

+# +
+
+
+ +Accessor to the internal vote counts. + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Amount of votes already cast passes the threshold limit. + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-). In this module, the forVotes must be strictly over the againstVotes. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 totalWeight, bytes) → uint256

+
+

internal

+# +
+
+
+ +See [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-). In this module, the support follows the `VoteType` enum (from Governor Bravo). + + +called by [`Governor._castVote`](#Governor-_castVote-uint256-address-uint8-string-bytes-) which emits the [`IGovernor.VoteCast`](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) (or [`IGovernor.VoteCastWithParams`](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-)) +event. + + +
+
+ + + +
+
+

_countOverride(uint256 proposalId, address account, uint8 support) → uint256

+
+

internal

+# +
+
+
+ +Variant of [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) that deals with vote overrides. + + +See [`IGovernor.hasVoted`](#IGovernor-hasVoted-uint256-address-) for more details about the difference between [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and [`GovernorCountingOverridable.castOverrideVote`](#GovernorCountingOverridable-castOverrideVote-uint256-uint8-string-). + + +
+
+ + + +
+
+

_castOverride(uint256 proposalId, address account, uint8 support, string reason) → uint256

+
+

internal

+# +
+
+
+ +Variant of [`Governor._castVote`](#Governor-_castVote-uint256-address-uint8-string-bytes-) that deals with vote overrides. Returns the overridden weight. + +
+
+ + + +
+
+

castOverrideVote(uint256 proposalId, uint8 support, string reason) → uint256

+
+

public

+# +
+
+
+ +Public function for casting an override vote. Returns the overridden weight. + +
+
+ + + +
+
+

castOverrideVoteBySig(uint256 proposalId, uint8 support, address voter, string reason, bytes signature) → uint256

+
+

public

+# +
+
+
+ +Public function for casting an override vote using a voter's signature. Returns the overridden weight. + +
+
+ + + +
+
+

OVERRIDE_BALLOT_TYPEHASH() → bytes32

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

VoteReduced(address indexed delegate, uint256 proposalId, uint8 support, uint256 weight)

+
+

event

+# +
+
+ +
+ +The votes casted by `delegate` were reduced by `weight` after an override vote was casted by the original token holder + +
+
+ + +
+
+

OverrideVoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason)

+
+

event

+# +
+
+ +
+ +A delegated vote on `proposalId` was overridden by `weight` + +
+
+ + + +
+
+

GovernorAlreadyOverriddenVote(address account)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `GovernorCountingSimple` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol"; +``` + +Extension of [`Governor`](#Governor) for simple, 3 options, vote counting. + +
+

Functions

+
+- [COUNTING_MODE()](#GovernorCountingSimple-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#GovernorCountingSimple-hasVoted-uint256-address-) +- [proposalVotes(proposalId)](#GovernorCountingSimple-proposalVotes-uint256-) +- [_quorumReached(proposalId)](#GovernorCountingSimple-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#GovernorCountingSimple-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, totalWeight, )](#GovernorCountingSimple-_countVote-uint256-address-uint8-uint256-bytes-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

COUNTING_MODE() → string

+
+

public

+# +
+
+
+ +A description of the possible `support` values for [`Governor.castVote`](#Governor-castVote-uint256-uint8-) and the way these votes are counted, meant to +be consumed by UIs to show correct vote options and interpret the results. The string is a URL-encoded sequence of +key-value pairs that each describe one aspect, for example `support=bravo&quorum=for,abstain`. + +There are 2 standard keys: `support` and `quorum`. + +- `support=bravo` refers to the vote options 0 = Against, 1 = For, 2 = Abstain, as in `GovernorBravo`. +- `quorum=bravo` means that only For votes are counted towards quorum. +- `quorum=for,abstain` means that both For and Abstain votes are counted towards quorum. + +If a counting module makes use of encoded `params`, it should include this under a `params` key with a unique +name that describes the behavior. For example: + +- `params=fractional` might refer to a scheme where votes are divided fractionally between for/against/abstain. +- `params=erc721` might refer to a scheme where specific NFTs are delegated to vote. + + +The string can be decoded by the standard +[`URLSearchParams`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) +JavaScript class. + + +
+
+ + + +
+
+

hasVoted(uint256 proposalId, address account) → bool

+
+

public

+# +
+
+
+ +Returns whether `account` has cast a vote on `proposalId`. + +
+
+ + + +
+
+

proposalVotes(uint256 proposalId) → uint256 againstVotes, uint256 forVotes, uint256 abstainVotes

+
+

public

+# +
+
+
+ +Accessor to the internal vote counts. + +
+
+ + + +
+
+

_quorumReached(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +Amount of votes already cast passes the threshold limit. + +
+
+ + + +
+
+

_voteSucceeded(uint256 proposalId) → bool

+
+

internal

+# +
+
+
+ +See [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-). In this module, the forVotes must be strictly over the againstVotes. + +
+
+ + + +
+
+

_countVote(uint256 proposalId, address account, uint8 support, uint256 totalWeight, bytes) → uint256

+
+

internal

+# +
+
+
+ +See [`Governor._countVote`](#Governor-_countVote-uint256-address-uint8-uint256-bytes-). In this module, the support follows the `VoteType` enum (from Governor Bravo). + +
+
+ + + +
+ +## `GovernorNoncesKeyed` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorNoncesKeyed.sol"; +``` + +An extension of [`Governor`](#Governor) that extends existing nonce management to use [`NoncesKeyed`](/contracts/5.x/api/utils#NoncesKeyed), where the key is the low-order 192 bits of the `proposalId`. +This is useful for voting by signature while maintaining separate sequences of nonces for each proposal. + + +Traditional (un-keyed) nonces are still supported and can continue to be used as if this extension was not present. + + +
+

Functions

+
+- [_useCheckedNonce(owner, nonce)](#GovernorNoncesKeyed-_useCheckedNonce-address-uint256-) +- [_validateVoteSig(proposalId, support, voter, signature)](#GovernorNoncesKeyed-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#GovernorNoncesKeyed-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +#### NoncesKeyed [!toc] +- [nonces(owner, key)](#NoncesKeyed-nonces-address-uint192-) +- [_useNonce(owner, key)](#NoncesKeyed-_useNonce-address-uint192-) +- [_useCheckedNonce(owner, key, nonce)](#NoncesKeyed-_useCheckedNonce-address-uint192-uint64-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### NoncesKeyed [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### NoncesKeyed [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

_useCheckedNonce(address owner, uint256 nonce)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_validateVoteSig(uint256 proposalId, uint8 support, address voter, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Check the signature against keyed nonce and falls back to the traditional nonce. + + +This function won't call `super._validateVoteSig` if the keyed nonce is valid. +Side effects may be skipped depending on the linearization of the function. + + +
+
+ + + +
+
+

_validateExtendedVoteSig(uint256 proposalId, uint8 support, address voter, string reason, bytes params, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Check the signature against keyed nonce and falls back to the traditional nonce. + + +This function won't call `super._validateExtendedVoteSig` if the keyed nonce is valid. +Side effects may be skipped depending on the linearization of the function. + + +
+
+ + + +
+ +## `GovernorPreventLateQuorum` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorPreventLateQuorum.sol"; +``` + +A module that ensures there is a minimum voting period after quorum is reached. This prevents a large voter from +swaying a vote and triggering quorum at the last minute, by ensuring there is always time for other voters to react +and try to oppose the decision. + +If a vote causes quorum to be reached, the proposal's voting period may be extended so that it does not end before at +least a specified time has passed (the "vote extension" parameter). This parameter can be set through a governance +proposal. + +
+

Functions

+
+- [constructor(initialVoteExtension)](#GovernorPreventLateQuorum-constructor-uint48-) +- [proposalDeadline(proposalId)](#GovernorPreventLateQuorum-proposalDeadline-uint256-) +- [_tallyUpdated(proposalId)](#GovernorPreventLateQuorum-_tallyUpdated-uint256-) +- [lateQuorumVoteExtension()](#GovernorPreventLateQuorum-lateQuorumVoteExtension--) +- [setLateQuorumVoteExtension(newVoteExtension)](#GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-) +- [_setLateQuorumVoteExtension(newVoteExtension)](#GovernorPreventLateQuorum-_setLateQuorumVoteExtension-uint48-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [ProposalExtended(proposalId, extendedDeadline)](#GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-) +- [LateQuorumVoteExtensionSet(oldVoteExtension, newVoteExtension)](#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(uint48 initialVoteExtension)

+
+

internal

+# +
+
+
+ +Initializes the vote extension parameter: the time in either number of blocks or seconds (depending on the +governor clock mode) that is required to pass since the moment a proposal reaches quorum until its voting period +ends. If necessary the voting period will be extended beyond the one set during proposal creation. + +
+
+ + + +
+
+

proposalDeadline(uint256 proposalId) → uint256

+
+

public

+# +
+
+
+ +Returns the proposal deadline, which may have been extended beyond that set at proposal creation, if the +proposal reached quorum late in the voting period. See [`Governor.proposalDeadline`](#Governor-proposalDeadline-uint256-). + +
+
+ + + +
+
+

_tallyUpdated(uint256 proposalId)

+
+

internal

+# +
+
+
+ +Vote tally updated and detects if it caused quorum to be reached, potentially extending the voting period. + +May emit a [`GovernorPreventLateQuorum.ProposalExtended`](#GovernorPreventLateQuorum-ProposalExtended-uint256-uint64-) event. + +
+
+ + + +
+
+

lateQuorumVoteExtension() → uint48

+
+

public

+# +
+
+
+ +Returns the current value of the vote extension parameter: the number of blocks that are required to pass +from the time a proposal reaches quorum until its voting period ends. + +
+
+ + + +
+
+

setLateQuorumVoteExtension(uint48 newVoteExtension)

+
+

public

+# +
+
+
+ +Changes the [`GovernorPreventLateQuorum.lateQuorumVoteExtension`](#GovernorPreventLateQuorum-lateQuorumVoteExtension--). This operation can only be performed by the governance executor, +generally through a governance proposal. + +Emits a [`GovernorPreventLateQuorum.LateQuorumVoteExtensionSet`](#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-) event. + +
+
+ + + +
+
+

_setLateQuorumVoteExtension(uint48 newVoteExtension)

+
+

internal

+# +
+
+
+ +Changes the [`GovernorPreventLateQuorum.lateQuorumVoteExtension`](#GovernorPreventLateQuorum-lateQuorumVoteExtension--). This is an internal function that can be exposed in a public function +like [`GovernorPreventLateQuorum.setLateQuorumVoteExtension`](#GovernorPreventLateQuorum-setLateQuorumVoteExtension-uint48-) if another access control mechanism is needed. + +Emits a [`GovernorPreventLateQuorum.LateQuorumVoteExtensionSet`](#GovernorPreventLateQuorum-LateQuorumVoteExtensionSet-uint64-uint64-) event. + +
+
+ + + +
+
+

ProposalExtended(uint256 indexed proposalId, uint64 extendedDeadline)

+
+

event

+# +
+
+ +
+ +Emitted when a proposal deadline is pushed back due to reaching quorum late in its voting period. + +
+
+ + +
+
+

LateQuorumVoteExtensionSet(uint64 oldVoteExtension, uint64 newVoteExtension)

+
+

event

+# +
+
+ +
+ +Emitted when the [`GovernorPreventLateQuorum.lateQuorumVoteExtension`](#GovernorPreventLateQuorum-lateQuorumVoteExtension--) parameter is changed. + +
+
+ + + +
+ +## `GovernorProposalGuardian` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorProposalGuardian.sol"; +``` + +Extension of [`Governor`](#Governor) which adds a proposal guardian that can cancel proposals at any stage in the proposal's lifecycle. + + +if the proposal guardian is not configured, then proposers take this role for their proposals. + + +
+

Functions

+
+- [proposalGuardian()](#GovernorProposalGuardian-proposalGuardian--) +- [setProposalGuardian(newProposalGuardian)](#GovernorProposalGuardian-setProposalGuardian-address-) +- [_setProposalGuardian(newProposalGuardian)](#GovernorProposalGuardian-_setProposalGuardian-address-) +- [_validateCancel(proposalId, caller)](#GovernorProposalGuardian-_validateCancel-uint256-address-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [ProposalGuardianSet(oldProposalGuardian, newProposalGuardian)](#GovernorProposalGuardian-ProposalGuardianSet-address-address-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

proposalGuardian() → address

+
+

public

+# +
+
+
+ +Getter that returns the address of the proposal guardian. + +
+
+ + + +
+
+

setProposalGuardian(address newProposalGuardian)

+
+

public

+# +
+
+
+ +Update the proposal guardian's address. This operation can only be performed through a governance proposal. + +Emits a [`GovernorProposalGuardian.ProposalGuardianSet`](#GovernorProposalGuardian-ProposalGuardianSet-address-address-) event. + +
+
+ + + +
+
+

_setProposalGuardian(address newProposalGuardian)

+
+

internal

+# +
+
+
+ +Internal setter for the proposal guardian. + +Emits a [`GovernorProposalGuardian.ProposalGuardianSet`](#GovernorProposalGuardian-ProposalGuardianSet-address-address-) event. + +
+
+ + + +
+
+

_validateCancel(uint256 proposalId, address caller) → bool

+
+

internal

+# +
+
+
+ +Override [`Governor._validateCancel`](#Governor-_validateCancel-uint256-address-) to implement the extended cancellation logic. + +* The [`GovernorProposalGuardian.proposalGuardian`](#GovernorProposalGuardian-proposalGuardian--) can cancel any proposal at any point. +* If no proposal guardian is set, the [`IGovernor.proposalProposer`](#IGovernor-proposalProposer-uint256-) can cancel their proposals at any point. +* In any case, permissions defined in [`Governor._validateCancel`](#Governor-_validateCancel-uint256-address-) (or another override) remains valid. + +
+
+ + + +
+
+

ProposalGuardianSet(address oldProposalGuardian, address newProposalGuardian)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `GovernorSequentialProposalId` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorSequentialProposalId.sol"; +``` + +Extension of [`Governor`](#Governor) that changes the numbering of proposal ids from the default hash-based approach to +sequential ids. + +
+

Functions

+
+- [getProposalId(targets, values, calldatas, descriptionHash)](#GovernorSequentialProposalId-getProposalId-address---uint256---bytes---bytes32-) +- [latestProposalId()](#GovernorSequentialProposalId-latestProposalId--) +- [_propose(targets, values, calldatas, description, proposer)](#GovernorSequentialProposalId-_propose-address---uint256---bytes---string-address-) +- [_initializeLatestProposalId(newLatestProposalId)](#GovernorSequentialProposalId-_initializeLatestProposalId-uint256-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorAlreadyInitializedLatestProposalId()](#GovernorSequentialProposalId-GovernorAlreadyInitializedLatestProposalId--) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

getProposalId(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

latestProposalId() → uint256

+
+

public

+# +
+
+
+ +Returns the latest proposal id. A return value of 0 means no proposals have been created yet. + +
+
+ + + +
+
+

_propose(address[] targets, uint256[] values, bytes[] calldatas, string description, address proposer) → uint256

+
+

internal

+# +
+
+
+ +See `IGovernor-_propose`. +Hook into the proposing mechanism to increment proposal count. + +
+
+ + + +
+
+

_initializeLatestProposalId(uint256 newLatestProposalId)

+
+

internal

+# +
+
+
+ +Internal function to set the [`GovernorSequentialProposalId.latestProposalId`](#GovernorSequentialProposalId-latestProposalId--). This function is helpful when transitioning +from another governance system. The next proposal id will be `newLatestProposalId` + 1. + +May only call this function if the current value of [`GovernorSequentialProposalId.latestProposalId`](#GovernorSequentialProposalId-latestProposalId--) is 0. + +
+
+ + + +
+
+

GovernorAlreadyInitializedLatestProposalId()

+
+

error

+# +
+
+
+ +The [`GovernorSequentialProposalId.latestProposalId`](#GovernorSequentialProposalId-latestProposalId--) may only be initialized if it hasn't been set yet +(through initialization or the creation of a proposal). + +
+
+ + + +
+ +## `GovernorSettings` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; +``` + +Extension of [`Governor`](#Governor) for settings updatable through governance. + +
+

Functions

+
+- [constructor(initialVotingDelay, initialVotingPeriod, initialProposalThreshold)](#GovernorSettings-constructor-uint48-uint32-uint256-) +- [votingDelay()](#GovernorSettings-votingDelay--) +- [votingPeriod()](#GovernorSettings-votingPeriod--) +- [proposalThreshold()](#GovernorSettings-proposalThreshold--) +- [setVotingDelay(newVotingDelay)](#GovernorSettings-setVotingDelay-uint48-) +- [setVotingPeriod(newVotingPeriod)](#GovernorSettings-setVotingPeriod-uint32-) +- [setProposalThreshold(newProposalThreshold)](#GovernorSettings-setProposalThreshold-uint256-) +- [_setVotingDelay(newVotingDelay)](#GovernorSettings-_setVotingDelay-uint48-) +- [_setVotingPeriod(newVotingPeriod)](#GovernorSettings-_setVotingPeriod-uint32-) +- [_setProposalThreshold(newProposalThreshold)](#GovernorSettings-_setProposalThreshold-uint256-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [VotingDelaySet(oldVotingDelay, newVotingDelay)](#GovernorSettings-VotingDelaySet-uint256-uint256-) +- [VotingPeriodSet(oldVotingPeriod, newVotingPeriod)](#GovernorSettings-VotingPeriodSet-uint256-uint256-) +- [ProposalThresholdSet(oldProposalThreshold, newProposalThreshold)](#GovernorSettings-ProposalThresholdSet-uint256-uint256-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(uint48 initialVotingDelay, uint32 initialVotingPeriod, uint256 initialProposalThreshold)

+
+

internal

+# +
+
+
+ +Initialize the governance parameters. + +
+
+ + + +
+
+

votingDelay() → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

votingPeriod() → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

proposalThreshold() → uint256

+
+

public

+# +
+
+
+ +The number of votes required in order for a voter to become a proposer. + +
+
+ + + +
+
+

setVotingDelay(uint48 newVotingDelay)

+
+

public

+# +
+
+
+ +Update the voting delay. This operation can only be performed through a governance proposal. + +Emits a [`GovernorSettings.VotingDelaySet`](#GovernorSettings-VotingDelaySet-uint256-uint256-) event. + +
+
+ + + +
+
+

setVotingPeriod(uint32 newVotingPeriod)

+
+

public

+# +
+
+
+ +Update the voting period. This operation can only be performed through a governance proposal. + +Emits a [`GovernorSettings.VotingPeriodSet`](#GovernorSettings-VotingPeriodSet-uint256-uint256-) event. + +
+
+ + + +
+
+

setProposalThreshold(uint256 newProposalThreshold)

+
+

public

+# +
+
+
+ +Update the proposal threshold. This operation can only be performed through a governance proposal. + +Emits a [`GovernorSettings.ProposalThresholdSet`](#GovernorSettings-ProposalThresholdSet-uint256-uint256-) event. + +
+
+ + + +
+
+

_setVotingDelay(uint48 newVotingDelay)

+
+

internal

+# +
+
+
+ +Internal setter for the voting delay. + +Emits a [`GovernorSettings.VotingDelaySet`](#GovernorSettings-VotingDelaySet-uint256-uint256-) event. + +
+
+ + + +
+
+

_setVotingPeriod(uint32 newVotingPeriod)

+
+

internal

+# +
+
+
+ +Internal setter for the voting period. + +Emits a [`GovernorSettings.VotingPeriodSet`](#GovernorSettings-VotingPeriodSet-uint256-uint256-) event. + +
+
+ + + +
+
+

_setProposalThreshold(uint256 newProposalThreshold)

+
+

internal

+# +
+
+
+ +Internal setter for the proposal threshold. + +Emits a [`GovernorSettings.ProposalThresholdSet`](#GovernorSettings-ProposalThresholdSet-uint256-uint256-) event. + +
+
+ + + +
+
+

VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `GovernorStorage` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorStorage.sol"; +``` + +Extension of [`Governor`](#Governor) that implements storage of proposal details. This module also provides primitives for +the enumerability of proposals. + +Use cases for this module include: +- UIs that explore the proposal state without relying on event indexing. +- Using only the proposalId as an argument in the [`Governor.queue`](#Governor-queue-address---uint256---bytes---bytes32-) and [`Governor.execute`](#Governor-execute-address---uint256---bytes---bytes32-) functions for L2 chains + where storage is cheap compared to calldata. + +
+

Functions

+
+- [_propose(targets, values, calldatas, description, proposer)](#GovernorStorage-_propose-address---uint256---bytes---string-address-) +- [queue(proposalId)](#GovernorStorage-queue-uint256-) +- [execute(proposalId)](#GovernorStorage-execute-uint256-) +- [cancel(proposalId)](#GovernorStorage-cancel-uint256-) +- [proposalCount()](#GovernorStorage-proposalCount--) +- [proposalDetails(proposalId)](#GovernorStorage-proposalDetails-uint256-) +- [proposalDetailsAt(index)](#GovernorStorage-proposalDetailsAt-uint256-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

_propose(address[] targets, uint256[] values, bytes[] calldatas, string description, address proposer) → uint256

+
+

internal

+# +
+
+
+ +Hook into the proposing mechanism + +
+
+ + + +
+
+

queue(uint256 proposalId)

+
+

public

+# +
+
+
+ +Version of [`IGovernor.queue`](#IGovernor-queue-address---uint256---bytes---bytes32-) with only `proposalId` as an argument. + +
+
+ + + +
+
+

execute(uint256 proposalId)

+
+

public

+# +
+
+
+ +Version of [`IGovernor.execute`](#IGovernor-execute-address---uint256---bytes---bytes32-) with only `proposalId` as an argument. + +
+
+ + + +
+
+

cancel(uint256 proposalId)

+
+

public

+# +
+
+
+ +ProposalId version of [`IGovernor.cancel`](#IGovernor-cancel-address---uint256---bytes---bytes32-). + +
+
+ + + +
+
+

proposalCount() → uint256

+
+

public

+# +
+
+
+ +Returns the number of stored proposals. + +
+
+ + + +
+
+

proposalDetails(uint256 proposalId) → address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash

+
+

public

+# +
+
+
+ +Returns the details of a proposalId. Reverts if `proposalId` is not a known proposal. + +
+
+ + + +
+
+

proposalDetailsAt(uint256 index) → uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash

+
+

public

+# +
+
+
+ +Returns the details (including the proposalId) of a proposal given its sequential index. + +
+
+ + + +
+ +## `GovernorSuperQuorum` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorSuperQuorum.sol"; +``` + +Extension of [`Governor`](#Governor) with a super quorum. Proposals that meet the super quorum (and have a majority of for +votes) advance to the `Succeeded` state before the proposal deadline. Counting modules that want to use this +extension must implement [`GovernorCountingFractional.proposalVotes`](#GovernorCountingFractional-proposalVotes-uint256-). + +
+

Functions

+
+- [superQuorum(timepoint)](#GovernorSuperQuorum-superQuorum-uint256-) +- [proposalVotes(proposalId)](#GovernorSuperQuorum-proposalVotes-uint256-) +- [state(proposalId)](#GovernorSuperQuorum-state-uint256-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

superQuorum(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Minimum number of cast votes required for a proposal to reach super quorum. Only FOR votes are counted +towards the super quorum. Once the super quorum is reached, an active proposal can proceed to the next state +without waiting for the proposal deadline. + + +The `timepoint` parameter corresponds to the snapshot used for counting the vote. This enables scaling of the +quorum depending on values such as the `totalSupply` of a token at this timepoint (see [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes)). + + + +Make sure the value specified for the super quorum is greater than [`Governor.quorum`](#Governor-quorum-uint256-), otherwise, it may be +possible to pass a proposal with less votes than the default quorum. + + +
+
+ + + +
+
+

proposalVotes(uint256 proposalId) → uint256 againstVotes, uint256 forVotes, uint256 abstainVotes

+
+

public

+# +
+
+
+ +Accessor to the internal vote counts. This must be implemented by the counting module. Counting modules +that don't implement this function are incompatible with this module + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Overridden version of the [`Governor.state`](#Governor-state-uint256-) function that checks if the proposal has reached the super +quorum. + + +If the proposal reaches super quorum but [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-) returns false, eg, assuming the super quorum +has been set low enough that both FOR and AGAINST votes have exceeded it and AGAINST votes exceed FOR votes, +the proposal continues to be active until [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-) returns true or the proposal deadline is reached. +This means that with a low super quorum it is also possible that a vote can succeed prematurely before enough +AGAINST voters have a chance to vote. Hence, it is recommended to set a high enough super quorum to avoid these +types of scenarios. + + +
+
+ + + +
+ +## `GovernorTimelockAccess` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockAccess.sol"; +``` + +This module connects a [`Governor`](#Governor) instance to an [`AccessManager`](/contracts/5.x/api/access#AccessManager) instance, allowing the governor to make calls +that are delay-restricted by the manager using the normal [`Governor.queue`](#Governor-queue-address---uint256---bytes---bytes32-) workflow. An optional base delay is applied to +operations that are not delayed externally by the manager. Execution of a proposal will be delayed as much as +necessary to meet the required delays of all of its operations. + +This extension allows the governor to hold and use its own assets and permissions, unlike [`GovernorTimelockControl`](#GovernorTimelockControl) +and [`GovernorTimelockCompound`](#GovernorTimelockCompound), where the timelock is a separate contract that must be the one to hold assets and +permissions. Operations that are delay-restricted by the manager, however, will be executed through the +[`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) function. + +==== Security Considerations + +Some operations may be cancelable in the `AccessManager` by the admin or a set of guardians, depending on the +restricted function being invoked. Since proposals are atomic, the cancellation by a guardian of a single operation +in a proposal will cause all of the proposal to become unable to execute. Consider proposing cancellable operations +separately. + +By default, function calls will be routed through the associated `AccessManager` whenever it claims the target +function to be restricted by it. However, admins may configure the manager to make that claim for functions that a +governor would want to call directly (e.g., token transfers) in an attempt to deny it access to those functions. To +mitigate this attack vector, the governor is able to ignore the restrictions claimed by the `AccessManager` using +[`GovernorTimelockAccess.setAccessManagerIgnored`](#GovernorTimelockAccess-setAccessManagerIgnored-address-bytes4---bool-). While permanent denial of service is mitigated, temporary DoS may still be technically +possible. All of the governor's own functions (e.g., [`GovernorTimelockAccess.setBaseDelaySeconds`](#GovernorTimelockAccess-setBaseDelaySeconds-uint32-)) ignore the `AccessManager` by default. + + +`AccessManager` does not support scheduling more than one operation with the same target and calldata at +the same time. See [`AccessManager.schedule`](/contracts/5.x/api/access#AccessManager-schedule-address-bytes-uint48-) for a workaround. + + +
+

Functions

+
+- [constructor(manager, initialBaseDelay)](#GovernorTimelockAccess-constructor-address-uint32-) +- [accessManager()](#GovernorTimelockAccess-accessManager--) +- [baseDelaySeconds()](#GovernorTimelockAccess-baseDelaySeconds--) +- [setBaseDelaySeconds(newBaseDelay)](#GovernorTimelockAccess-setBaseDelaySeconds-uint32-) +- [_setBaseDelaySeconds(newBaseDelay)](#GovernorTimelockAccess-_setBaseDelaySeconds-uint32-) +- [isAccessManagerIgnored(target, selector)](#GovernorTimelockAccess-isAccessManagerIgnored-address-bytes4-) +- [setAccessManagerIgnored(target, selectors, ignored)](#GovernorTimelockAccess-setAccessManagerIgnored-address-bytes4---bool-) +- [_setAccessManagerIgnored(target, selector, ignored)](#GovernorTimelockAccess-_setAccessManagerIgnored-address-bytes4-bool-) +- [proposalExecutionPlan(proposalId)](#GovernorTimelockAccess-proposalExecutionPlan-uint256-) +- [proposalNeedsQueuing(proposalId)](#GovernorTimelockAccess-proposalNeedsQueuing-uint256-) +- [propose(targets, values, calldatas, description)](#GovernorTimelockAccess-propose-address---uint256---bytes---string-) +- [_queueOperations(proposalId, targets, , calldatas, )](#GovernorTimelockAccess-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [_executeOperations(proposalId, targets, values, calldatas, )](#GovernorTimelockAccess-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#GovernorTimelockAccess-_cancel-address---uint256---bytes---bytes32-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [BaseDelaySet(oldBaseDelaySeconds, newBaseDelaySeconds)](#GovernorTimelockAccess-BaseDelaySet-uint32-uint32-) +- [AccessManagerIgnoredSet(target, selector, ignored)](#GovernorTimelockAccess-AccessManagerIgnoredSet-address-bytes4-bool-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorUnmetDelay(proposalId, neededTimestamp)](#GovernorTimelockAccess-GovernorUnmetDelay-uint256-uint256-) +- [GovernorMismatchedNonce(proposalId, expectedNonce, actualNonce)](#GovernorTimelockAccess-GovernorMismatchedNonce-uint256-uint256-uint256-) +- [GovernorLockedIgnore()](#GovernorTimelockAccess-GovernorLockedIgnore--) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(address manager, uint32 initialBaseDelay)

+
+

internal

+# +
+
+
+ +Initialize the governor with an [`AccessManager`](/contracts/5.x/api/access#AccessManager) and initial base delay. + +
+
+ + + +
+
+

accessManager() → contract IAccessManager

+
+

public

+# +
+
+
+ +Returns the [`AccessManager`](/contracts/5.x/api/access#AccessManager) instance associated to this governor. + +
+
+ + + +
+
+

baseDelaySeconds() → uint32

+
+

public

+# +
+
+
+ +Base delay that will be applied to all function calls. Some may be further delayed by their associated +`AccessManager` authority; in this case the final delay will be the maximum of the base delay and the one +demanded by the authority. + + +Execution delays are processed by the `AccessManager` contracts, and according to that contract are +expressed in seconds. Therefore, the base delay is also in seconds, regardless of the governor's clock mode. + + +
+
+ + + +
+
+

setBaseDelaySeconds(uint32 newBaseDelay)

+
+

public

+# +
+
+
+ +Change the value of [`GovernorTimelockAccess.baseDelaySeconds`](#GovernorTimelockAccess-baseDelaySeconds--). This operation can only be invoked through a governance proposal. + +
+
+ + + +
+
+

_setBaseDelaySeconds(uint32 newBaseDelay)

+
+

internal

+# +
+
+
+ +Change the value of [`GovernorTimelockAccess.baseDelaySeconds`](#GovernorTimelockAccess-baseDelaySeconds--). Internal function without access control. + +
+
+ + + +
+
+

isAccessManagerIgnored(address target, bytes4 selector) → bool

+
+

public

+# +
+
+
+ +Check if restrictions from the associated [`AccessManager`](/contracts/5.x/api/access#AccessManager) are ignored for a target function. Returns true +when the target function will be invoked directly regardless of `AccessManager` settings for the function. +See [`GovernorTimelockAccess.setAccessManagerIgnored`](#GovernorTimelockAccess-setAccessManagerIgnored-address-bytes4---bool-) and Security Considerations above. + +
+
+ + + +
+
+

setAccessManagerIgnored(address target, bytes4[] selectors, bool ignored)

+
+

public

+# +
+
+
+ +Configure whether restrictions from the associated [`AccessManager`](/contracts/5.x/api/access#AccessManager) are ignored for a target function. +See Security Considerations above. + +
+
+ + + +
+
+

_setAccessManagerIgnored(address target, bytes4 selector, bool ignored)

+
+

internal

+# +
+
+
+ +Internal version of [`GovernorTimelockAccess.setAccessManagerIgnored`](#GovernorTimelockAccess-setAccessManagerIgnored-address-bytes4---bool-) without access restriction. + +
+
+ + + +
+
+

proposalExecutionPlan(uint256 proposalId) → uint32 delay, bool[] indirect, bool[] withDelay

+
+

public

+# +
+
+
+ +Public accessor to check the execution plan, including the number of seconds that the proposal will be +delayed since queuing, an array indicating which of the proposal actions will be executed indirectly through +the associated [`AccessManager`](/contracts/5.x/api/access#AccessManager), and another indicating which will be scheduled in [`Governor.queue`](#Governor-queue-address---uint256---bytes---bytes32-). Note that +those that must be scheduled are cancellable by `AccessManager` guardians. + +
+
+ + + +
+
+

proposalNeedsQueuing(uint256 proposalId) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

propose(address[] targets, uint256[] values, bytes[] calldatas, string description) → uint256

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_queueOperations(uint256 proposalId, address[] targets, uint256[], bytes[] calldatas, bytes32) → uint48

+
+

internal

+# +
+
+
+ +Mechanism to queue a proposal, potentially scheduling some of its operations in the AccessManager. + + +The execution delay is chosen based on the delay information retrieved in [`Governor.propose`](#Governor-propose-address---uint256---bytes---string-). This value may be +off if the delay was updated since proposal creation. In this case, the proposal needs to be recreated. + + +
+
+ + + +
+
+

_executeOperations(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32)

+
+

internal

+# +
+
+
+ +Mechanism to execute a proposal, potentially going through [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) for delayed operations. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Internal cancel mechanism with minimal restrictions. A proposal can be cancelled in any state other than +Canceled, Expired, or Executed. Once cancelled a proposal can't be re-submitted. + +Emits a [`IGovernor.ProposalCanceled`](#IGovernor-ProposalCanceled-uint256-) event. + +
+
+ + + +
+
+

BaseDelaySet(uint32 oldBaseDelaySeconds, uint32 newBaseDelaySeconds)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

AccessManagerIgnoredSet(address target, bytes4 selector, bool ignored)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

GovernorUnmetDelay(uint256 proposalId, uint256 neededTimestamp)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

GovernorMismatchedNonce(uint256 proposalId, uint256 expectedNonce, uint256 actualNonce)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

GovernorLockedIgnore()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `GovernorTimelockCompound` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol"; +``` + +Extension of [`Governor`](#Governor) that binds the execution process to a Compound Timelock. This adds a delay, enforced by +the external timelock to all successful proposals (in addition to the voting duration). The [`Governor`](#Governor) needs to be +the admin of the timelock for any operation to be performed. A public, unrestricted, +[`GovernorTimelockCompound.__acceptAdmin`](#GovernorTimelockCompound-__acceptAdmin--) is available to accept ownership of the timelock. + +Using this model means the proposal will be operated by the [`TimelockController`](#TimelockController) and not by the [`Governor`](#Governor). Thus, +the assets and permissions must be attached to the [`TimelockController`](#TimelockController). Any asset sent to the [`Governor`](#Governor) will be +inaccessible from a proposal, unless executed via [`Governor.relay`](#Governor-relay-address-uint256-bytes-). + +
+

Functions

+
+- [constructor(timelockAddress)](#GovernorTimelockCompound-constructor-contract-ICompoundTimelock-) +- [state(proposalId)](#GovernorTimelockCompound-state-uint256-) +- [timelock()](#GovernorTimelockCompound-timelock--) +- [proposalNeedsQueuing()](#GovernorTimelockCompound-proposalNeedsQueuing-uint256-) +- [_queueOperations(proposalId, targets, values, calldatas, )](#GovernorTimelockCompound-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [_executeOperations(proposalId, targets, values, calldatas, )](#GovernorTimelockCompound-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#GovernorTimelockCompound-_cancel-address---uint256---bytes---bytes32-) +- [_executor()](#GovernorTimelockCompound-_executor--) +- [__acceptAdmin()](#GovernorTimelockCompound-__acceptAdmin--) +- [updateTimelock(newTimelock)](#GovernorTimelockCompound-updateTimelock-contract-ICompoundTimelock-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [TimelockChange(oldTimelock, newTimelock)](#GovernorTimelockCompound-TimelockChange-address-address-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(contract ICompoundTimelock timelockAddress)

+
+

internal

+# +
+
+
+ +Set the timelock. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Overridden version of the [`Governor.state`](#Governor-state-uint256-) function with added support for the `Expired` state. + +
+
+ + + +
+
+

timelock() → address

+
+

public

+# +
+
+
+ +Public accessor to check the address of the timelock + +
+
+ + + +
+
+

proposalNeedsQueuing(uint256) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_queueOperations(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32) → uint48

+
+

internal

+# +
+
+
+ +Function to queue a proposal to the timelock. + +
+
+ + + +
+
+

_executeOperations(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32)

+
+

internal

+# +
+
+
+ +Overridden version of the [`Governor._executeOperations`](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) function that run the already queued proposal +through the timelock. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Overridden version of the [`Governor._cancel`](#Governor-_cancel-address---uint256---bytes---bytes32-) function to cancel the timelocked proposal if it has already +been queued. + +
+
+ + + +
+
+

_executor() → address

+
+

internal

+# +
+
+
+ +Address through which the governor executes action. In this case, the timelock. + +
+
+ + + +
+
+

__acceptAdmin()

+
+

public

+# +
+
+
+ +Accept admin right over the timelock. + +
+
+ + + +
+
+

updateTimelock(contract ICompoundTimelock newTimelock)

+
+

public

+# +
+
+
+ +Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates +must be proposed, scheduled, and executed through governance proposals. + +For security reasons, the timelock must be handed over to another admin before setting up a new one. The two +operations (hand over the timelock) and do the update can be batched in a single proposal. + +Note that if the timelock admin has been handed over in a previous operation, we refuse updates made through the +timelock if admin of the timelock has already been accepted and the operation is executed outside the scope of +governance. + +CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + +
+
+ + + +
+
+

TimelockChange(address oldTimelock, address newTimelock)

+
+

event

+# +
+
+ +
+ +Emitted when the timelock controller used for proposal execution is modified. + +
+
+ + + +
+ +## `GovernorTimelockControl` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; +``` + +Extension of [`Governor`](#Governor) that binds the execution process to an instance of [`TimelockController`](#TimelockController). This adds a +delay, enforced by the [`TimelockController`](#TimelockController) to all successful proposals (in addition to the voting duration). The +[`Governor`](#Governor) needs the proposer (and ideally the executor and canceller) roles for the [`Governor`](#Governor) to work properly. + +Using this model means the proposal will be operated by the [`TimelockController`](#TimelockController) and not by the [`Governor`](#Governor). Thus, +the assets and permissions must be attached to the [`TimelockController`](#TimelockController). Any asset sent to the [`Governor`](#Governor) will be +inaccessible from a proposal, unless executed via [`Governor.relay`](#Governor-relay-address-uint256-bytes-). + + +Setting up the TimelockController to have additional proposers or cancelers besides the governor is very +risky, as it grants them the ability to: 1) execute operations as the timelock, and thus possibly performing +operations or accessing funds that are expected to only be accessible through a vote, and 2) block governance +proposals that have been approved by the voters, effectively executing a Denial of Service attack. + + +
+

Functions

+
+- [constructor(timelockAddress)](#GovernorTimelockControl-constructor-contract-TimelockController-) +- [state(proposalId)](#GovernorTimelockControl-state-uint256-) +- [timelock()](#GovernorTimelockControl-timelock--) +- [proposalNeedsQueuing()](#GovernorTimelockControl-proposalNeedsQueuing-uint256-) +- [_queueOperations(proposalId, targets, values, calldatas, descriptionHash)](#GovernorTimelockControl-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [_executeOperations(proposalId, targets, values, calldatas, descriptionHash)](#GovernorTimelockControl-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#GovernorTimelockControl-_cancel-address---uint256---bytes---bytes32-) +- [_executor()](#GovernorTimelockControl-_executor--) +- [updateTimelock(newTimelock)](#GovernorTimelockControl-updateTimelock-contract-TimelockController-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_getVotes(account, timepoint, params)](#Governor-_getVotes-address-uint256-bytes-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [clock()](#Governor-clock--) +- [CLOCK_MODE()](#Governor-CLOCK_MODE--) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [TimelockChange(oldTimelock, newTimelock)](#GovernorTimelockControl-TimelockChange-address-address-) +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(contract TimelockController timelockAddress)

+
+

internal

+# +
+
+
+ +Set the timelock. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Overridden version of the [`Governor.state`](#Governor-state-uint256-) function that considers the status reported by the timelock. + +
+
+ + + +
+
+

timelock() → address

+
+

public

+# +
+
+
+ +Public accessor to check the address of the timelock + +
+
+ + + +
+
+

proposalNeedsQueuing(uint256) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_queueOperations(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint48

+
+

internal

+# +
+
+
+ +Function to queue a proposal to the timelock. + +
+
+ + + +
+
+

_executeOperations(uint256 proposalId, address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash)

+
+

internal

+# +
+
+
+ +Overridden version of the [`Governor._executeOperations`](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) function that runs the already queued proposal +through the timelock. + +
+
+ + + +
+
+

_cancel(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash) → uint256

+
+

internal

+# +
+
+
+ +Overridden version of the [`Governor._cancel`](#Governor-_cancel-address---uint256---bytes---bytes32-) function to cancel the timelocked proposal if it has already +been queued. + +
+
+ + + +
+
+

_executor() → address

+
+

internal

+# +
+
+
+ +Address through which the governor executes action. In this case, the timelock. + +
+
+ + + +
+
+

updateTimelock(contract TimelockController newTimelock)

+
+

public

+# +
+
+
+ +Public endpoint to update the underlying timelock instance. Restricted to the timelock itself, so updates +must be proposed, scheduled, and executed through governance proposals. + +CAUTION: It is not recommended to change the timelock while there are other queued governance proposals. + +
+
+ + + +
+
+

TimelockChange(address oldTimelock, address newTimelock)

+
+

event

+# +
+
+ +
+ +Emitted when the timelock controller used for proposal execution is modified. + +
+
+ + + +
+ +## `GovernorVotes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; +``` + +Extension of [`Governor`](#Governor) for voting weight extraction from an [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes) token, or since v4.5 an [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes) +token. + +
+

Functions

+
+- [constructor(tokenAddress)](#GovernorVotes-constructor-contract-IVotes-) +- [token()](#GovernorVotes-token--) +- [clock()](#GovernorVotes-clock--) +- [CLOCK_MODE()](#GovernorVotes-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotes-_getVotes-address-uint256-bytes-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [quorum(timepoint)](#Governor-quorum-uint256-) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(contract IVotes tokenAddress)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

token() → contract IERC5805

+
+

public

+# +
+
+
+ +The token that voting power is sourced from. + +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock (as specified in ERC-6372) is set to match the token's clock. Fallback to block numbers if the token +does not implement ERC-6372. + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Machine-readable description of the clock as specified in ERC-6372. + +
+
+ + + +
+
+

_getVotes(address account, uint256 timepoint, bytes) → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `GovernorVotesQuorumFraction` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol"; +``` + +Extension of [`Governor`](#Governor) for voting weight extraction from an [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes) token and a quorum expressed as a +fraction of the total supply. + +
+

Functions

+
+- [constructor(quorumNumeratorValue)](#GovernorVotesQuorumFraction-constructor-uint256-) +- [quorumNumerator()](#GovernorVotesQuorumFraction-quorumNumerator--) +- [quorumNumerator(timepoint)](#GovernorVotesQuorumFraction-quorumNumerator-uint256-) +- [quorumDenominator()](#GovernorVotesQuorumFraction-quorumDenominator--) +- [quorum(timepoint)](#GovernorVotesQuorumFraction-quorum-uint256-) +- [updateQuorumNumerator(newQuorumNumerator)](#GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-) +- [_updateQuorumNumerator(newQuorumNumerator)](#GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-) +- [_optimisticUpperLookupRecent(ckpts, timepoint)](#GovernorVotesQuorumFraction-_optimisticUpperLookupRecent-struct-Checkpoints-Trace208-uint256-) +#### GovernorVotes [!toc] +- [token()](#GovernorVotes-token--) +- [clock()](#GovernorVotes-clock--) +- [CLOCK_MODE()](#GovernorVotes-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotes-_getVotes-address-uint256-bytes-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [state(proposalId)](#Governor-state-uint256-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator)](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) +#### GovernorVotes [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorInvalidQuorumFraction(quorumNumerator, quorumDenominator)](#GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256-) +#### GovernorVotes [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(uint256 quorumNumeratorValue)

+
+

internal

+# +
+
+
+ +Initialize quorum as a fraction of the token's total supply. + +The fraction is specified as `numerator / denominator`. By default the denominator is 100, so quorum is +specified as a percent: a numerator of 10 corresponds to quorum being 10% of total supply. The denominator can be +customized by overriding [`GovernorVotesQuorumFraction.quorumDenominator`](#GovernorVotesQuorumFraction-quorumDenominator--). + +
+
+ + + +
+
+

quorumNumerator() → uint256

+
+

public

+# +
+
+
+ +Returns the current quorum numerator. See [`GovernorVotesQuorumFraction.quorumDenominator`](#GovernorVotesQuorumFraction-quorumDenominator--). + +
+
+ + + +
+
+

quorumNumerator(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the quorum numerator at a specific timepoint. See [`GovernorVotesQuorumFraction.quorumDenominator`](#GovernorVotesQuorumFraction-quorumDenominator--). + +
+
+ + + +
+
+

quorumDenominator() → uint256

+
+

public

+# +
+
+
+ +Returns the quorum denominator. Defaults to 100, but may be overridden. + +
+
+ + + +
+
+

quorum(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the quorum for a timepoint, in terms of number of votes: `supply * numerator / denominator`. + +
+
+ + + +
+
+

updateQuorumNumerator(uint256 newQuorumNumerator)

+
+

public

+# +
+
+
+ +Changes the quorum numerator. + +Emits a [`GovernorVotesQuorumFraction.QuorumNumeratorUpdated`](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) event. + +Requirements: + +- Must be called through a governance proposal. +- New numerator must be smaller or equal to the denominator. + +
+
+ + + +
+
+

_updateQuorumNumerator(uint256 newQuorumNumerator)

+
+

internal

+# +
+
+
+ +Changes the quorum numerator. + +Emits a [`GovernorVotesQuorumFraction.QuorumNumeratorUpdated`](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) event. + +Requirements: + +- New numerator must be smaller or equal to the denominator. + +
+
+ + + +
+
+

_optimisticUpperLookupRecent(struct Checkpoints.Trace208 ckpts, uint256 timepoint) → uint256

+
+

internal

+# +
+
+
+ +Returns the numerator at a specific timepoint. + +
+
+ + + +
+
+

QuorumNumeratorUpdated(uint256 oldQuorumNumerator, uint256 newQuorumNumerator)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

GovernorInvalidQuorumFraction(uint256 quorumNumerator, uint256 quorumDenominator)

+
+

error

+# +
+
+
+ +The quorum set is not a valid fraction. + +
+
+ + + +
+ +## `GovernorVotesSuperQuorumFraction` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/extensions/GovernorVotesSuperQuorumFraction.sol"; +``` + +Extension of [`GovernorVotesQuorumFraction`](#GovernorVotesQuorumFraction) with a super quorum expressed as a +fraction of the total supply. Proposals that meet the super quorum (and have a majority of for votes) advance to +the `Succeeded` state before the proposal deadline. + +
+

Functions

+
+- [constructor(superQuorumNumeratorValue)](#GovernorVotesSuperQuorumFraction-constructor-uint256-) +- [superQuorumNumerator()](#GovernorVotesSuperQuorumFraction-superQuorumNumerator--) +- [superQuorumNumerator(timepoint)](#GovernorVotesSuperQuorumFraction-superQuorumNumerator-uint256-) +- [superQuorum(timepoint)](#GovernorVotesSuperQuorumFraction-superQuorum-uint256-) +- [updateSuperQuorumNumerator(newSuperQuorumNumerator)](#GovernorVotesSuperQuorumFraction-updateSuperQuorumNumerator-uint256-) +- [_updateSuperQuorumNumerator(newSuperQuorumNumerator)](#GovernorVotesSuperQuorumFraction-_updateSuperQuorumNumerator-uint256-) +- [_updateQuorumNumerator(newQuorumNumerator)](#GovernorVotesSuperQuorumFraction-_updateQuorumNumerator-uint256-) +- [state(proposalId)](#GovernorVotesSuperQuorumFraction-state-uint256-) +#### GovernorSuperQuorum [!toc] +- [proposalVotes(proposalId)](#GovernorSuperQuorum-proposalVotes-uint256-) +#### GovernorVotesQuorumFraction [!toc] +- [quorumNumerator()](#GovernorVotesQuorumFraction-quorumNumerator--) +- [quorumNumerator(timepoint)](#GovernorVotesQuorumFraction-quorumNumerator-uint256-) +- [quorumDenominator()](#GovernorVotesQuorumFraction-quorumDenominator--) +- [quorum(timepoint)](#GovernorVotesQuorumFraction-quorum-uint256-) +- [updateQuorumNumerator(newQuorumNumerator)](#GovernorVotesQuorumFraction-updateQuorumNumerator-uint256-) +- [_optimisticUpperLookupRecent(ckpts, timepoint)](#GovernorVotesQuorumFraction-_optimisticUpperLookupRecent-struct-Checkpoints-Trace208-uint256-) +#### GovernorVotes [!toc] +- [token()](#GovernorVotes-token--) +- [clock()](#GovernorVotes-clock--) +- [CLOCK_MODE()](#GovernorVotes-CLOCK_MODE--) +- [_getVotes(account, timepoint, )](#GovernorVotes-_getVotes-address-uint256-bytes-) +#### Governor [!toc] +- [receive()](#Governor-receive--) +- [supportsInterface(interfaceId)](#Governor-supportsInterface-bytes4-) +- [name()](#Governor-name--) +- [version()](#Governor-version--) +- [hashProposal(targets, values, calldatas, descriptionHash)](#Governor-hashProposal-address---uint256---bytes---bytes32-) +- [getProposalId(targets, values, calldatas, descriptionHash)](#Governor-getProposalId-address---uint256---bytes---bytes32-) +- [proposalThreshold()](#Governor-proposalThreshold--) +- [proposalSnapshot(proposalId)](#Governor-proposalSnapshot-uint256-) +- [proposalDeadline(proposalId)](#Governor-proposalDeadline-uint256-) +- [proposalProposer(proposalId)](#Governor-proposalProposer-uint256-) +- [proposalEta(proposalId)](#Governor-proposalEta-uint256-) +- [proposalNeedsQueuing()](#Governor-proposalNeedsQueuing-uint256-) +- [_checkGovernance()](#Governor-_checkGovernance--) +- [_quorumReached(proposalId)](#Governor-_quorumReached-uint256-) +- [_voteSucceeded(proposalId)](#Governor-_voteSucceeded-uint256-) +- [_countVote(proposalId, account, support, totalWeight, params)](#Governor-_countVote-uint256-address-uint8-uint256-bytes-) +- [_tallyUpdated(proposalId)](#Governor-_tallyUpdated-uint256-) +- [_defaultParams()](#Governor-_defaultParams--) +- [propose(targets, values, calldatas, description)](#Governor-propose-address---uint256---bytes---string-) +- [_propose(targets, values, calldatas, description, proposer)](#Governor-_propose-address---uint256---bytes---string-address-) +- [queue(targets, values, calldatas, descriptionHash)](#Governor-queue-address---uint256---bytes---bytes32-) +- [_queueOperations(, , , , )](#Governor-_queueOperations-uint256-address---uint256---bytes---bytes32-) +- [execute(targets, values, calldatas, descriptionHash)](#Governor-execute-address---uint256---bytes---bytes32-) +- [_executeOperations(, targets, values, calldatas, )](#Governor-_executeOperations-uint256-address---uint256---bytes---bytes32-) +- [cancel(targets, values, calldatas, descriptionHash)](#Governor-cancel-address---uint256---bytes---bytes32-) +- [_cancel(targets, values, calldatas, descriptionHash)](#Governor-_cancel-address---uint256---bytes---bytes32-) +- [getVotes(account, timepoint)](#Governor-getVotes-address-uint256-) +- [getVotesWithParams(account, timepoint, params)](#Governor-getVotesWithParams-address-uint256-bytes-) +- [castVote(proposalId, support)](#Governor-castVote-uint256-uint8-) +- [castVoteWithReason(proposalId, support, reason)](#Governor-castVoteWithReason-uint256-uint8-string-) +- [castVoteWithReasonAndParams(proposalId, support, reason, params)](#Governor-castVoteWithReasonAndParams-uint256-uint8-string-bytes-) +- [castVoteBySig(proposalId, support, voter, signature)](#Governor-castVoteBySig-uint256-uint8-address-bytes-) +- [castVoteWithReasonAndParamsBySig(proposalId, support, voter, reason, params, signature)](#Governor-castVoteWithReasonAndParamsBySig-uint256-uint8-address-string-bytes-bytes-) +- [_validateVoteSig(proposalId, support, voter, signature)](#Governor-_validateVoteSig-uint256-uint8-address-bytes-) +- [_validateExtendedVoteSig(proposalId, support, voter, reason, params, signature)](#Governor-_validateExtendedVoteSig-uint256-uint8-address-string-bytes-bytes-) +- [_castVote(proposalId, account, support, reason)](#Governor-_castVote-uint256-address-uint8-string-) +- [_castVote(proposalId, account, support, reason, params)](#Governor-_castVote-uint256-address-uint8-string-bytes-) +- [relay(target, value, data)](#Governor-relay-address-uint256-bytes-) +- [_executor()](#Governor-_executor--) +- [onERC721Received(, , , )](#Governor-onERC721Received-address-address-uint256-bytes-) +- [onERC1155Received(, , , , )](#Governor-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#Governor-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +- [_encodeStateBitmap(proposalState)](#Governor-_encodeStateBitmap-enum-IGovernor-ProposalState-) +- [_validateStateBitmap(proposalId, allowedStates)](#Governor-_validateStateBitmap-uint256-bytes32-) +- [_isValidDescriptionForProposer(proposer, description)](#Governor-_isValidDescriptionForProposer-address-string-) +- [_validateCancel(proposalId, caller)](#Governor-_validateCancel-uint256-address-) +- [votingDelay()](#Governor-votingDelay--) +- [votingPeriod()](#Governor-votingPeriod--) +- [BALLOT_TYPEHASH()](#Governor-BALLOT_TYPEHASH-bytes32) +- [EXTENDED_BALLOT_TYPEHASH()](#Governor-EXTENDED_BALLOT_TYPEHASH-bytes32) +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [COUNTING_MODE()](#IGovernor-COUNTING_MODE--) +- [hasVoted(proposalId, account)](#IGovernor-hasVoted-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [SuperQuorumNumeratorUpdated(oldSuperQuorumNumerator, newSuperQuorumNumerator)](#GovernorVotesSuperQuorumFraction-SuperQuorumNumeratorUpdated-uint256-uint256-) +#### GovernorSuperQuorum [!toc] +#### GovernorVotesQuorumFraction [!toc] +- [QuorumNumeratorUpdated(oldQuorumNumerator, newQuorumNumerator)](#GovernorVotesQuorumFraction-QuorumNumeratorUpdated-uint256-uint256-) +#### GovernorVotes [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [ProposalCreated(proposalId, proposer, targets, values, signatures, calldatas, voteStart, voteEnd, description)](#IGovernor-ProposalCreated-uint256-address-address---uint256---string---bytes---uint256-uint256-string-) +- [ProposalQueued(proposalId, etaSeconds)](#IGovernor-ProposalQueued-uint256-uint256-) +- [ProposalExecuted(proposalId)](#IGovernor-ProposalExecuted-uint256-) +- [ProposalCanceled(proposalId)](#IGovernor-ProposalCanceled-uint256-) +- [VoteCast(voter, proposalId, support, weight, reason)](#IGovernor-VoteCast-address-uint256-uint8-uint256-string-) +- [VoteCastWithParams(voter, proposalId, support, weight, reason, params)](#IGovernor-VoteCastWithParams-address-uint256-uint8-uint256-string-bytes-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [GovernorInvalidSuperQuorumFraction(superQuorumNumerator, denominator)](#GovernorVotesSuperQuorumFraction-GovernorInvalidSuperQuorumFraction-uint256-uint256-) +- [GovernorInvalidSuperQuorumTooSmall(superQuorumNumerator, quorumNumerator)](#GovernorVotesSuperQuorumFraction-GovernorInvalidSuperQuorumTooSmall-uint256-uint256-) +- [GovernorInvalidQuorumTooLarge(quorumNumerator, superQuorumNumerator)](#GovernorVotesSuperQuorumFraction-GovernorInvalidQuorumTooLarge-uint256-uint256-) +#### GovernorSuperQuorum [!toc] +#### GovernorVotesQuorumFraction [!toc] +- [GovernorInvalidQuorumFraction(quorumNumerator, quorumDenominator)](#GovernorVotesQuorumFraction-GovernorInvalidQuorumFraction-uint256-uint256-) +#### GovernorVotes [!toc] +#### Governor [!toc] +#### IERC1155Receiver [!toc] +#### IERC721Receiver [!toc] +#### IGovernor [!toc] +- [GovernorInvalidProposalLength(targets, calldatas, values)](#IGovernor-GovernorInvalidProposalLength-uint256-uint256-uint256-) +- [GovernorAlreadyCastVote(voter)](#IGovernor-GovernorAlreadyCastVote-address-) +- [GovernorDisabledDeposit()](#IGovernor-GovernorDisabledDeposit--) +- [GovernorOnlyExecutor(account)](#IGovernor-GovernorOnlyExecutor-address-) +- [GovernorNonexistentProposal(proposalId)](#IGovernor-GovernorNonexistentProposal-uint256-) +- [GovernorUnexpectedProposalState(proposalId, current, expectedStates)](#IGovernor-GovernorUnexpectedProposalState-uint256-enum-IGovernor-ProposalState-bytes32-) +- [GovernorInvalidVotingPeriod(votingPeriod)](#IGovernor-GovernorInvalidVotingPeriod-uint256-) +- [GovernorInsufficientProposerVotes(proposer, votes, threshold)](#IGovernor-GovernorInsufficientProposerVotes-address-uint256-uint256-) +- [GovernorRestrictedProposer(proposer)](#IGovernor-GovernorRestrictedProposer-address-) +- [GovernorInvalidVoteType()](#IGovernor-GovernorInvalidVoteType--) +- [GovernorInvalidVoteParams()](#IGovernor-GovernorInvalidVoteParams--) +- [GovernorQueueNotImplemented()](#IGovernor-GovernorQueueNotImplemented--) +- [GovernorNotQueuedProposal(proposalId)](#IGovernor-GovernorNotQueuedProposal-uint256-) +- [GovernorAlreadyQueuedProposal(proposalId)](#IGovernor-GovernorAlreadyQueuedProposal-uint256-) +- [GovernorInvalidSignature(voter)](#IGovernor-GovernorInvalidSignature-address-) +- [GovernorUnableToCancel(proposalId, account)](#IGovernor-GovernorUnableToCancel-uint256-address-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(uint256 superQuorumNumeratorValue)

+
+

internal

+# +
+
+
+ +Initialize super quorum as a fraction of the token's total supply. + +The super quorum is specified as a fraction of the token's total supply and has to +be greater than the quorum. + +
+
+ + + +
+
+

superQuorumNumerator() → uint256

+
+

public

+# +
+
+
+ +Returns the current super quorum numerator. + +
+
+ + + +
+
+

superQuorumNumerator(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the super quorum numerator at a specific `timepoint`. + +
+
+ + + +
+
+

superQuorum(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the super quorum for a `timepoint`, in terms of number of votes: `supply * numerator / denominator`. +See [`GovernorSuperQuorum.superQuorum`](#GovernorSuperQuorum-superQuorum-uint256-) for more details. + +
+
+ + + +
+
+

updateSuperQuorumNumerator(uint256 newSuperQuorumNumerator)

+
+

public

+# +
+
+
+ +Changes the super quorum numerator. + +Emits a [`GovernorVotesSuperQuorumFraction.SuperQuorumNumeratorUpdated`](#GovernorVotesSuperQuorumFraction-SuperQuorumNumeratorUpdated-uint256-uint256-) event. + +Requirements: + +- Must be called through a governance proposal. +- New super quorum numerator must be smaller or equal to the denominator. +- New super quorum numerator must be greater than or equal to the quorum numerator. + +
+
+ + + +
+
+

_updateSuperQuorumNumerator(uint256 newSuperQuorumNumerator)

+
+

internal

+# +
+
+
+ +Changes the super quorum numerator. + +Emits a [`GovernorVotesSuperQuorumFraction.SuperQuorumNumeratorUpdated`](#GovernorVotesSuperQuorumFraction-SuperQuorumNumeratorUpdated-uint256-uint256-) event. + +Requirements: + +- New super quorum numerator must be smaller or equal to the denominator. +- New super quorum numerator must be greater than or equal to the quorum numerator. + +
+
+ + + +
+
+

_updateQuorumNumerator(uint256 newQuorumNumerator)

+
+

internal

+# +
+
+
+ +Overrides [`GovernorVotesQuorumFraction._updateQuorumNumerator`](#GovernorVotesQuorumFraction-_updateQuorumNumerator-uint256-) to ensure the super +quorum numerator is greater than or equal to the quorum numerator. + +
+
+ + + +
+
+

state(uint256 proposalId) → enum IGovernor.ProposalState

+
+

public

+# +
+
+
+ +Overridden version of the [`Governor.state`](#Governor-state-uint256-) function that checks if the proposal has reached the super +quorum. + + +If the proposal reaches super quorum but [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-) returns false, eg, assuming the super quorum +has been set low enough that both FOR and AGAINST votes have exceeded it and AGAINST votes exceed FOR votes, +the proposal continues to be active until [`Governor._voteSucceeded`](#Governor-_voteSucceeded-uint256-) returns true or the proposal deadline is reached. +This means that with a low super quorum it is also possible that a vote can succeed prematurely before enough +AGAINST voters have a chance to vote. Hence, it is recommended to set a high enough super quorum to avoid these +types of scenarios. + + +
+
+ + + +
+
+

SuperQuorumNumeratorUpdated(uint256 oldSuperQuorumNumerator, uint256 newSuperQuorumNumerator)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+
+

GovernorInvalidSuperQuorumFraction(uint256 superQuorumNumerator, uint256 denominator)

+
+

error

+# +
+
+
+ +The super quorum set is not valid as it exceeds the quorum denominator. + +
+
+ + + +
+
+

GovernorInvalidSuperQuorumTooSmall(uint256 superQuorumNumerator, uint256 quorumNumerator)

+
+

error

+# +
+
+
+ +The super quorum set is not valid as it is smaller or equal to the quorum. + +
+
+ + + +
+
+

GovernorInvalidQuorumTooLarge(uint256 quorumNumerator, uint256 superQuorumNumerator)

+
+

error

+# +
+
+
+ +The quorum set is not valid as it exceeds the super quorum. + +
+
+ + + +
+ +## `IVotes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/utils/IVotes.sol"; +``` + +Common interface for [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes), [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes), and other [`Votes`](#Votes)-enabled contracts. + +
+

Functions

+
+- [getVotes(account)](#IVotes-getVotes-address-) +- [getPastVotes(account, timepoint)](#IVotes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#IVotes-getPastTotalSupply-uint256-) +- [delegates(account)](#IVotes-delegates-address-) +- [delegate(delegatee)](#IVotes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +
+
+ +
+

Events

+
+- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +
+
+ +
+

Errors

+
+- [VotesExpiredSignature(expiry)](#IVotes-VotesExpiredSignature-uint256-) +
+
+ + + +
+
+

getVotes(address account) → uint256

+
+

external

+# +
+
+
+ +Returns the current amount of votes that `account` has. + +
+
+ + + +
+
+

getPastVotes(address account, uint256 timepoint) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +
+
+ + + +
+
+

getPastTotalSupply(uint256 timepoint) → uint256

+
+

external

+# +
+
+
+ +Returns the total supply of votes available at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. +Votes that have not been delegated are still part of total supply, even though they would not participate in a +vote. + + +
+
+ + + +
+
+

delegates(address account) → address

+
+

external

+# +
+
+
+ +Returns the delegate that `account` has chosen. + +
+
+ + + +
+
+

delegate(address delegatee)

+
+

external

+# +
+
+
+ +Delegates votes from the sender to `delegatee`. + +
+
+ + + +
+
+

delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

+
+

external

+# +
+
+
+ +Delegates votes from signer to `delegatee`. + +
+
+ + + +
+
+

DelegateChanged(address indexed delegator, address indexed fromDelegate, address indexed toDelegate)

+
+

event

+# +
+
+ +
+ +Emitted when an account changes their delegate. + +
+
+ + +
+
+

DelegateVotesChanged(address indexed delegate, uint256 previousVotes, uint256 newVotes)

+
+

event

+# +
+
+ +
+ +Emitted when a token transfer or delegate change results in changes to a delegate's number of voting units. + +
+
+ + + +
+
+

VotesExpiredSignature(uint256 expiry)

+
+

error

+# +
+
+
+ +The signature used has expired. + +
+
+ + + +
+ +## `Votes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/utils/Votes.sol"; +``` + +This is a base abstract contract that tracks voting units, which are a measure of voting power that can be +transferred, and provides a system of vote delegation, where an account can delegate its voting units to a sort of +"representative" that will pool delegated voting units from different accounts and can then use it to vote in +decisions. In fact, voting units _must_ be delegated in order to count as actual votes, and an account has to +delegate those votes to itself if it wishes to participate in decisions and does not have a trusted representative. + +This contract is often combined with a token contract such that voting units correspond to token units. For an +example, see [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes). + +The full history of delegate votes is tracked on-chain so that governance protocols can consider votes as distributed +at a particular block number to protect against flash loans and double voting. The opt-in delegate system makes the +cost of this history tracking optional. + +When using this module the derived contract must implement [`Votes._getVotingUnits`](#Votes-_getVotingUnits-address-) (for example, make it return +[`ERC721.balanceOf`](/contracts/5.x/api/token/ERC721#ERC721-balanceOf-address-)), and can use [`Votes._transferVotingUnits`](#Votes-_transferVotingUnits-address-address-uint256-) to track a change in the distribution of those units (in the +previous example, it would be included in [`ERC721._update`](/contracts/5.x/api/token/ERC721#ERC721-_update-address-uint256-address-)). + +
+

Functions

+
+- [clock()](#Votes-clock--) +- [CLOCK_MODE()](#Votes-CLOCK_MODE--) +- [_validateTimepoint(timepoint)](#Votes-_validateTimepoint-uint256-) +- [getVotes(account)](#Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#Votes-getPastTotalSupply-uint256-) +- [_getTotalSupply()](#Votes-_getTotalSupply--) +- [delegates(account)](#Votes-delegates-address-) +- [delegate(delegatee)](#Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#Votes-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#Votes-_transferVotingUnits-address-address-uint256-) +- [_moveDelegateVotes(from, to, amount)](#Votes-_moveDelegateVotes-address-address-uint256-) +- [_numCheckpoints(account)](#Votes-_numCheckpoints-address-) +- [_checkpoints(account, pos)](#Votes-_checkpoints-address-uint32-) +- [_getVotingUnits()](#Votes-_getVotingUnits-address-) +#### IERC5805 [!toc] +#### IVotes [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +
+
+ +
+

Events

+
+#### IERC5805 [!toc] +#### IVotes [!toc] +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ +
+

Errors

+
+- [ERC6372InconsistentClock()](#Votes-ERC6372InconsistentClock--) +- [ERC5805FutureLookup(timepoint, clock)](#Votes-ERC5805FutureLookup-uint256-uint48-) +#### IERC5805 [!toc] +#### IVotes [!toc] +- [VotesExpiredSignature(expiry)](#IVotes-VotesExpiredSignature-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +
+
+ + + +
+
+

clock() → uint48

+
+

public

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based +checkpoints (and voting), in which case [`Governor.CLOCK_MODE`](#Governor-CLOCK_MODE--) should be overridden as well to match. + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

public

+# +
+
+
+ +Machine-readable description of the clock as specified in ERC-6372. + +
+
+ + + +
+
+

_validateTimepoint(uint256 timepoint) → uint48

+
+

internal

+# +
+
+
+ +Validate that a timepoint is in the past, and return it as a uint48. + +
+
+ + + +
+
+

getVotes(address account) → uint256

+
+

public

+# +
+
+
+ +Returns the current amount of votes that `account` has. + +
+
+ + + +
+
+

getPastVotes(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of votes that `account` had at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

getPastTotalSupply(uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the total supply of votes available at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + + +This value is the sum of all available votes, which is not necessarily the sum of all delegated votes. +Votes that have not been delegated are still part of total supply, even though they would not participate in a +vote. + + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

_getTotalSupply() → uint256

+
+

internal

+# +
+
+
+ +Returns the current total supply of votes. + +
+
+ + + +
+
+

delegates(address account) → address

+
+

public

+# +
+
+
+ +Returns the delegate that `account` has chosen. + +
+
+ + + +
+
+

delegate(address delegatee)

+
+

public

+# +
+
+
+ +Delegates votes from the sender to `delegatee`. + +
+
+ + + +
+
+

delegateBySig(address delegatee, uint256 nonce, uint256 expiry, uint8 v, bytes32 r, bytes32 s)

+
+

public

+# +
+
+
+ +Delegates votes from signer to `delegatee`. + +
+
+ + + +
+
+

_delegate(address account, address delegatee)

+
+

internal

+# +
+
+
+ +Delegate all of `account`'s voting units to `delegatee`. + +Emits events [`IVotes.DelegateChanged`](#IVotes-DelegateChanged-address-address-address-) and [`IVotes.DelegateVotesChanged`](#IVotes-DelegateVotesChanged-address-uint256-uint256-). + +
+
+ + + +
+
+

_transferVotingUnits(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` +should be zero. Total supply of voting units will be adjusted with mints and burns. + +
+
+ + + +
+
+

_moveDelegateVotes(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Moves delegated votes from one delegate to another. + +
+
+ + + +
+
+

_numCheckpoints(address account) → uint32

+
+

internal

+# +
+
+
+ +Get number of checkpoints for `account`. + +
+
+ + + +
+
+

_checkpoints(address account, uint32 pos) → struct Checkpoints.Checkpoint208

+
+

internal

+# +
+
+
+ +Get the `pos`-th checkpoint for `account`. + +
+
+ + + +
+
+

_getVotingUnits(address) → uint256

+
+

internal

+# +
+
+
+ +Must return the voting units held by an account. + +
+
+ + + +
+
+

ERC6372InconsistentClock()

+
+

error

+# +
+
+
+ +The clock was incorrectly modified. + +
+
+ + + +
+
+

ERC5805FutureLookup(uint256 timepoint, uint48 clock)

+
+

error

+# +
+
+
+ +Lookup to future votes is not available. + +
+
+ + + +
+ +## `VotesExtended` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/governance/utils/VotesExtended.sol"; +``` + +Extension of [`Votes`](#Votes) that adds checkpoints for delegations and balances. + + +While this contract extends [`Votes`](#Votes), valid uses of [`Votes`](#Votes) may not be compatible with +[`VotesExtended`](#VotesExtended) without additional considerations. This implementation of [`Votes._transferVotingUnits`](#Votes-_transferVotingUnits-address-address-uint256-) must +run AFTER the voting weight movement is registered, such that it is reflected on [`Votes._getVotingUnits`](#Votes-_getVotingUnits-address-). + + +Said differently, [`VotesExtended`](#VotesExtended) MUST be integrated in a way that calls [`Votes._transferVotingUnits`](#Votes-_transferVotingUnits-address-address-uint256-) AFTER the +asset transfer is registered and balances are updated: + +```solidity +contract VotingToken is Token, VotesExtended { + function transfer(address from, address to, uint256 tokenId) public override { + super.transfer(from, to, tokenId); // <- Perform the transfer first ... + _transferVotingUnits(from, to, 1); // <- ... then call _transferVotingUnits. + } + + function _getVotingUnits(address account) internal view override returns (uint256) { + return balanceOf(account); + } +} +``` + +[`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes) and [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes) follow this pattern and are thus safe to use with [`VotesExtended`](#VotesExtended). + +
+

Functions

+
+- [getPastDelegate(account, timepoint)](#VotesExtended-getPastDelegate-address-uint256-) +- [getPastBalanceOf(account, timepoint)](#VotesExtended-getPastBalanceOf-address-uint256-) +- [_delegate(account, delegatee)](#VotesExtended-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#VotesExtended-_transferVotingUnits-address-address-uint256-) +#### Votes [!toc] +- [clock()](#Votes-clock--) +- [CLOCK_MODE()](#Votes-CLOCK_MODE--) +- [_validateTimepoint(timepoint)](#Votes-_validateTimepoint-uint256-) +- [getVotes(account)](#Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#Votes-getPastTotalSupply-uint256-) +- [_getTotalSupply()](#Votes-_getTotalSupply--) +- [delegates(account)](#Votes-delegates-address-) +- [delegate(delegatee)](#Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_moveDelegateVotes(from, to, amount)](#Votes-_moveDelegateVotes-address-address-uint256-) +- [_numCheckpoints(account)](#Votes-_numCheckpoints-address-) +- [_checkpoints(account, pos)](#Votes-_checkpoints-address-uint32-) +- [_getVotingUnits()](#Votes-_getVotingUnits-address-) +#### IERC5805 [!toc] +#### IVotes [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +
+
+ +
+

Events

+
+#### Votes [!toc] +#### IERC5805 [!toc] +#### IVotes [!toc] +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ +
+

Errors

+
+#### Votes [!toc] +- [ERC6372InconsistentClock()](#Votes-ERC6372InconsistentClock--) +- [ERC5805FutureLookup(timepoint, clock)](#Votes-ERC5805FutureLookup-uint256-uint48-) +#### IERC5805 [!toc] +#### IVotes [!toc] +- [VotesExpiredSignature(expiry)](#IVotes-VotesExpiredSignature-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +
+
+ + + +
+
+

getPastDelegate(address account, uint256 timepoint) → address

+
+

public

+# +
+
+
+ +Returns the delegate of an `account` at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

getPastBalanceOf(address account, uint256 timepoint) → uint256

+
+

public

+# +
+
+
+ +Returns the `balanceOf` of an `account` at a specific moment in the past. If the `clock()` is +configured to use block numbers, this will return the value at the end of the corresponding block. + +Requirements: + +- `timepoint` must be in the past. If operating using block numbers, the block must be already mined. + +
+
+ + + +
+
+

_delegate(address account, address delegatee)

+
+

internal

+# +
+
+
+ +Delegate all of `account`'s voting units to `delegatee`. + +Emits events [`IVotes.DelegateChanged`](#IVotes-DelegateChanged-address-address-address-) and [`IVotes.DelegateVotesChanged`](#IVotes-DelegateVotesChanged-address-uint256-uint256-). + +
+
+ + + +
+
+

_transferVotingUnits(address from, address to, uint256 amount)

+
+

internal

+# +
+
+
+ +Transfers, mints, or burns voting units. To register a mint, `from` should be zero. To register a burn, `to` +should be zero. Total supply of voting units will be adjusted with mints and burns. + +
+
diff --git a/docs/content/contracts/5.x/api/index.mdx b/docs/content/contracts/5.x/api/index.mdx new file mode 100644 index 00000000..ea12a036 --- /dev/null +++ b/docs/content/contracts/5.x/api/index.mdx @@ -0,0 +1,32 @@ +--- +title: API Reference +--- + +This API reference is automatically generated from the OpenZeppelin Contracts repository. + +## Contract Categories + +### Access Control +- [Access Control](/contracts/5.x/api/access) - Role-based access control mechanisms +- [Ownable](/contracts/5.x/api/access#ownable) - Simple ownership access control + +### Tokens +- [ERC20](/contracts/5.x/api/token/ERC20) - Fungible token standard implementation +- [ERC721](/contracts/5.x/api/token/ERC721) - Non-fungible token standard implementation +- [ERC1155](/contracts/5.x/api/token/ERC1155) - Multi-token standard implementation + +### Crosschain +- [ERC7786](/contracts/5.x/api/crosschain) - Contracts for sending and receiving crosschain messages + +### Utilities +- [Utils](/contracts/5.x/api/utils) - General utility functions and contracts +- [Cryptography](/contracts/5.x/api/utils/cryptography) - Cryptographic utilities + +### Governance +- [Governance](/contracts/5.x/api/governance) - On-chain governance systems + +### Proxy Patterns +- [Proxy](/contracts/5.x/api/proxy) - Upgradeable proxy patterns + +### Interfaces +- [Interfaces](/contracts/5.x/api/interfaces) - Standard interfaces diff --git a/docs/content/contracts/5.x/api/interfaces.mdx b/docs/content/contracts/5.x/api/interfaces.mdx new file mode 100644 index 00000000..fa89f4ea --- /dev/null +++ b/docs/content/contracts/5.x/api/interfaces.mdx @@ -0,0 +1,5062 @@ +--- +title: "Interfaces" +description: "Smart contract interfaces utilities and implementations" +--- + +## List of standardized interfaces +These interfaces are available as `.sol` files, and also as compiler `.json` ABI files (through the npm package). These +are useful to interact with third party contracts that implement them. + +* [`IERC20`](/contracts/5.x/api/token/ERC20#IERC20) +* [`IERC20Errors`](#IERC20Errors) +* [`IERC20Metadata`](/contracts/5.x/api/token/ERC20#IERC20Metadata) +* [`IERC165`](/contracts/5.x/api/utils#IERC165) +* [`IERC721`](/contracts/5.x/api/token/ERC721#IERC721) +* [`IERC721Receiver`](/contracts/5.x/api/token/ERC721#IERC721Receiver) +* [`IERC721Enumerable`](/contracts/5.x/api/token/ERC721#IERC721Enumerable) +* [`IERC721Metadata`](/contracts/5.x/api/token/ERC721#IERC721Metadata) +* [`IERC721Errors`](#IERC721Errors) +* [`IERC777`](#IERC777) +* [`IERC777Recipient`](#IERC777Recipient) +* [`IERC777Sender`](#IERC777Sender) +* [`IERC1155`](/contracts/5.x/api/token/ERC1155#IERC1155) +* [`IERC1155Receiver`](/contracts/5.x/api/token/ERC1155#IERC1155Receiver) +* [`IERC1155MetadataURI`](/contracts/5.x/api/token/ERC1155#IERC1155MetadataURI) +* [`IERC1155Errors`](#IERC1155Errors) +* [`IERC1271`](#IERC1271) +* [`IERC1363`](#IERC1363) +* [`IERC1363Receiver`](#IERC1363Receiver) +* [`IERC1363Spender`](#IERC1363Spender) +* [`IERC1820Implementer`](#IERC1820Implementer) +* [`IERC1820Registry`](#IERC1820Registry) +* [`IERC1822Proxiable`](#IERC1822Proxiable) +* [`IERC2612`](#IERC2612) +* [`IERC2981`](#IERC2981) +* [`IERC3156FlashLender`](#IERC3156FlashLender) +* [`IERC3156FlashBorrower`](#IERC3156FlashBorrower) +* [`IERC4626`](#IERC4626) +* [`IERC4906`](#IERC4906) +* [`IERC5267`](#IERC5267) +* [`IERC5313`](#IERC5313) +* [`IERC5805`](#IERC5805) +* [`IERC6372`](#IERC6372) +* [`IERC6909`](#IERC6909) +* [`IERC6909ContentURI`](#IERC6909ContentURI) +* [`IERC6909Metadata`](#IERC6909Metadata) +* [`IERC6909TokenSupply`](#IERC6909TokenSupply) +* [`IERC7674`](#IERC7674) +- [`IERC7751`](#IERC7751) +* [`IERC7786GatewaySource`](#IERC7786GatewaySource) +* [`IERC7786Recipient`](#IERC7786Recipient) +* [`IERC7802`](#IERC7802) + +## Detailed ABI + +[`IERC20Errors`](#IERC20Errors) + +[`IERC721Errors`](#IERC721Errors) + +[`IERC1155Errors`](#IERC1155Errors) + +[`IERC1271`](#IERC1271) + +[`IERC1363`](#IERC1363) + +[`IERC1363Receiver`](#IERC1363Receiver) + +[`IERC1363Spender`](#IERC1363Spender) + +[`IERC1820Implementer`](#IERC1820Implementer) + +[`IERC1820Registry`](#IERC1820Registry) + +[`IERC1822Proxiable`](#IERC1822Proxiable) + +[`IERC2612`](#IERC2612) + +[`IERC2981`](#IERC2981) + +[`IERC3156FlashLender`](#IERC3156FlashLender) + +[`IERC3156FlashBorrower`](#IERC3156FlashBorrower) + +[`IERC4626`](#IERC4626) + +[`IERC4906`](#IERC4906) + +[`IERC5267`](#IERC5267) + +[`IERC5313`](#IERC5313) + +[`IERC5805`](#IERC5805) + +[`IERC6372`](#IERC6372) + +[`IERC6909`](#IERC6909) + +[`IERC6909ContentURI`](#IERC6909ContentURI) + +[`IERC6909Metadata`](#IERC6909Metadata) + +[`IERC6909TokenSupply`](#IERC6909TokenSupply) + +[`IERC7674`](#IERC7674) + +[`IERC7751`](#IERC7751) + +[`IERC7786GatewaySource`](#IERC7786GatewaySource) + +[`IERC7786Recipient`](#IERC7786Recipient) + +[`IERC7802`](#IERC7802) + + + +
+ +## `IERC1271` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1271.sol"; +``` + +Interface of the ERC-1271 standard signature validation method for +contracts as defined in [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271). + +
+

Functions

+
+- [isValidSignature(hash, signature)](#IERC1271-isValidSignature-bytes32-bytes-) +
+
+ + + +
+
+

isValidSignature(bytes32 hash, bytes signature) → bytes4 magicValue

+
+

external

+# +
+
+
+ +Should return whether the signature provided is valid for the provided data + +
+
+ + + +
+ +## `IERC1363` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363.sol"; +``` + +Interface of the ERC-1363 standard as defined in the [ERC-1363](https://eips.ethereum.org/EIPS/eip-1363). + +Defines an extension interface for ERC-20 tokens that supports executing code on a recipient contract +after `transfer` or `transferFrom`, or code on a spender contract after `approve`, in a single transaction. + +
+

Functions

+
+- [transferAndCall(to, value)](#IERC1363-transferAndCall-address-uint256-) +- [transferAndCall(to, value, data)](#IERC1363-transferAndCall-address-uint256-bytes-) +- [transferFromAndCall(from, to, value)](#IERC1363-transferFromAndCall-address-address-uint256-) +- [transferFromAndCall(from, to, value, data)](#IERC1363-transferFromAndCall-address-address-uint256-bytes-) +- [approveAndCall(spender, value)](#IERC1363-approveAndCall-address-uint256-) +- [approveAndCall(spender, value, data)](#IERC1363-approveAndCall-address-uint256-bytes-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +#### IERC20 [!toc] +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, value)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, value)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+#### IERC165 [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

transferAndCall(address to, uint256 value) → bool

+
+

external

+# +
+
+
+ +Moves a `value` amount of tokens from the caller's account to `to` +and then calls [`IERC1363Receiver.onTransferReceived`](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on `to`. + +
+
+ + + +
+
+

transferAndCall(address to, uint256 value, bytes data) → bool

+
+

external

+# +
+
+
+ +Moves a `value` amount of tokens from the caller's account to `to` +and then calls [`IERC1363Receiver.onTransferReceived`](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on `to`. + +
+
+ + + +
+
+

transferFromAndCall(address from, address to, uint256 value) → bool

+
+

external

+# +
+
+
+ +Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism +and then calls [`IERC1363Receiver.onTransferReceived`](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on `to`. + +
+
+ + + +
+
+

transferFromAndCall(address from, address to, uint256 value, bytes data) → bool

+
+

external

+# +
+
+
+ +Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism +and then calls [`IERC1363Receiver.onTransferReceived`](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on `to`. + +
+
+ + + +
+
+

approveAndCall(address spender, uint256 value) → bool

+
+

external

+# +
+
+
+ +Sets a `value` amount of tokens as the allowance of `spender` over the +caller's tokens and then calls [`IERC1363Spender.onApprovalReceived`](#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) on `spender`. + +
+
+ + + +
+
+

approveAndCall(address spender, uint256 value, bytes data) → bool

+
+

external

+# +
+
+
+ +Sets a `value` amount of tokens as the allowance of `spender` over the +caller's tokens and then calls [`IERC1363Spender.onApprovalReceived`](#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) on `spender`. + +
+
+ + + +
+ +## `IERC1363Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; +``` + +Interface for any contract that wants to support `transferAndCall` or `transferFromAndCall` +from ERC-1363 token contracts. + +
+

Functions

+
+- [onTransferReceived(operator, from, value, data)](#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) +
+
+ + + +
+
+

onTransferReceived(address operator, address from, uint256 value, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Whenever ERC-1363 tokens are transferred to this contract via `transferAndCall` or `transferFromAndCall` +by `operator` from `from`, this function is called. + + +To accept the transfer, this must return +`bytes4(keccak256("onTransferReceived(address,address,uint256,bytes)"))` +(i.e. 0x88a7ca5c, or its own function selector). + + +
+
+ + + +
+ +## `IERC1363Spender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; +``` + +Interface for any contract that wants to support `approveAndCall` +from ERC-1363 token contracts. + +
+

Functions

+
+- [onApprovalReceived(owner, value, data)](#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) +
+
+ + + +
+
+

onApprovalReceived(address owner, uint256 value, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Whenever an ERC-1363 token `owner` approves this contract via `approveAndCall` +to spend their tokens, this function is called. + + +To accept the approval, this must return +`bytes4(keccak256("onApprovalReceived(address,uint256,bytes)"))` +(i.e. 0x7b04a2d0, or its own function selector). + + +
+
+ + + +
+ +## `IERC1820Implementer` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1820Implementer.sol"; +``` + +Interface for an ERC-1820 implementer, as defined in the +[ERC](https://eips.ethereum.org/EIPS/eip-1820#interface-implementation-erc1820implementerinterface). +Used by contracts that will be registered as implementers in the +[`IERC1820Registry`](#IERC1820Registry). + +
+

Functions

+
+- [canImplementInterfaceForAddress(interfaceHash, account)](#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-) +
+
+ + + +
+
+

canImplementInterfaceForAddress(bytes32 interfaceHash, address account) → bytes32

+
+

external

+# +
+
+
+ +Returns a special value (`ERC1820_ACCEPT_MAGIC`) if this contract +implements `interfaceHash` for `account`. + +See [`IERC1820Registry.setInterfaceImplementer`](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-). + +
+
+ + + +
+ +## `IERC1820Registry` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1820Registry.sol"; +``` + +Interface of the global ERC-1820 Registry, as defined in the +[ERC](https://eips.ethereum.org/EIPS/eip-1820). Accounts may register +implementers for interfaces in this registry, as well as query support. + +Implementers may be shared by multiple accounts, and can also implement more +than a single interface for each account. Contracts can implement interfaces +for themselves, but externally-owned accounts (EOA) must delegate this to a +contract. + +[`IERC165`](/contracts/5.x/api/utils#IERC165) interfaces can also be queried via the registry. + +For an in-depth explanation and source code analysis, see the ERC text. + +
+

Functions

+
+- [setManager(account, newManager)](#IERC1820Registry-setManager-address-address-) +- [getManager(account)](#IERC1820Registry-getManager-address-) +- [setInterfaceImplementer(account, _interfaceHash, implementer)](#IERC1820Registry-setInterfaceImplementer-address-bytes32-address-) +- [getInterfaceImplementer(account, _interfaceHash)](#IERC1820Registry-getInterfaceImplementer-address-bytes32-) +- [interfaceHash(interfaceName)](#IERC1820Registry-interfaceHash-string-) +- [updateERC165Cache(account, interfaceId)](#IERC1820Registry-updateERC165Cache-address-bytes4-) +- [implementsERC165Interface(account, interfaceId)](#IERC1820Registry-implementsERC165Interface-address-bytes4-) +- [implementsERC165InterfaceNoCache(account, interfaceId)](#IERC1820Registry-implementsERC165InterfaceNoCache-address-bytes4-) +
+
+ +
+

Events

+
+- [InterfaceImplementerSet(account, interfaceHash, implementer)](#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-) +- [ManagerChanged(account, newManager)](#IERC1820Registry-ManagerChanged-address-address-) +
+
+ + + +
+
+

setManager(address account, address newManager)

+
+

external

+# +
+
+
+ +Sets `newManager` as the manager for `account`. A manager of an +account is able to set interface implementers for it. + +By default, each account is its own manager. Passing a value of `0x0` in +`newManager` will reset the manager to this initial state. + +Emits a [`IERC1820Registry.ManagerChanged`](#IERC1820Registry-ManagerChanged-address-address-) event. + +Requirements: + +- the caller must be the current manager for `account`. + +
+
+ + + +
+
+

getManager(address account) → address

+
+

external

+# +
+
+
+ +Returns the manager for `account`. + +See [`IERC1820Registry.setManager`](#IERC1820Registry-setManager-address-address-). + +
+
+ + + +
+
+

setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer)

+
+

external

+# +
+
+
+ +Sets the `implementer` contract as ``account``'s implementer for +`interfaceHash`. + +`account` being the zero address is an alias for the caller's address. +The zero address can also be used in `implementer` to remove an old one. + +See [`IERC1820Registry.interfaceHash`](#IERC1820Registry-interfaceHash-string-) to learn how these are created. + +Emits an [`IERC1820Registry.InterfaceImplementerSet`](#IERC1820Registry-InterfaceImplementerSet-address-bytes32-address-) event. + +Requirements: + +- the caller must be the current manager for `account`. +- `interfaceHash` must not be an [`IERC165`](/contracts/5.x/api/utils#IERC165) interface id (i.e. it must not +end in 28 zeroes). +- `implementer` must implement [`IERC1820Implementer`](#IERC1820Implementer) and return true when +queried for support, unless `implementer` is the caller. See +[`IERC1820Implementer.canImplementInterfaceForAddress`](#IERC1820Implementer-canImplementInterfaceForAddress-bytes32-address-). + +
+
+ + + +
+
+

getInterfaceImplementer(address account, bytes32 _interfaceHash) → address

+
+

external

+# +
+
+
+ +Returns the implementer of `interfaceHash` for `account`. If no such +implementer is registered, returns the zero address. + +If `interfaceHash` is an [`IERC165`](/contracts/5.x/api/utils#IERC165) interface id (i.e. it ends with 28 +zeroes), `account` will be queried for support of it. + +`account` being the zero address is an alias for the caller's address. + +
+
+ + + +
+
+

interfaceHash(string interfaceName) → bytes32

+
+

external

+# +
+
+
+ +Returns the interface hash for an `interfaceName`, as defined in the +corresponding +[section of the ERC](https://eips.ethereum.org/EIPS/eip-1820#interface-name). + +
+
+ + + +
+
+

updateERC165Cache(address account, bytes4 interfaceId)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

implementsERC165Interface(address account, bytes4 interfaceId) → bool

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) → bool

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ManagerChanged(address indexed account, address indexed newManager)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC1967` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC1967.sol"; +``` + +ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. + +
+

Events

+
+- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +
+
+ + + +
+
+

Upgraded(address indexed implementation)

+
+

event

+# +
+
+ +
+ +Emitted when the implementation is upgraded. + +
+
+ + +
+
+

AdminChanged(address previousAdmin, address newAdmin)

+
+

event

+# +
+
+ +
+ +Emitted when the admin account has changed. + +
+
+ + +
+
+

BeaconUpgraded(address indexed beacon)

+
+

event

+# +
+
+ +
+ +Emitted when the beacon is changed. + +
+
+ + + +
+ +## `IERC2309` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC2309.sol"; +``` + +ERC-2309: ERC-721 Consecutive Transfer Extension. + +
+

Events

+
+- [ConsecutiveTransfer(fromTokenId, toTokenId, fromAddress, toAddress)](#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-) +
+
+ + + +
+
+

ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed fromAddress, address indexed toAddress)

+
+

event

+# +
+
+ +
+ +Emitted when the tokens from `fromTokenId` to `toTokenId` are transferred from `fromAddress` to `toAddress`. + +
+
+ + + +
+ +## `IERC2612` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC2612.sol"; +``` + +
+

Functions

+
+#### IERC20Permit [!toc] +- [permit(owner, spender, value, deadline, v, r, s)](#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#IERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#IERC20Permit-DOMAIN_SEPARATOR--) +
+
+ + + +
+ +## `IERC2981` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC2981.sol"; +``` + +Interface for the NFT Royalty Standard. + +A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal +support for royalty payments across all NFT marketplaces and ecosystem participants. + +
+

Functions

+
+- [royaltyInfo(tokenId, salePrice)](#IERC2981-royaltyInfo-uint256-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ + + +
+
+

royaltyInfo(uint256 tokenId, uint256 salePrice) → address receiver, uint256 royaltyAmount

+
+

external

+# +
+
+
+ +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of +exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + + +ERC-2981 allows setting the royalty to 100% of the price. In that case all the price would be sent to the +royalty receiver and 0 tokens to the seller. Contracts dealing with royalty should consider empty transfers. + + +
+
+ + + +
+ +## `IERC3156FlashBorrower` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; +``` + +Interface of the ERC-3156 FlashBorrower, as defined in +[ERC-3156](https://eips.ethereum.org/EIPS/eip-3156). + +
+

Functions

+
+- [onFlashLoan(initiator, token, amount, fee, data)](#IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-) +
+
+ + + +
+
+

onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes data) → bytes32

+
+

external

+# +
+
+
+ +Receive a flash loan. + +
+
+ + + +
+ +## `IERC3156FlashLender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC3156FlashLender.sol"; +``` + +Interface of the ERC-3156 FlashLender, as defined in +[ERC-3156](https://eips.ethereum.org/EIPS/eip-3156). + +
+

Functions

+
+- [maxFlashLoan(token)](#IERC3156FlashLender-maxFlashLoan-address-) +- [flashFee(token, amount)](#IERC3156FlashLender-flashFee-address-uint256-) +- [flashLoan(receiver, token, amount, data)](#IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-) +
+
+ + + +
+
+

maxFlashLoan(address token) → uint256

+
+

external

+# +
+
+
+ +The amount of currency available to be lent. + +
+
+ + + +
+
+

flashFee(address token, uint256 amount) → uint256

+
+

external

+# +
+
+
+ +The fee to be charged for a given loan. + +
+
+ + + +
+
+

flashLoan(contract IERC3156FlashBorrower receiver, address token, uint256 amount, bytes data) → bool

+
+

external

+# +
+
+
+ +Initiate a flash loan. + +
+
+ + + +
+ +## `IERC4626` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC4626.sol"; +``` + +Interface of the ERC-4626 "Tokenized Vault Standard", as defined in +[ERC-4626](https://eips.ethereum.org/EIPS/eip-4626). + +
+

Functions

+
+- [asset()](#IERC4626-asset--) +- [totalAssets()](#IERC4626-totalAssets--) +- [convertToShares(assets)](#IERC4626-convertToShares-uint256-) +- [convertToAssets(shares)](#IERC4626-convertToAssets-uint256-) +- [maxDeposit(receiver)](#IERC4626-maxDeposit-address-) +- [previewDeposit(assets)](#IERC4626-previewDeposit-uint256-) +- [deposit(assets, receiver)](#IERC4626-deposit-uint256-address-) +- [maxMint(receiver)](#IERC4626-maxMint-address-) +- [previewMint(shares)](#IERC4626-previewMint-uint256-) +- [mint(shares, receiver)](#IERC4626-mint-uint256-address-) +- [maxWithdraw(owner)](#IERC4626-maxWithdraw-address-) +- [previewWithdraw(assets)](#IERC4626-previewWithdraw-uint256-) +- [withdraw(assets, receiver, owner)](#IERC4626-withdraw-uint256-address-address-) +- [maxRedeem(owner)](#IERC4626-maxRedeem-address-) +- [previewRedeem(shares)](#IERC4626-previewRedeem-uint256-) +- [redeem(shares, receiver, owner)](#IERC4626-redeem-uint256-address-address-) +#### IERC20Metadata [!toc] +- [name()](#IERC20Metadata-name--) +- [symbol()](#IERC20Metadata-symbol--) +- [decimals()](#IERC20Metadata-decimals--) +#### IERC20 [!toc] +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, value)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, value)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+- [Deposit(sender, owner, assets, shares)](#IERC4626-Deposit-address-address-uint256-uint256-) +- [Withdraw(sender, receiver, owner, assets, shares)](#IERC4626-Withdraw-address-address-address-uint256-uint256-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

asset() → address assetTokenAddress

+
+

external

+# +
+
+
+ +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + +- MUST be an ERC-20 token contract. +- MUST NOT revert. + +
+
+ + + +
+
+

totalAssets() → uint256 totalManagedAssets

+
+

external

+# +
+
+
+ +Returns the total amount of the underlying asset that is “managed” by Vault. + +- SHOULD include any compounding that occurs from yield. +- MUST be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT revert. + +
+
+ + + +
+
+

convertToShares(uint256 assets) → uint256 shares

+
+

external

+# +
+
+
+ +Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + + +This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the +“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + + +
+
+ + + +
+
+

convertToAssets(uint256 shares) → uint256 assets

+
+

external

+# +
+
+
+ +Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + + +This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the +“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + + +
+
+ + + +
+
+

maxDeposit(address receiver) → uint256 maxAssets

+
+

external

+# +
+
+
+ +Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, +through a deposit call. + +- MUST return a limited value if receiver is subject to some deposit limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. +- MUST NOT revert. + +
+
+ + + +
+
+

previewDeposit(uint256 assets) → uint256 shares

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given +current on-chain conditions. + +- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + in the same transaction. +- MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + deposit would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + + +
+
+ + + +
+
+

deposit(uint256 assets, address receiver) → uint256 shares

+
+

external

+# +
+
+
+ +Deposit `assets` underlying tokens and send the corresponding number of vault shares (`shares`) to `receiver`. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + deposit execution, and are accounted for during deposit. +- MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + + +most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + + +
+
+ + + +
+
+

maxMint(address receiver) → uint256 maxShares

+
+

external

+# +
+
+
+ +Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. +- MUST return a limited value if receiver is subject to some mint limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. +- MUST NOT revert. + +
+
+ + + +
+
+

previewMint(uint256 shares) → uint256 assets

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given +current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + same transaction. +- MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by minting. + + +
+
+ + + +
+
+

mint(uint256 shares, address receiver) → uint256 assets

+
+

external

+# +
+
+
+ +Mints exactly `shares` vault shares to `receiver` in exchange for `assets` underlying tokens. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + execution, and are accounted for during mint. +- MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + + +most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + + +
+
+ + + +
+
+

maxWithdraw(address owner) → uint256 maxAssets

+
+

external

+# +
+
+
+ +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the +Vault, through a withdraw call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST NOT revert. + +
+
+ + + +
+
+

previewWithdraw(uint256 assets) → uint256 shares

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, +given current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + called + in the same transaction. +- MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + the withdrawal would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + + +
+
+ + + +
+
+

withdraw(uint256 assets, address receiver, address owner) → uint256 shares

+
+

external

+# +
+
+
+ +Burns shares from owner and sends exactly assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + withdraw execution, and are accounted for during withdraw. +- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + +Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + +
+
+ + + +
+
+

maxRedeem(address owner) → uint256 maxShares

+
+

external

+# +
+
+
+ +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, +through a redeem call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. +- MUST NOT revert. + +
+
+ + + +
+
+

previewRedeem(uint256 shares) → uint256 assets

+
+

external

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block, +given current on-chain conditions. + +- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + same transaction. +- MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + redemption would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by redeeming. + + +
+
+ + + +
+
+

redeem(uint256 shares, address receiver, address owner) → uint256 assets

+
+

external

+# +
+
+
+ +Burns exactly shares from owner and sends assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + redeem execution, and are accounted for during redeem. +- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + + +some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + + +
+
+ + + +
+
+

Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

Withdraw(address indexed sender, address indexed receiver, address indexed owner, uint256 assets, uint256 shares)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC4906` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC4906.sol"; +``` + +
+

Functions

+
+#### IERC721 [!toc] +- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [MetadataUpdate(_tokenId)](#IERC4906-MetadataUpdate-uint256-) +- [BatchMetadataUpdate(_fromTokenId, _toTokenId)](#IERC4906-BatchMetadataUpdate-uint256-uint256-) +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 [!toc] +
+
+ + + +
+
+

MetadataUpdate(uint256 _tokenId)

+
+

event

+# +
+
+ +
+ +This event emits when the metadata of a token is changed. +So that the third-party platforms such as NFT market could +timely update the images and related attributes of the NFT. + +
+
+ + +
+
+

BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId)

+
+

event

+# +
+
+ +
+ +This event emits when the metadata of a range of tokens is changed. +So that the third-party platforms such as NFT market could +timely update the images and related attributes of the NFTs. + +
+
+ + + +
+ +## `IERC5267` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC5267.sol"; +``` + +
+

Functions

+
+- [eip712Domain()](#IERC5267-eip712Domain--) +
+
+ +
+

Events

+
+- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ + + +
+
+

eip712Domain() → bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions

+
+

external

+# +
+
+
+ +returns the fields and values that describe the domain separator used by this contract for EIP-712 +signature. + +
+
+ + + +
+
+

EIP712DomainChanged()

+
+

event

+# +
+
+ +
+ +MAY be emitted to signal that the domain could have changed. + +
+
+ + + +
+ +## `IERC5313` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC5313.sol"; +``` + +Interface for the Light Contract Ownership Standard. + +A standardized minimal interface required to identify an account that controls a contract + +
+

Functions

+
+- [owner()](#IERC5313-owner--) +
+
+ + + +
+
+

owner() → address

+
+

external

+# +
+
+
+ +Gets the address of the owner. + +
+
+ + + +
+ +## `IERC5805` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC5805.sol"; +``` + +
+

Functions

+
+#### IVotes [!toc] +- [getVotes(account)](#IVotes-getVotes-address-) +- [getPastVotes(account, timepoint)](#IVotes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#IVotes-getPastTotalSupply-uint256-) +- [delegates(account)](#IVotes-delegates-address-) +- [delegate(delegatee)](#IVotes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#IVotes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +#### IERC6372 [!toc] +- [clock()](#IERC6372-clock--) +- [CLOCK_MODE()](#IERC6372-CLOCK_MODE--) +
+
+ +
+

Events

+
+#### IVotes [!toc] +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 [!toc] +
+
+ +
+

Errors

+
+#### IVotes [!toc] +- [VotesExpiredSignature(expiry)](#IVotes-VotesExpiredSignature-uint256-) +#### IERC6372 [!toc] +
+
+ + + +
+ +## `IERC6372` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC6372.sol"; +``` + +
+

Functions

+
+- [clock()](#IERC6372-clock--) +- [CLOCK_MODE()](#IERC6372-CLOCK_MODE--) +
+
+ + + +
+
+

clock() → uint48

+
+

external

+# +
+
+
+ +Clock used for flagging checkpoints. Can be overridden to implement timestamp based checkpoints (and voting). + +
+
+ + + +
+
+

CLOCK_MODE() → string

+
+

external

+# +
+
+
+ +Description of the clock + +
+
+ + + +
+ +## `IERC6909` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC6909.sol"; +``` + +Required interface of an ERC-6909 compliant contract, as defined in the +[ERC](https://eips.ethereum.org/EIPS/eip-6909). + +
+

Functions

+
+- [balanceOf(owner, id)](#IERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#IERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#IERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#IERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#IERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#IERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#IERC6909-transferFrom-address-address-uint256-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

balanceOf(address owner, uint256 id) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens of type `id` owned by `owner`. + +
+
+ + + +
+
+

allowance(address owner, address spender, uint256 id) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens of type `id` that `spender` is allowed to spend on behalf of `owner`. + + +Does not include operator allowances. + + +
+
+ + + +
+
+

isOperator(address owner, address spender) → bool

+
+

external

+# +
+
+
+ +Returns true if `spender` is set as an operator for `owner`. + +
+
+ + + +
+
+

approve(address spender, uint256 id, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Sets an approval to `spender` for `amount` of tokens of type `id` from the caller's tokens. An `amount` of +`type(uint256).max` signifies an unlimited approval. + +Must return true. + +
+
+ + + +
+
+

setOperator(address spender, bool approved) → bool

+
+

external

+# +
+
+
+ +Grants or revokes unlimited transfer permission of any token id to `spender` for the caller's tokens. + +Must return true. + +
+
+ + + +
+
+

transfer(address receiver, uint256 id, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Transfers `amount` of token type `id` from the caller's account to `receiver`. + +Must return true. + +
+
+ + + +
+
+

transferFrom(address sender, address receiver, uint256 id, uint256 amount) → bool

+
+

external

+# +
+
+
+ +Transfers `amount` of token type `id` from `sender` to `receiver`. + +Must return true. + +
+
+ + + +
+
+

Approval(address indexed owner, address indexed spender, uint256 indexed id, uint256 amount)

+
+

event

+# +
+
+ +
+ +Emitted when the allowance of a `spender` for an `owner` is set for a token of type `id`. +The new allowance is `amount`. + +
+
+ + +
+
+

OperatorSet(address indexed owner, address indexed spender, bool approved)

+
+

event

+# +
+
+ +
+ +Emitted when `owner` grants or revokes operator status for a `spender`. + +
+
+ + +
+
+

Transfer(address caller, address indexed sender, address indexed receiver, uint256 indexed id, uint256 amount)

+
+

event

+# +
+
+ +
+ +Emitted when `amount` tokens of type `id` are moved from `sender` to `receiver` initiated by `caller`. + +
+
+ + + +
+ +## `IERC6909Metadata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC6909.sol"; +``` + +Optional extension of [`IERC6909`](#IERC6909) that adds metadata functions. + +
+

Functions

+
+- [name(id)](#IERC6909Metadata-name-uint256-) +- [symbol(id)](#IERC6909Metadata-symbol-uint256-) +- [decimals(id)](#IERC6909Metadata-decimals-uint256-) +#### IERC6909 [!toc] +- [balanceOf(owner, id)](#IERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#IERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#IERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#IERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#IERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#IERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#IERC6909-transferFrom-address-address-uint256-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

name(uint256 id) → string

+
+

external

+# +
+
+
+ +Returns the name of the token of type `id`. + +
+
+ + + +
+
+

symbol(uint256 id) → string

+
+

external

+# +
+
+
+ +Returns the ticker symbol of the token of type `id`. + +
+
+ + + +
+
+

decimals(uint256 id) → uint8

+
+

external

+# +
+
+
+ +Returns the number of decimals for the token of type `id`. + +
+
+ + + +
+ +## `IERC6909ContentURI` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC6909.sol"; +``` + +Optional extension of [`IERC6909`](#IERC6909) that adds content URI functions. + +
+

Functions

+
+- [contractURI()](#IERC6909ContentURI-contractURI--) +- [tokenURI(id)](#IERC6909ContentURI-tokenURI-uint256-) +#### IERC6909 [!toc] +- [balanceOf(owner, id)](#IERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#IERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#IERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#IERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#IERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#IERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#IERC6909-transferFrom-address-address-uint256-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

contractURI() → string

+
+

external

+# +
+
+
+ +Returns URI for the contract. + +
+
+ + + +
+
+

tokenURI(uint256 id) → string

+
+

external

+# +
+
+
+ +Returns the URI for the token of type `id`. + +
+
+ + + +
+ +## `IERC6909TokenSupply` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC6909.sol"; +``` + +Optional extension of [`IERC6909`](#IERC6909) that adds a token supply function. + +
+

Functions

+
+- [totalSupply(id)](#IERC6909TokenSupply-totalSupply-uint256-) +#### IERC6909 [!toc] +- [balanceOf(owner, id)](#IERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#IERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#IERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#IERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#IERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#IERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#IERC6909-transferFrom-address-address-uint256-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

totalSupply(uint256 id) → uint256

+
+

external

+# +
+
+
+ +Returns the total supply of the token of type `id`. + +
+
+ + + +
+ +## `IERC7751` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC7751.sol"; +``` + +Wrapping of bubbled up reverts +Interface of the [ERC-7751](https://eips.ethereum.org/EIPS/eip-7751) wrapping of bubbled up reverts. + +
+

Errors

+
+- [WrappedError(target, selector, reason, details)](#IERC7751-WrappedError-address-bytes4-bytes-bytes-) +
+
+ + + +
+
+

WrappedError(address target, bytes4 selector, bytes reason, bytes details)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `IERC777` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC777.sol"; +``` + +Interface of the ERC-777 Token standard as defined in the ERC. + +This contract uses the +[ERC-1820 registry standard](https://eips.ethereum.org/EIPS/eip-1820) to let +token holders and recipients react to token movements by using setting implementers +for the associated interfaces in said registry. See [`IERC1820Registry`](#IERC1820Registry) and +[`IERC1820Implementer`](#IERC1820Implementer). + +
+

Functions

+
+- [name()](#IERC777-name--) +- [symbol()](#IERC777-symbol--) +- [granularity()](#IERC777-granularity--) +- [totalSupply()](#IERC777-totalSupply--) +- [balanceOf(owner)](#IERC777-balanceOf-address-) +- [send(recipient, amount, data)](#IERC777-send-address-uint256-bytes-) +- [burn(amount, data)](#IERC777-burn-uint256-bytes-) +- [isOperatorFor(operator, tokenHolder)](#IERC777-isOperatorFor-address-address-) +- [authorizeOperator(operator)](#IERC777-authorizeOperator-address-) +- [revokeOperator(operator)](#IERC777-revokeOperator-address-) +- [defaultOperators()](#IERC777-defaultOperators--) +- [operatorSend(sender, recipient, amount, data, operatorData)](#IERC777-operatorSend-address-address-uint256-bytes-bytes-) +- [operatorBurn(account, amount, data, operatorData)](#IERC777-operatorBurn-address-uint256-bytes-bytes-) +
+
+ +
+

Events

+
+- [Minted(operator, to, amount, data, operatorData)](#IERC777-Minted-address-address-uint256-bytes-bytes-) +- [Burned(operator, from, amount, data, operatorData)](#IERC777-Burned-address-address-uint256-bytes-bytes-) +- [AuthorizedOperator(operator, tokenHolder)](#IERC777-AuthorizedOperator-address-address-) +- [RevokedOperator(operator, tokenHolder)](#IERC777-RevokedOperator-address-address-) +- [Sent(operator, from, to, amount, data, operatorData)](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the symbol of the token, usually a shorter version of the +name. + +
+
+ + + +
+
+

granularity() → uint256

+
+

external

+# +
+
+
+ +Returns the smallest part of the token that is not divisible. This +means all token operations (creation, movement and destruction) must have +amounts that are a multiple of this number. + +For most token contracts, this value will equal 1. + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens in existence. + +
+
+ + + +
+
+

balanceOf(address owner) → uint256

+
+

external

+# +
+
+
+ +Returns the amount of tokens owned by an account (`owner`). + +
+
+ + + +
+
+

send(address recipient, uint256 amount, bytes data)

+
+

external

+# +
+
+
+ +Moves `amount` tokens from the caller's account to `recipient`. + +If send or receive hooks are registered for the caller and `recipient`, +the corresponding functions will be called with `data` and empty +`operatorData`. See [`IERC777Sender`](#IERC777Sender) and [`IERC777Recipient`](#IERC777Recipient). + +Emits a [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) event. + +Requirements + +- the caller must have at least `amount` tokens. +- `recipient` cannot be the zero address. +- if `recipient` is a contract, it must implement the [`IERC777Recipient`](#IERC777Recipient) +interface. + +
+
+ + + +
+
+

burn(uint256 amount, bytes data)

+
+

external

+# +
+
+
+ +Destroys `amount` tokens from the caller's account, reducing the +total supply. + +If a send hook is registered for the caller, the corresponding function +will be called with `data` and empty `operatorData`. See [`IERC777Sender`](#IERC777Sender). + +Emits a [`IERC777.Burned`](#IERC777-Burned-address-address-uint256-bytes-bytes-) event. + +Requirements + +- the caller must have at least `amount` tokens. + +
+
+ + + +
+
+

isOperatorFor(address operator, address tokenHolder) → bool

+
+

external

+# +
+
+
+ +Returns true if an account is an operator of `tokenHolder`. +Operators can send and burn tokens on behalf of their owners. All +accounts are their own operator. + +See [`IERC777.operatorSend`](#IERC777-operatorSend-address-address-uint256-bytes-bytes-) and [`IERC777.operatorBurn`](#IERC777-operatorBurn-address-uint256-bytes-bytes-). + +
+
+ + + +
+
+

authorizeOperator(address operator)

+
+

external

+# +
+
+
+ +Make an account an operator of the caller. + +See [`IERC777.isOperatorFor`](#IERC777-isOperatorFor-address-address-). + +Emits an [`IERC777.AuthorizedOperator`](#IERC777-AuthorizedOperator-address-address-) event. + +Requirements + +- `operator` cannot be calling address. + +
+
+ + + +
+
+

revokeOperator(address operator)

+
+

external

+# +
+
+
+ +Revoke an account's operator status for the caller. + +See [`IERC777.isOperatorFor`](#IERC777-isOperatorFor-address-address-) and [`IERC777.defaultOperators`](#IERC777-defaultOperators--). + +Emits a [`IERC777.RevokedOperator`](#IERC777-RevokedOperator-address-address-) event. + +Requirements + +- `operator` cannot be calling address. + +
+
+ + + +
+
+

defaultOperators() → address[]

+
+

external

+# +
+
+
+ +Returns the list of default operators. These accounts are operators +for all token holders, even if [`IERC777.authorizeOperator`](#IERC777-authorizeOperator-address-) was never called on +them. + +This list is immutable, but individual holders may revoke these via +[`IERC777.revokeOperator`](#IERC777-revokeOperator-address-), in which case [`IERC777.isOperatorFor`](#IERC777-isOperatorFor-address-address-) will return false. + +
+
+ + + +
+
+

operatorSend(address sender, address recipient, uint256 amount, bytes data, bytes operatorData)

+
+

external

+# +
+
+
+ +Moves `amount` tokens from `sender` to `recipient`. The caller must +be an operator of `sender`. + +If send or receive hooks are registered for `sender` and `recipient`, +the corresponding functions will be called with `data` and +`operatorData`. See [`IERC777Sender`](#IERC777Sender) and [`IERC777Recipient`](#IERC777Recipient). + +Emits a [`IERC777.Sent`](#IERC777-Sent-address-address-address-uint256-bytes-bytes-) event. + +Requirements + +- `sender` cannot be the zero address. +- `sender` must have at least `amount` tokens. +- the caller must be an operator for `sender`. +- `recipient` cannot be the zero address. +- if `recipient` is a contract, it must implement the [`IERC777Recipient`](#IERC777Recipient) +interface. + +
+
+ + + +
+
+

operatorBurn(address account, uint256 amount, bytes data, bytes operatorData)

+
+

external

+# +
+
+
+ +Destroys `amount` tokens from `account`, reducing the total supply. +The caller must be an operator of `account`. + +If a send hook is registered for `account`, the corresponding function +will be called with `data` and `operatorData`. See [`IERC777Sender`](#IERC777Sender). + +Emits a [`IERC777.Burned`](#IERC777-Burned-address-address-uint256-bytes-bytes-) event. + +Requirements + +- `account` cannot be the zero address. +- `account` must have at least `amount` tokens. +- the caller must be an operator for `account`. + +
+
+ + + +
+
+

Minted(address indexed operator, address indexed to, uint256 amount, bytes data, bytes operatorData)

+
+

event

+# +
+
+ +
+ +Emitted when `amount` tokens are created by `operator` and assigned to `to`. + +Note that some additional user `data` and `operatorData` can be logged in the event. + +
+
+ + +
+
+

Burned(address indexed operator, address indexed from, uint256 amount, bytes data, bytes operatorData)

+
+

event

+# +
+
+ +
+ +Emitted when `operator` destroys `amount` tokens from `account`. + +Note that some additional user `data` and `operatorData` can be logged in the event. + +
+
+ + +
+
+

AuthorizedOperator(address indexed operator, address indexed tokenHolder)

+
+

event

+# +
+
+ +
+ +Emitted when `operator` is made operator for `tokenHolder`. + +
+
+ + +
+
+

RevokedOperator(address indexed operator, address indexed tokenHolder)

+
+

event

+# +
+
+ +
+ +Emitted when `operator` is revoked its operator status for `tokenHolder`. + +
+
+ + +
+
+

Sent(address indexed operator, address indexed from, address indexed to, uint256 amount, bytes data, bytes operatorData)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC777Recipient` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC777Recipient.sol"; +``` + +Interface of the ERC-777 Tokens Recipient standard as defined in the ERC. + +Accounts can be notified of [`IERC777`](#IERC777) tokens being sent to them by having a +contract implement this interface (contract holders can be their own +implementer) and registering it on the +[ERC-1820 global registry](https://eips.ethereum.org/EIPS/eip-1820). + +See [`IERC1820Registry`](#IERC1820Registry) and [`IERC1820Implementer`](#IERC1820Implementer). + +
+

Functions

+
+- [tokensReceived(operator, from, to, amount, userData, operatorData)](#IERC777Recipient-tokensReceived-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

tokensReceived(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData)

+
+

external

+# +
+
+
+ +Called by an [`IERC777`](#IERC777) token contract whenever tokens are being +moved or created into a registered account (`to`). The type of operation +is conveyed by `from` being the zero address or not. + +This call occurs _after_ the token contract's state is updated, so +[`IERC777.balanceOf`](#IERC777-balanceOf-address-), etc., can be used to query the post-operation state. + +This function may revert to prevent the operation from being executed. + +
+
+ + + +
+ +## `IERC777Sender` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC777Sender.sol"; +``` + +Interface of the ERC-777 Tokens Sender standard as defined in the ERC. + +[`IERC777`](#IERC777) Token holders can be notified of operations performed on their +tokens by having a contract implement this interface (contract holders can be +their own implementer) and registering it on the +[ERC-1820 global registry](https://eips.ethereum.org/EIPS/eip-1820). + +See [`IERC1820Registry`](#IERC1820Registry) and [`IERC1820Implementer`](#IERC1820Implementer). + +
+

Functions

+
+- [tokensToSend(operator, from, to, amount, userData, operatorData)](#IERC777Sender-tokensToSend-address-address-address-uint256-bytes-bytes-) +
+
+ + + +
+
+

tokensToSend(address operator, address from, address to, uint256 amount, bytes userData, bytes operatorData)

+
+

external

+# +
+
+
+ +Called by an [`IERC777`](#IERC777) token contract whenever a registered holder's +(`from`) tokens are about to be moved or destroyed. The type of operation +is conveyed by `to` being the zero address or not. + +This call occurs _before_ the token contract's state is updated, so +[`IERC777.balanceOf`](#IERC777-balanceOf-address-), etc., can be used to query the pre-operation state. + +This function may revert to prevent the operation from being executed. + +
+
+ + + +
+ +## `IERC7913SignatureVerifier` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/IERC7913.sol"; +``` + +Signature verifier interface. + +
+

Functions

+
+- [verify(key, hash, signature)](#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-) +
+
+ + + +
+
+

verify(bytes key, bytes32 hash, bytes signature) → bytes4

+
+

external

+# +
+
+
+ +Verifies `signature` as a valid signature of `hash` by `key`. + +MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. +SHOULD return 0xffffffff or revert if the signature is not valid. +SHOULD return 0xffffffff or revert if the key is empty + +
+
+ + + +
+ +## `IERC1822Proxiable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC1822.sol"; +``` + +ERC-1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified +proxy whose upgrades are fully controlled by the current implementation. + +
+

Functions

+
+- [proxiableUUID()](#IERC1822Proxiable-proxiableUUID--) +
+
+ + + +
+
+

proxiableUUID() → bytes32

+
+

external

+# +
+
+
+ +Returns the storage slot that the proxiable contract assumes is being used to store the implementation +address. + + +A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks +bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this +function revert if invoked through a proxy. + + +
+
+ + + +
+ +## `PackedUserOperation` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +A [user operation](https://github.com/ethereum/ercs/blob/master/ERCS/erc-4337.md#useroperation) is composed of the following elements: +- `sender` (`address`): The account making the operation +- `nonce` (`uint256`): Anti-replay parameter (see “Semi-abstracted Nonce Support” ) +- `factory` (`address`): account factory, only for new accounts +- `factoryData` (`bytes`): data for account factory (only if account factory exists) +- `callData` (`bytes`): The data to pass to the sender during the main execution call +- `callGasLimit` (`uint256`): The amount of gas to allocate the main execution call +- `verificationGasLimit` (`uint256`): The amount of gas to allocate for the verification step +- `preVerificationGas` (`uint256`): Extra gas to pay the bundler +- `maxFeePerGas` (`uint256`): Maximum fee per gas (similar to EIP-1559 max_fee_per_gas) +- `maxPriorityFeePerGas` (`uint256`): Maximum priority fee per gas (similar to EIP-1559 max_priority_fee_per_gas) +- `paymaster` (`address`): Address of paymaster contract, (or empty, if account pays for itself) +- `paymasterVerificationGasLimit` (`uint256`): The amount of gas to allocate for the paymaster validation code +- `paymasterPostOpGasLimit` (`uint256`): The amount of gas to allocate for the paymaster post-operation code +- `paymasterData` (`bytes`): Data for paymaster (only if paymaster exists) +- `signature` (`bytes`): Data passed into the account to verify authorization + +When passed to on-chain contracts, the following packed version is used. +- `sender` (`address`) +- `nonce` (`uint256`) +- `initCode` (`bytes`): concatenation of factory address and factoryData (or empty) +- `callData` (`bytes`) +- `accountGasLimits` (`bytes32`): concatenation of verificationGas (16 bytes) and callGas (16 bytes) +- `preVerificationGas` (`uint256`) +- `gasFees` (`bytes32`): concatenation of maxPriorityFeePerGas (16 bytes) and maxFeePerGas (16 bytes) +- `paymasterAndData` (`bytes`): concatenation of paymaster fields (or empty) +- `signature` (`bytes`) + + + +
+ +## `IAggregator` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Aggregates and validates multiple signatures for a batch of user operations. + +A contract could implement this interface with custom validation schemes that allow signature aggregation, +enabling significant optimizations and gas savings for execution and transaction data cost. + +Bundlers and clients whitelist supported aggregators. + +See [ERC-7766](https://eips.ethereum.org/EIPS/eip-7766) + +
+

Functions

+
+- [validateUserOpSignature(userOp)](#IAggregator-validateUserOpSignature-struct-PackedUserOperation-) +- [aggregateSignatures(userOps)](#IAggregator-aggregateSignatures-struct-PackedUserOperation---) +- [validateSignatures(userOps, signature)](#IAggregator-validateSignatures-struct-PackedUserOperation---bytes-) +
+
+ + + +
+
+

validateUserOpSignature(struct PackedUserOperation userOp) → bytes sigForUserOp

+
+

external

+# +
+
+
+ +Validates the signature for a user operation. +Returns an alternative signature that should be used during bundling. + +
+
+ + + +
+
+

aggregateSignatures(struct PackedUserOperation[] userOps) → bytes aggregatesSignature

+
+

external

+# +
+
+
+ +Returns an aggregated signature for a batch of user operation's signatures. + +
+
+ + + +
+
+

validateSignatures(struct PackedUserOperation[] userOps, bytes signature)

+
+

external

+# +
+
+
+ +Validates that the aggregated signature is valid for the user operations. + +Requirements: + +- The aggregated signature MUST match the given list of operations. + +
+
+ + + +
+ +## `IEntryPointNonces` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Handle nonce management for accounts. + +Nonces are used in accounts as a replay protection mechanism and to ensure the order of user operations. +To avoid limiting the number of operations an account can perform, the interface allows using parallel +nonces by using a `key` parameter. + +See [ERC-4337 semi-abstracted nonce support](https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support). + +
+

Functions

+
+- [getNonce(sender, key)](#IEntryPointNonces-getNonce-address-uint192-) +
+
+ + + +
+
+

getNonce(address sender, uint192 key) → uint256 nonce

+
+

external

+# +
+
+
+ +Returns the nonce for a `sender` account and a `key`. + +Nonces for a certain `key` are always increasing. + +
+
+ + + +
+ +## `IEntryPointStake` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Handle stake management for entities (i.e. accounts, paymasters, factories). + +The EntryPoint must implement the following API to let entities like paymasters have a stake, +and thus have more flexibility in their storage access +(see [reputation, throttling and banning.](https://eips.ethereum.org/EIPS/eip-4337#reputation-scoring-and-throttlingbanning-for-global-entities)) + +
+

Functions

+
+- [balanceOf(account)](#IEntryPointStake-balanceOf-address-) +- [depositTo(account)](#IEntryPointStake-depositTo-address-) +- [withdrawTo(withdrawAddress, withdrawAmount)](#IEntryPointStake-withdrawTo-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#IEntryPointStake-addStake-uint32-) +- [unlockStake()](#IEntryPointStake-unlockStake--) +- [withdrawStake(withdrawAddress)](#IEntryPointStake-withdrawStake-address-payable-) +
+
+ + + +
+
+

balanceOf(address account) → uint256

+
+

external

+# +
+
+
+ +Returns the balance of the account. + +
+
+ + + +
+
+

depositTo(address account)

+
+

external

+# +
+
+
+ +Deposits `msg.value` to the account. + +
+
+ + + +
+
+

withdrawTo(address payable withdrawAddress, uint256 withdrawAmount)

+
+

external

+# +
+
+
+ +Withdraws `withdrawAmount` from the account to `withdrawAddress`. + +
+
+ + + +
+
+

addStake(uint32 unstakeDelaySec)

+
+

external

+# +
+
+
+ +Adds stake to the account with an unstake delay of `unstakeDelaySec`. + +
+
+ + + +
+
+

unlockStake()

+
+

external

+# +
+
+
+ +Unlocks the stake of the account. + +
+
+ + + +
+
+

withdrawStake(address payable withdrawAddress)

+
+

external

+# +
+
+
+ +Withdraws the stake of the account to `withdrawAddress`. + +
+
+ + + +
+ +## `IEntryPoint` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Entry point for user operations. + +User operations are validated and executed by this contract. + +
+

Functions

+
+- [handleOps(ops, beneficiary)](#IEntryPoint-handleOps-struct-PackedUserOperation---address-payable-) +- [handleAggregatedOps(opsPerAggregator, beneficiary)](#IEntryPoint-handleAggregatedOps-struct-IEntryPoint-UserOpsPerAggregator---address-payable-) +#### IEntryPointStake [!toc] +- [balanceOf(account)](#IEntryPointStake-balanceOf-address-) +- [depositTo(account)](#IEntryPointStake-depositTo-address-) +- [withdrawTo(withdrawAddress, withdrawAmount)](#IEntryPointStake-withdrawTo-address-payable-uint256-) +- [addStake(unstakeDelaySec)](#IEntryPointStake-addStake-uint32-) +- [unlockStake()](#IEntryPointStake-unlockStake--) +- [withdrawStake(withdrawAddress)](#IEntryPointStake-withdrawStake-address-payable-) +#### IEntryPointNonces [!toc] +- [getNonce(sender, key)](#IEntryPointNonces-getNonce-address-uint192-) +
+
+ +
+

Errors

+
+- [FailedOp(opIndex, reason)](#IEntryPoint-FailedOp-uint256-string-) +- [FailedOpWithRevert(opIndex, reason, inner)](#IEntryPoint-FailedOpWithRevert-uint256-string-bytes-) +#### IEntryPointStake [!toc] +#### IEntryPointNonces [!toc] +
+
+ + + +
+
+

handleOps(struct PackedUserOperation[] ops, address payable beneficiary)

+
+

external

+# +
+
+
+ +Executes a batch of user operations. + +
+
+ + + +
+
+

handleAggregatedOps(struct IEntryPoint.UserOpsPerAggregator[] opsPerAggregator, address payable beneficiary)

+
+

external

+# +
+
+
+ +Executes a batch of aggregated user operations per aggregator. + +
+
+ + + +
+
+

FailedOp(uint256 opIndex, string reason)

+
+

error

+# +
+
+
+ +A user operation at `opIndex` failed with `reason`. + +
+
+ + + +
+
+

FailedOpWithRevert(uint256 opIndex, string reason, bytes inner)

+
+

error

+# +
+
+
+ +A user operation at `opIndex` failed with `reason` and `inner` returned data. + +
+
+ + + +
+ +## `IAccount` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Base interface for an ERC-4337 account. + +
+

Functions

+
+- [validateUserOp(userOp, userOpHash, missingAccountFunds)](#IAccount-validateUserOp-struct-PackedUserOperation-bytes32-uint256-) +
+
+ + + +
+
+

validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 missingAccountFunds) → uint256 validationData

+
+

external

+# +
+
+
+ +Validates a user operation. + +* MUST validate the caller is a trusted EntryPoint +* MUST validate that the signature is a valid signature of the userOpHash, and SHOULD + return SIG_VALIDATION_FAILED (and not revert) on signature mismatch. Any other error MUST revert. +* MUST pay the entryPoint (caller) at least the “missingAccountFunds” (which might + be zero, in case the current account’s deposit is high enough) + +Returns an encoded packed validation data that is composed of the following elements: + +- `authorizer` (`address`): 0 for success, 1 for failure, otherwise the address of an authorizer contract +- `validUntil` (`uint48`): The UserOp is valid only up to this time. Zero for “infinite”. +- `validAfter` (`uint48`): The UserOp is valid only after this time. + +
+
+ + + +
+ +## `IAccountExecute` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Support for executing user operations by prepending the [`IAccountExecute.executeUserOp`](#IAccountExecute-executeUserOp-struct-PackedUserOperation-bytes32-) function selector +to the UserOperation's `callData`. + +
+

Functions

+
+- [executeUserOp(userOp, userOpHash)](#IAccountExecute-executeUserOp-struct-PackedUserOperation-bytes32-) +
+
+ + + +
+
+

executeUserOp(struct PackedUserOperation userOp, bytes32 userOpHash)

+
+

external

+# +
+
+
+ +Executes a user operation. + +
+
+ + + +
+ +## `IPaymaster` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +``` + +Interface for a paymaster contract that agrees to pay for the gas costs of a user operation. + + +A paymaster must hold a stake to cover the required entrypoint stake and also the gas for the transaction. + + +
+

Functions

+
+- [validatePaymasterUserOp(userOp, userOpHash, maxCost)](#IPaymaster-validatePaymasterUserOp-struct-PackedUserOperation-bytes32-uint256-) +- [postOp(mode, context, actualGasCost, actualUserOpFeePerGas)](#IPaymaster-postOp-enum-IPaymaster-PostOpMode-bytes-uint256-uint256-) +
+
+ + + +
+
+

validatePaymasterUserOp(struct PackedUserOperation userOp, bytes32 userOpHash, uint256 maxCost) → bytes context, uint256 validationData

+
+

external

+# +
+
+
+ +Validates whether the paymaster is willing to pay for the user operation. See +[`IAccount.validateUserOp`](#IAccount-validateUserOp-struct-PackedUserOperation-bytes32-uint256-) for additional information on the return value. + + +Bundlers will reject this method if it modifies the state, unless it's whitelisted. + + +
+
+ + + +
+
+

postOp(enum IPaymaster.PostOpMode mode, bytes context, uint256 actualGasCost, uint256 actualUserOpFeePerGas)

+
+

external

+# +
+
+
+ +Verifies the sender is the entrypoint. + +
+
+ + + +
+ +## `IERC20Errors` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +``` + +Standard ERC-20 Errors +Interface of the [ERC-6093](https://eips.ethereum.org/EIPS/eip-6093) custom errors for ERC-20 tokens. + +
+

Errors

+
+- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +
+
+ + + +
+
+

ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed)

+
+

error

+# +
+
+
+ +Indicates an error related to the current `balance` of a `sender`. Used in transfers. + +
+
+ + + +
+
+

ERC20InvalidSender(address sender)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `sender`. Used in transfers. + +
+
+ + + +
+
+

ERC20InvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `receiver`. Used in transfers. + +
+
+ + + +
+
+

ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed)

+
+

error

+# +
+
+
+ +Indicates a failure with the `spender`’s `allowance`. Used in transfers. + +
+
+ + + +
+
+

ERC20InvalidApprover(address approver)

+
+

error

+# +
+
+
+ +Indicates a failure with the `approver` of a token to be approved. Used in approvals. + +
+
+ + + +
+
+

ERC20InvalidSpender(address spender)

+
+

error

+# +
+
+
+ +Indicates a failure with the `spender` to be approved. Used in approvals. + +
+
+ + + +
+ +## `IERC721Errors` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +``` + +Standard ERC-721 Errors +Interface of the [ERC-6093](https://eips.ethereum.org/EIPS/eip-6093) custom errors for ERC-721 tokens. + +
+

Errors

+
+- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +
+
+ + + +
+
+

ERC721InvalidOwner(address owner)

+
+

error

+# +
+
+
+ +Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in ERC-721. +Used in balance queries. + +
+
+ + + +
+
+

ERC721NonexistentToken(uint256 tokenId)

+
+

error

+# +
+
+
+ +Indicates a `tokenId` whose `owner` is the zero address. + +
+
+ + + +
+
+

ERC721IncorrectOwner(address sender, uint256 tokenId, address owner)

+
+

error

+# +
+
+
+ +Indicates an error related to the ownership over a particular token. Used in transfers. + +
+
+ + + +
+
+

ERC721InvalidSender(address sender)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `sender`. Used in transfers. + +
+
+ + + +
+
+

ERC721InvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `receiver`. Used in transfers. + +
+
+ + + +
+
+

ERC721InsufficientApproval(address operator, uint256 tokenId)

+
+

error

+# +
+
+
+ +Indicates a failure with the `operator`’s approval. Used in transfers. + +
+
+ + + +
+
+

ERC721InvalidApprover(address approver)

+
+

error

+# +
+
+
+ +Indicates a failure with the `approver` of a token to be approved. Used in approvals. + +
+
+ + + +
+
+

ERC721InvalidOperator(address operator)

+
+

error

+# +
+
+
+ +Indicates a failure with the `operator` to be approved. Used in approvals. + +
+
+ + + +
+ +## `IERC1155Errors` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; +``` + +Standard ERC-1155 Errors +Interface of the [ERC-6093](https://eips.ethereum.org/EIPS/eip-6093) custom errors for ERC-1155 tokens. + +
+

Errors

+
+- [ERC1155InsufficientBalance(sender, balance, needed, tokenId)](#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC1155InvalidSender(sender)](#IERC1155Errors-ERC1155InvalidSender-address-) +- [ERC1155InvalidReceiver(receiver)](#IERC1155Errors-ERC1155InvalidReceiver-address-) +- [ERC1155MissingApprovalForAll(operator, owner)](#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-) +- [ERC1155InvalidApprover(approver)](#IERC1155Errors-ERC1155InvalidApprover-address-) +- [ERC1155InvalidOperator(operator)](#IERC1155Errors-ERC1155InvalidOperator-address-) +- [ERC1155InvalidArrayLength(idsLength, valuesLength)](#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-) +
+
+ + + +
+
+

ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId)

+
+

error

+# +
+
+
+ +Indicates an error related to the current `balance` of a `sender`. Used in transfers. + +
+
+ + + +
+
+

ERC1155InvalidSender(address sender)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `sender`. Used in transfers. + +
+
+ + + +
+
+

ERC1155InvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `receiver`. Used in transfers. + +
+
+ + + +
+
+

ERC1155MissingApprovalForAll(address operator, address owner)

+
+

error

+# +
+
+
+ +Indicates a failure with the `operator`’s approval. Used in transfers. + +
+
+ + + +
+
+

ERC1155InvalidApprover(address approver)

+
+

error

+# +
+
+
+ +Indicates a failure with the `approver` of a token to be approved. Used in approvals. + +
+
+ + + +
+
+

ERC1155InvalidOperator(address operator)

+
+

error

+# +
+
+
+ +Indicates a failure with the `operator` to be approved. Used in approvals. + +
+
+ + + +
+
+

ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength)

+
+

error

+# +
+
+
+ +Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation. +Used in batch transfers. + +
+
+ + + +
+ +## `VALIDATION_SUCCESS` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `VALIDATION_FAILED` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `MODULE_TYPE_VALIDATOR` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `MODULE_TYPE_EXECUTOR` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `MODULE_TYPE_FALLBACK` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `MODULE_TYPE_HOOK` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `IERC7579Module` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + +Minimal configuration interface for ERC-7579 modules + +
+

Functions

+
+- [onInstall(data)](#IERC7579Module-onInstall-bytes-) +- [onUninstall(data)](#IERC7579Module-onUninstall-bytes-) +- [isModuleType(moduleTypeId)](#IERC7579Module-isModuleType-uint256-) +
+
+ + + +
+
+

onInstall(bytes data)

+
+

external

+# +
+
+
+ +This function is called by the smart account during installation of the module + +
+
+ + + +
+
+

onUninstall(bytes data)

+
+

external

+# +
+
+
+ +This function is called by the smart account during uninstallation of the module + +
+
+ + + +
+
+

isModuleType(uint256 moduleTypeId) → bool

+
+

external

+# +
+
+
+ +Returns boolean value if module is a certain type + +
+
+ + + +
+ +## `IERC7579Validator` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + +ERC-7579 Validation module (type 1). + +A module that implements logic to validate user operations and signatures. + +
+

Functions

+
+- [validateUserOp(userOp, userOpHash)](#IERC7579Validator-validateUserOp-struct-PackedUserOperation-bytes32-) +- [isValidSignatureWithSender(sender, hash, signature)](#IERC7579Validator-isValidSignatureWithSender-address-bytes32-bytes-) +#### IERC7579Module [!toc] +- [onInstall(data)](#IERC7579Module-onInstall-bytes-) +- [onUninstall(data)](#IERC7579Module-onUninstall-bytes-) +- [isModuleType(moduleTypeId)](#IERC7579Module-isModuleType-uint256-) +
+
+ + + +
+
+

validateUserOp(struct PackedUserOperation userOp, bytes32 userOpHash) → uint256

+
+

external

+# +
+
+
+ +Validates a UserOperation + +
+
+ + + +
+
+

isValidSignatureWithSender(address sender, bytes32 hash, bytes signature) → bytes4

+
+

external

+# +
+
+
+ +Validates a signature using ERC-1271 + +
+
+ + + +
+ +## `IERC7579Hook` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + +ERC-7579 Hooks module (type 4). + +A module that implements logic to execute before and after the account executes a user operation, +either individually or batched. + +
+

Functions

+
+- [preCheck(msgSender, value, msgData)](#IERC7579Hook-preCheck-address-uint256-bytes-) +- [postCheck(hookData)](#IERC7579Hook-postCheck-bytes-) +#### IERC7579Module [!toc] +- [onInstall(data)](#IERC7579Module-onInstall-bytes-) +- [onUninstall(data)](#IERC7579Module-onUninstall-bytes-) +- [isModuleType(moduleTypeId)](#IERC7579Module-isModuleType-uint256-) +
+
+ + + +
+
+

preCheck(address msgSender, uint256 value, bytes msgData) → bytes hookData

+
+

external

+# +
+
+
+ +Called by the smart account before execution + +
+
+ + + +
+
+

postCheck(bytes hookData)

+
+

external

+# +
+
+
+ +Called by the smart account after execution + +
+
+ + + +
+ +## `Execution` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + + + +
+ +## `IERC7579Execution` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + +ERC-7579 Execution. + +Accounts should implement this interface so that the Entrypoint and ERC-7579 modules can execute operations. + +
+

Functions

+
+- [execute(mode, executionCalldata)](#IERC7579Execution-execute-bytes32-bytes-) +- [executeFromExecutor(mode, executionCalldata)](#IERC7579Execution-executeFromExecutor-bytes32-bytes-) +
+
+ + + +
+
+

execute(bytes32 mode, bytes executionCalldata)

+
+

external

+# +
+
+
+ +Executes a transaction on behalf of the account. + +
+
+ + + +
+
+

executeFromExecutor(bytes32 mode, bytes executionCalldata) → bytes[] returnData

+
+

external

+# +
+
+
+ +Executes a transaction on behalf of the account. + This function is intended to be called by Executor Modules + +
+
+ + + +
+ +## `IERC7579AccountConfig` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + +ERC-7579 Account Config. + +Accounts should implement this interface to expose information that identifies the account, supported modules and capabilities. + +
+

Functions

+
+- [accountId()](#IERC7579AccountConfig-accountId--) +- [supportsExecutionMode(encodedMode)](#IERC7579AccountConfig-supportsExecutionMode-bytes32-) +- [supportsModule(moduleTypeId)](#IERC7579AccountConfig-supportsModule-uint256-) +
+
+ + + +
+
+

accountId() → string accountImplementationId

+
+

external

+# +
+
+
+ +Returns the account id of the smart account + +
+
+ + + +
+
+

supportsExecutionMode(bytes32 encodedMode) → bool

+
+

external

+# +
+
+
+ +Function to check if the account supports a certain execution mode (see above) + +
+
+ + + +
+
+

supportsModule(uint256 moduleTypeId) → bool

+
+

external

+# +
+
+
+ +Function to check if the account supports a certain module typeId + +
+
+ + + +
+ +## `IERC7579ModuleConfig` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +``` + +ERC-7579 Module Config. + +Accounts should implement this interface to allow installing and uninstalling modules. + +
+

Functions

+
+- [installModule(moduleTypeId, module, initData)](#IERC7579ModuleConfig-installModule-uint256-address-bytes-) +- [uninstallModule(moduleTypeId, module, deInitData)](#IERC7579ModuleConfig-uninstallModule-uint256-address-bytes-) +- [isModuleInstalled(moduleTypeId, module, additionalContext)](#IERC7579ModuleConfig-isModuleInstalled-uint256-address-bytes-) +
+
+ +
+

Events

+
+- [ModuleInstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleInstalled-uint256-address-) +- [ModuleUninstalled(moduleTypeId, module)](#IERC7579ModuleConfig-ModuleUninstalled-uint256-address-) +
+
+ + + +
+
+

installModule(uint256 moduleTypeId, address module, bytes initData)

+
+

external

+# +
+
+
+ +Installs a Module of a certain type on the smart account + +
+
+ + + +
+
+

uninstallModule(uint256 moduleTypeId, address module, bytes deInitData)

+
+

external

+# +
+
+
+ +Uninstalls a Module of a certain type on the smart account + +
+
+ + + +
+
+

isModuleInstalled(uint256 moduleTypeId, address module, bytes additionalContext) → bool

+
+

external

+# +
+
+
+ +Returns whether a module is installed on the smart account + +
+
+ + + +
+
+

ModuleInstalled(uint256 moduleTypeId, address module)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

ModuleUninstalled(uint256 moduleTypeId, address module)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC7674` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7674.sol"; +``` + +Temporary Approval Extension for ERC-20 ([ERC-7674](https://github.com/ethereum/ERCs/pull/358)) + +
+

Functions

+
+- [temporaryApprove(spender, value)](#IERC7674-temporaryApprove-address-uint256-) +#### IERC20 [!toc] +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, value)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, value)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

temporaryApprove(address spender, uint256 value) → bool success

+
+

external

+# +
+
+
+ +Set the temporary allowance, allowing `spender` to withdraw (within the same transaction) assets +held by the caller. + +
+
+ + + +
+ +## `IERC7786GatewaySource` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7786.sol"; +``` + +Interface for ERC-7786 source gateways. + +See ERC-7786 for more details + +
+

Functions

+
+- [supportsAttribute(selector)](#IERC7786GatewaySource-supportsAttribute-bytes4-) +- [sendMessage(recipient, payload, attributes)](#IERC7786GatewaySource-sendMessage-bytes-bytes-bytes---) +
+
+ +
+

Events

+
+- [MessageSent(sendId, sender, recipient, payload, value, attributes)](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) +
+
+ +
+

Errors

+
+- [UnsupportedAttribute(selector)](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) +
+
+ + + +
+
+

supportsAttribute(bytes4 selector) → bool

+
+

external

+# +
+
+
+ +Getter to check whether an attribute is supported or not. + +
+
+ + + +
+
+

sendMessage(bytes recipient, bytes payload, bytes[] attributes) → bytes32 sendId

+
+

external

+# +
+
+
+ +Endpoint for creating a new message. If the message requires further (gateway specific) processing before +it can be sent to the destination chain, then a non-zero `sendId` must be returned. Otherwise, the +message MUST be sent and this function must return 0. + +* MUST emit a [`IERC7786GatewaySource.MessageSent`](#IERC7786GatewaySource-MessageSent-bytes32-bytes-bytes-bytes-uint256-bytes---) event. + +If any of the `attributes` is not supported, this function SHOULD revert with an [`IERC7786GatewaySource.UnsupportedAttribute`](#IERC7786GatewaySource-UnsupportedAttribute-bytes4-) error. +Other errors SHOULD revert with errors not specified in ERC-7786. + +
+
+ + + +
+
+

MessageSent(bytes32 indexed sendId, bytes sender, bytes recipient, bytes payload, uint256 value, bytes[] attributes)

+
+

event

+# +
+
+ +
+ +Event emitted when a message is created. If `sendId` is zero, no further processing is necessary. If +`sendId` is not zero, then further (gateway specific, and non-standardized) action is required. + +
+
+ + + +
+
+

UnsupportedAttribute(bytes4 selector)

+
+

error

+# +
+
+
+ +This error is thrown when a message creation fails because of an unsupported attribute being specified. + +
+
+ + + +
+ +## `IERC7786Recipient` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7786.sol"; +``` + +Interface for the ERC-7786 client contract (receiver). + +See ERC-7786 for more details + +
+

Functions

+
+- [receiveMessage(receiveId, sender, payload)](#IERC7786Recipient-receiveMessage-bytes32-bytes-bytes-) +
+
+ + + +
+
+

receiveMessage(bytes32 receiveId, bytes sender, bytes payload) → bytes4

+
+

external

+# +
+
+
+ +Endpoint for receiving cross-chain message. + +This function may be called directly by the gateway. + +
+
+ + + +
+ +## `IERC7802` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; +``` + +
+

Functions

+
+- [crosschainMint(_to, _amount)](#IERC7802-crosschainMint-address-uint256-) +- [crosschainBurn(_from, _amount)](#IERC7802-crosschainBurn-address-uint256-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [CrosschainMint(to, amount, sender)](#IERC7802-CrosschainMint-address-uint256-address-) +- [CrosschainBurn(from, amount, sender)](#IERC7802-CrosschainBurn-address-uint256-address-) +#### IERC165 [!toc] +
+
+ + + +
+
+

crosschainMint(address _to, uint256 _amount)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

crosschainBurn(address _from, uint256 _amount)

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

CrosschainMint(address indexed to, uint256 amount, address indexed sender)

+
+

event

+# +
+
+ +
+ +
+
+ + +
+
+

CrosschainBurn(address indexed from, uint256 amount, address indexed sender)

+
+

event

+# +
+
+ +
+ +
+
+ + + +
+ +## `IERC7821` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/interfaces/draft-IERC7821.sol"; +``` + +Interface for minimal batch executor. + +
+

Functions

+
+- [execute(mode, executionData)](#IERC7821-execute-bytes32-bytes-) +- [supportsExecutionMode(mode)](#IERC7821-supportsExecutionMode-bytes32-) +
+
+ + + +
+
+

execute(bytes32 mode, bytes executionData)

+
+

external

+# +
+
+
+ +Executes the calls in `executionData`. +Reverts and bubbles up error if any call fails. + +`executionData` encoding: +- If `opData` is empty, `executionData` is simply `abi.encode(calls)`. +- Else, `executionData` is `abi.encode(calls, opData)`. + See: https://eips.ethereum.org/EIPS/eip-7579 + +Supported modes: +- `bytes32(0x01000000000000000000...)`: does not support optional `opData`. +- `bytes32(0x01000000000078210001...)`: supports optional `opData`. + +Authorization checks: +- If `opData` is empty, the implementation SHOULD require that + `msg.sender == address(this)`. +- If `opData` is not empty, the implementation SHOULD use the signature + encoded in `opData` to determine if the caller can perform the execution. + +`opData` may be used to store additional data for authentication, +paymaster data, gas limits, etc. + +For calldata compression efficiency, if a Call.to is `address(0)`, +it will be replaced with `address(this)`. + +
+
+ + + +
+
+

supportsExecutionMode(bytes32 mode) → bool

+
+

external

+# +
+
+
+ +This function is provided for frontends to detect support. +Only returns true for: +- `bytes32(0x01000000000000000000...)`: does not support optional `opData`. +- `bytes32(0x01000000000078210001...)`: supports optional `opData`. + +
+
diff --git a/docs/content/contracts/5.x/api/metatx.mdx b/docs/content/contracts/5.x/api/metatx.mdx new file mode 100644 index 00000000..438e9c0e --- /dev/null +++ b/docs/content/contracts/5.x/api/metatx.mdx @@ -0,0 +1,563 @@ +--- +title: "Metatx" +description: "Smart contract metatx utilities and implementations" +--- + +This directory includes contracts for adding meta-transaction capabilities (i.e. abstracting the execution context from the transaction origin) following the [ERC-2771 specification](https://eips.ethereum.org/EIPS/eip-2771). + +* [`ERC2771Context`](#ERC2771Context): Provides a mechanism to override the sender and calldata of the execution context (`msg.sender` and `msg.data`) with a custom value specified by a trusted forwarder. +* [`ERC2771Forwarder`](#ERC2771Forwarder): A production-ready forwarder that relays operation requests signed off-chain by an EOA. + +## Core + +[`ERC2771Context`](#ERC2771Context) + +## Utils + +[`ERC2771Forwarder`](#ERC2771Forwarder) + + + +
+ +## `ERC2771Context` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +``` + +Context variant with ERC-2771 support. + + +Avoid using this pattern in contracts that rely on a specific calldata length as they'll +be affected by any forwarder whose `msg.data` is suffixed with the `from` address according to the ERC-2771 +specification adding the address size in bytes (20) to the calldata size. An example of an unexpected +behavior could be an unintended fallback (or another function) invocation while trying to invoke the `receive` +function only accessible if `msg.data.length == 0`. + + + +The usage of `delegatecall` in this contract is dangerous and may result in context corruption. +Any forwarded request to this contract triggering a `delegatecall` to itself will result in an invalid [`ERC2771Context._msgSender`](#ERC2771Context-_msgSender--) +recovery. + + +
+

Functions

+
+- [constructor(trustedForwarder_)](#ERC2771Context-constructor-address-) +- [trustedForwarder()](#ERC2771Context-trustedForwarder--) +- [isTrustedForwarder(forwarder)](#ERC2771Context-isTrustedForwarder-address-) +- [_msgSender()](#ERC2771Context-_msgSender--) +- [_msgData()](#ERC2771Context-_msgData--) +- [_contextSuffixLength()](#ERC2771Context-_contextSuffixLength--) +
+
+ + + +
+
+

constructor(address trustedForwarder_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

trustedForwarder() → address

+
+

public

+# +
+
+
+ +Returns the address of the trusted forwarder. + +
+
+ + + +
+
+

isTrustedForwarder(address forwarder) → bool

+
+

public

+# +
+
+
+ +Indicates whether any particular address is the trusted forwarder. + +
+
+ + + +
+
+

_msgSender() → address

+
+

internal

+# +
+
+
+ +Override for `msg.sender`. Defaults to the original `msg.sender` whenever +a call is not performed by the trusted forwarder or the calldata length is less than +20 bytes (an address length). + +
+
+ + + +
+
+

_msgData() → bytes

+
+

internal

+# +
+
+
+ +Override for `msg.data`. Defaults to the original `msg.data` whenever +a call is not performed by the trusted forwarder or the calldata length is less than +20 bytes (an address length). + +
+
+ + + +
+
+

_contextSuffixLength() → uint256

+
+

internal

+# +
+
+
+ +ERC-2771 specifies the context as being a single address (20 bytes). + +
+
+ + + +
+ +## `ERC2771Forwarder` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; +``` + +A forwarder compatible with ERC-2771 contracts. See [`ERC2771Context`](#ERC2771Context). + +This forwarder operates on forward requests that include: + +* `from`: An address to operate on behalf of. It is required to be equal to the request signer. +* `to`: The address that should be called. +* `value`: The amount of native token to attach with the requested call. +* `gas`: The amount of gas limit that will be forwarded with the requested call. +* `nonce`: A unique transaction ordering identifier to avoid replayability and request invalidation. +* `deadline`: A timestamp after which the request is not executable anymore. +* `data`: Encoded `msg.data` to send with the requested call. + +Relayers are able to submit batches if they are processing a high volume of requests. With high +throughput, relayers may run into limitations of the chain such as limits on the number of +transactions in the mempool. In these cases the recommendation is to distribute the load among +multiple accounts. + + +Batching requests includes an optional refund for unused `msg.value` that is achieved by +performing a call with empty calldata. While this is within the bounds of ERC-2771 compliance, +if the refund receiver happens to consider the forwarder a trusted forwarder, it MUST properly +handle `msg.data.length == 0`. `ERC2771Context` in OpenZeppelin Contracts versions prior to 4.9.3 +do not handle this properly. + + +==== Security Considerations + +If a relayer submits a forward request, it should be willing to pay up to 100% of the gas amount +specified in the request. This contract does not implement any kind of retribution for this gas, +and it is assumed that there is an out of band incentive for relayers to pay for execution on +behalf of signers. Often, the relayer is operated by a project that will consider it a user +acquisition cost. + +By offering to pay for gas, relayers are at risk of having that gas used by an attacker toward +some other purpose that is not aligned with the expected out of band incentives. If you operate a +relayer, consider whitelisting target contracts and function selectors. When relaying ERC-721 or +ERC-1155 transfers specifically, consider rejecting the use of the `data` field, since it can be +used to execute arbitrary code. + +
+

Functions

+
+- [constructor(name)](#ERC2771Forwarder-constructor-string-) +- [verify(request)](#ERC2771Forwarder-verify-struct-ERC2771Forwarder-ForwardRequestData-) +- [execute(request)](#ERC2771Forwarder-execute-struct-ERC2771Forwarder-ForwardRequestData-) +- [executeBatch(requests, refundReceiver)](#ERC2771Forwarder-executeBatch-struct-ERC2771Forwarder-ForwardRequestData---address-payable-) +- [_validate(request)](#ERC2771Forwarder-_validate-struct-ERC2771Forwarder-ForwardRequestData-) +- [_recoverForwardRequestSigner(request)](#ERC2771Forwarder-_recoverForwardRequestSigner-struct-ERC2771Forwarder-ForwardRequestData-) +- [_execute(request, requireValidRequest)](#ERC2771Forwarder-_execute-struct-ERC2771Forwarder-ForwardRequestData-bool-) +- [_isTrustedByTarget(target)](#ERC2771Forwarder-_isTrustedByTarget-address-) +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +
+
+ +
+

Events

+
+- [ExecutedForwardRequest(signer, nonce, success)](#ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-) +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ +
+

Errors

+
+- [ERC2771ForwarderInvalidSigner(signer, from)](#ERC2771Forwarder-ERC2771ForwarderInvalidSigner-address-address-) +- [ERC2771ForwarderMismatchedValue(requestedValue, msgValue)](#ERC2771Forwarder-ERC2771ForwarderMismatchedValue-uint256-uint256-) +- [ERC2771ForwarderExpiredRequest(deadline)](#ERC2771Forwarder-ERC2771ForwarderExpiredRequest-uint48-) +- [ERC2771UntrustfulTarget(target, forwarder)](#ERC2771Forwarder-ERC2771UntrustfulTarget-address-address-) +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +
+
+ + + +
+
+

constructor(string name)

+
+

public

+# +
+
+
+ +See [`EIP712.constructor`](/contracts/5.x/api/utils/cryptography#EIP712-constructor-string-string-). + +
+
+ + + +
+
+

verify(struct ERC2771Forwarder.ForwardRequestData request) → bool

+
+

public

+# +
+
+
+ +Returns `true` if a request is valid for a provided `signature` at the current block timestamp. + +A transaction is considered valid when the target trusts this forwarder, the request hasn't expired +(deadline is not met), and the signer matches the `from` parameter of the signed request. + + +A request may return false here but it won't cause [`TimelockController.executeBatch`](/contracts/5.x/api/governance#TimelockController-executeBatch-address---uint256---bytes---bytes32-bytes32-) to revert if a refund +receiver is provided. + + +
+
+ + + +
+
+

execute(struct ERC2771Forwarder.ForwardRequestData request)

+
+

public

+# +
+
+
+ +Executes a `request` on behalf of `signature`'s signer using the ERC-2771 protocol. The gas +provided to the requested call may not be exactly the amount requested, but the call will not run +out of gas. Will revert if the request is invalid or the call reverts, in this case the nonce is not consumed. + +Requirements: + +- The request value should be equal to the provided `msg.value`. +- The request should be valid according to [`IERC7913SignatureVerifier.verify`](/contracts/5.x/api/interfaces#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-). + +
+
+ + + +
+
+

executeBatch(struct ERC2771Forwarder.ForwardRequestData[] requests, address payable refundReceiver)

+
+

public

+# +
+
+
+ +Batch version of [`AccessManager.execute`](/contracts/5.x/api/access#AccessManager-execute-address-bytes-) with optional refunding and atomic execution. + +In case a batch contains at least one invalid request (see [`IERC7913SignatureVerifier.verify`](/contracts/5.x/api/interfaces#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-)), the +request will be skipped and the `refundReceiver` parameter will receive back the +unused requested value at the end of the execution. This is done to prevent reverting +the entire batch when a request is invalid or has already been submitted. + +If the `refundReceiver` is the `address(0)`, this function will revert when at least +one of the requests was not valid instead of skipping it. This could be useful if +a batch is required to get executed atomically (at least at the top-level). For example, +refunding (and thus atomicity) can be opt-out if the relayer is using a service that avoids +including reverted transactions. + +Requirements: + +- The sum of the requests' values should be equal to the provided `msg.value`. +- All of the requests should be valid (see [`IERC7913SignatureVerifier.verify`](/contracts/5.x/api/interfaces#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-)) when `refundReceiver` is the zero address. + + +Setting a zero `refundReceiver` guarantees an all-or-nothing requests execution only for +the first-level forwarded calls. In case a forwarded request calls to a contract with another +subcall, the second-level call may revert without the top-level call reverting. + + +
+
+ + + +
+
+

_validate(struct ERC2771Forwarder.ForwardRequestData request) → bool isTrustedForwarder, bool active, bool signerMatch, address signer

+
+

internal

+# +
+
+
+ +Validates if the provided request can be executed at current block timestamp with +the given `request.signature` on behalf of `request.signer`. + +
+
+ + + +
+
+

_recoverForwardRequestSigner(struct ERC2771Forwarder.ForwardRequestData request) → bool isValid, address signer

+
+

internal

+# +
+
+
+ +Returns a tuple with the recovered the signer of an EIP712 forward request message hash +and a boolean indicating if the signature is valid. + + +The signature is considered valid if [`ECDSA.tryRecover`](/contracts/5.x/api/utils/cryptography#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) indicates no recover error for it. + + +
+
+ + + +
+
+

_execute(struct ERC2771Forwarder.ForwardRequestData request, bool requireValidRequest) → bool success

+
+

internal

+# +
+
+
+ +Validates and executes a signed request returning the request call `success` value. + +Internal function without msg.value validation. + +Requirements: + +- The caller must have provided enough gas to forward with the call. +- The request must be valid (see [`IERC7913SignatureVerifier.verify`](/contracts/5.x/api/interfaces#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-)) if the `requireValidRequest` is true. + +Emits an [`ERC2771Forwarder.ExecutedForwardRequest`](#ERC2771Forwarder-ExecutedForwardRequest-address-uint256-bool-) event. + + +Using this function doesn't check that all the `msg.value` was sent, potentially +leaving value stuck in the contract. + + +
+
+ + + +
+
+

_isTrustedByTarget(address target) → bool

+
+

internal

+# +
+
+
+ +Returns whether the target trusts this forwarder. + +This function performs a static call to the target contract calling the +[`ERC2771Context.isTrustedForwarder`](#ERC2771Context-isTrustedForwarder-address-) function. + + +Consider the execution of this forwarder is permissionless. Without this check, anyone may transfer assets +that are owned by, or are approved to this forwarder. + + +
+
+ + + +
+
+

ExecutedForwardRequest(address indexed signer, uint256 nonce, bool success)

+
+

event

+# +
+
+ +
+ +Emitted when a `ForwardRequest` is executed. + + +An unsuccessful forward request could be due to an invalid signature, an expired deadline, +or simply a revert in the requested call. The contract guarantees that the relayer is not able to force +the requested call to run out of gas. + + +
+
+ + + +
+
+

ERC2771ForwarderInvalidSigner(address signer, address from)

+
+

error

+# +
+
+
+ +The request `from` doesn't match with the recovered `signer`. + +
+
+ + + +
+
+

ERC2771ForwarderMismatchedValue(uint256 requestedValue, uint256 msgValue)

+
+

error

+# +
+
+
+ +The `requestedValue` doesn't match with the available `msgValue`. + +
+
+ + + +
+
+

ERC2771ForwarderExpiredRequest(uint48 deadline)

+
+

error

+# +
+
+
+ +The request `deadline` has expired. + +
+
+ + + +
+
+

ERC2771UntrustfulTarget(address target, address forwarder)

+
+

error

+# +
+
+
+ +The request target doesn't trust the `forwarder`. + +
+
diff --git a/docs/content/contracts/5.x/api/proxy.mdx b/docs/content/contracts/5.x/api/proxy.mdx new file mode 100644 index 00000000..bf552503 --- /dev/null +++ b/docs/content/contracts/5.x/api/proxy.mdx @@ -0,0 +1,2100 @@ +--- +title: "Proxy" +description: "Smart contract proxy utilities and implementations" +--- + +This is a low-level set of contracts implementing different proxy patterns with and without upgradeability. For an in-depth overview of this pattern check out the [Proxy Upgrade Pattern](/upgrades-plugins/proxies) page. + +Most of the proxies below are built on an abstract base contract. + +* [`Proxy`](#Proxy): Abstract contract implementing the core delegation functionality. + +In order to avoid clashes with the storage variables of the implementation contract behind a proxy, we use [ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) storage slots. + +* [`ERC1967Utils`](#ERC1967Utils): Internal functions to get and set the storage slots defined in ERC-1967. +* [`ERC1967Proxy`](#ERC1967Proxy): A proxy using ERC-1967 storage slots. Not upgradeable by default. + +There are two alternative ways to add upgradeability to an ERC-1967 proxy. Their differences are explained below in [Transparent vs UUPS Proxies](#transparent-vs-uups-proxies). + +* [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy): A proxy with a built-in immutable admin and upgrade interface. +* [`UUPSUpgradeable`](#UUPSUpgradeable): An upgradeability mechanism to be included in the implementation contract. + +**🔥 CAUTION**\ +Using upgradeable proxies correctly and securely is a difficult task that requires deep knowledge of the proxy pattern, Solidity, and the EVM. Unless you want a lot of low level control, we recommend using the [OpenZeppelin Upgrades Plugins](/upgrades-plugins) for Hardhat and Foundry. + +A different family of proxies are beacon proxies. This pattern, popularized by Dharma, allows multiple proxies to be upgraded to a different implementation in a single transaction. + +* [`BeaconProxy`](#BeaconProxy): A proxy that retrieves its implementation from a beacon contract. +* [`UpgradeableBeacon`](#UpgradeableBeacon): A beacon contract with a built in admin that can upgrade the [`BeaconProxy`](#BeaconProxy) pointing to it. + +In this pattern, the proxy contract doesn’t hold the implementation address in storage like an ERC-1967 proxy. Instead, the address is stored in a separate beacon contract. The `upgrade` operations are sent to the beacon instead of to the proxy contract, and all proxies that follow that beacon are automatically upgraded. + +Outside the realm of upgradeability, proxies can also be useful to make cheap contract clones, such as those created by an on-chain factory contract that creates many instances of the same contract. These instances are designed to be both cheap to deploy, and cheap to call. + +* [`Clones`](#Clones): A library that can deploy cheap minimal non-upgradeable proxies. + +## Transparent vs UUPS Proxies + +The original proxies included in OpenZeppelin followed the [Transparent Proxy Pattern](https://blog.openzeppelin.com/the-transparent-proxy-pattern/). While this pattern is still provided, our recommendation is now shifting towards UUPS proxies, which are both lightweight and versatile. The name UUPS comes from [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822), which first documented the pattern. + +While both of these share the same interface for upgrades, in UUPS proxies the upgrade is handled by the implementation, and can eventually be removed. Transparent proxies, on the other hand, include the upgrade and admin logic in the proxy itself. This means [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) is more expensive to deploy than what is possible with UUPS proxies. + +UUPS proxies are implemented using an [`ERC1967Proxy`](#ERC1967Proxy). Note that this proxy is not by itself upgradeable. It is the role of the implementation to include, alongside the contract’s logic, all the code necessary to update the implementation’s address that is stored at a specific slot in the proxy’s storage space. This is where the [`UUPSUpgradeable`](#UUPSUpgradeable) contract comes in. Inheriting from it (and overriding the [`_authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-) function with the relevant access control mechanism) will turn your contract into a UUPS compliant implementation. + +Note that since both proxies use the same storage slot for the implementation address, using a UUPS compliant implementation with a [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) might allow non-admins to perform upgrade operations. + +By default, the upgrade functionality included in [`UUPSUpgradeable`](#UUPSUpgradeable) contains a security mechanism that will prevent any upgrades to a non UUPS compliant implementation. This prevents upgrades to an implementation contract that wouldn’t contain the necessary upgrade mechanism, as it would lock the upgradeability of the proxy forever. This security mechanism can be bypassed by either of: + +* Adding a flag mechanism in the implementation that will disable the upgrade function when triggered. +* Upgrading to an implementation that features an upgrade mechanism without the additional security check, and then upgrading again to another implementation without the upgrade mechanism. + +The current implementation of this security mechanism uses [ERC-1822](https://eips.ethereum.org/EIPS/eip-1822) to detect the storage slot used by the implementation. A previous implementation, now deprecated, relied on a rollback check. It is possible to upgrade from a contract using the old mechanism to a new one. The inverse is however not possible, as old implementations (before version 4.5) did not include the ERC-1822 interface. + +## Core + +[`Proxy`](#Proxy) + +## ERC-1967 + +[`IERC1967`](/contracts/5.x/api/interfaces#IERC1967) + +[`ERC1967Proxy`](#ERC1967Proxy) + +[`ERC1967Utils`](#ERC1967Utils) + +## Transparent Proxy + +[`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) + +[`ProxyAdmin`](#ProxyAdmin) + +## Beacon + +[`BeaconProxy`](#BeaconProxy) + +[`IBeacon`](#IBeacon) + +[`UpgradeableBeacon`](#UpgradeableBeacon) + +## Minimal Clones + +[`Clones`](#Clones) + +## Utils + +[`Initializable`](#Initializable) + +[`UUPSUpgradeable`](#UUPSUpgradeable) + + + +
+ +## `Clones` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/Clones.sol"; +``` + +[ERC-1167](https://eips.ethereum.org/EIPS/eip-1167) is a standard for +deploying minimal proxy contracts, also known as "clones". + +> To simply and cheaply clone contract functionality in an immutable way, this standard specifies +> a minimal bytecode implementation that delegates all calls to a known, fixed address. + +The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2` +(salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the +deterministic method. + +
+

Functions

+
+- [clone(implementation)](#Clones-clone-address-) +- [clone(implementation, value)](#Clones-clone-address-uint256-) +- [cloneDeterministic(implementation, salt)](#Clones-cloneDeterministic-address-bytes32-) +- [cloneDeterministic(implementation, salt, value)](#Clones-cloneDeterministic-address-bytes32-uint256-) +- [predictDeterministicAddress(implementation, salt, deployer)](#Clones-predictDeterministicAddress-address-bytes32-address-) +- [predictDeterministicAddress(implementation, salt)](#Clones-predictDeterministicAddress-address-bytes32-) +- [cloneWithImmutableArgs(implementation, args)](#Clones-cloneWithImmutableArgs-address-bytes-) +- [cloneWithImmutableArgs(implementation, args, value)](#Clones-cloneWithImmutableArgs-address-bytes-uint256-) +- [cloneDeterministicWithImmutableArgs(implementation, args, salt)](#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-) +- [cloneDeterministicWithImmutableArgs(implementation, args, salt, value)](#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-) +- [predictDeterministicAddressWithImmutableArgs(implementation, args, salt, deployer)](#Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-address-) +- [predictDeterministicAddressWithImmutableArgs(implementation, args, salt)](#Clones-predictDeterministicAddressWithImmutableArgs-address-bytes-bytes32-) +- [fetchCloneArgs(instance)](#Clones-fetchCloneArgs-address-) +
+
+ +
+

Errors

+
+- [CloneArgumentsTooLong()](#Clones-CloneArgumentsTooLong--) +
+
+ + + +
+
+

clone(address implementation) → address instance

+
+

internal

+# +
+
+
+ +Deploys and returns the address of a clone that mimics the behavior of `implementation`. + +This function uses the create opcode, which should never revert. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + +
+
+ + + +
+
+

clone(address implementation, uint256 value) → address instance

+
+

internal

+# +
+
+
+ +Same as [clone](#Clones-clone-address-), but with a `value` parameter to send native currency +to the new contract. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + + +Using a non-zero value at creation will require the contract using this function (e.g. a factory) +to always have enough balance for new deployments. Consider exposing this function under a payable method. + + +
+
+ + + +
+
+

cloneDeterministic(address implementation, bytes32 salt) → address instance

+
+

internal

+# +
+
+
+ +Deploys and returns the address of a clone that mimics the behavior of `implementation`. + +This function uses the create2 opcode and a `salt` to deterministically deploy +the clone. Using the same `implementation` and `salt` multiple times will revert, since +the clones cannot be deployed twice at the same address. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + +
+
+ + + +
+
+

cloneDeterministic(address implementation, bytes32 salt, uint256 value) → address instance

+
+

internal

+# +
+
+
+ +Same as [cloneDeterministic](#Clones-cloneDeterministic-address-bytes32-), but with +a `value` parameter to send native currency to the new contract. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + + +Using a non-zero value at creation will require the contract using this function (e.g. a factory) +to always have enough balance for new deployments. Consider exposing this function under a payable method. + + +
+
+ + + +
+
+

predictDeterministicAddress(address implementation, bytes32 salt, address deployer) → address predicted

+
+

internal

+# +
+
+
+ +Computes the address of a clone deployed using [`Clones.cloneDeterministic`](#Clones-cloneDeterministic-address-bytes32-uint256-). + +
+
+ + + +
+
+

predictDeterministicAddress(address implementation, bytes32 salt) → address predicted

+
+

internal

+# +
+
+
+ +Computes the address of a clone deployed using [`Clones.cloneDeterministic`](#Clones-cloneDeterministic-address-bytes32-uint256-). + +
+
+ + + +
+
+

cloneWithImmutableArgs(address implementation, bytes args) → address instance

+
+

internal

+# +
+
+
+ +Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom +immutable arguments. These are provided through `args` and cannot be changed after deployment. To +access the arguments within the implementation, use [`Clones.fetchCloneArgs`](#Clones-fetchCloneArgs-address-). + +This function uses the create opcode, which should never revert. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + +
+
+ + + +
+
+

cloneWithImmutableArgs(address implementation, bytes args, uint256 value) → address instance

+
+

internal

+# +
+
+
+ +Same as [cloneWithImmutableArgs](#Clones-cloneWithImmutableArgs-address-bytes-), but with a `value` +parameter to send native currency to the new contract. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + + +Using a non-zero value at creation will require the contract using this function (e.g. a factory) +to always have enough balance for new deployments. Consider exposing this function under a payable method. + + +
+
+ + + +
+
+

cloneDeterministicWithImmutableArgs(address implementation, bytes args, bytes32 salt) → address instance

+
+

internal

+# +
+
+
+ +Deploys and returns the address of a clone that mimics the behavior of `implementation` with custom +immutable arguments. These are provided through `args` and cannot be changed after deployment. To +access the arguments within the implementation, use [`Clones.fetchCloneArgs`](#Clones-fetchCloneArgs-address-). + +This function uses the create2 opcode and a `salt` to deterministically deploy the clone. Using the same +`implementation`, `args` and `salt` multiple times will revert, since the clones cannot be deployed twice +at the same address. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + +
+
+ + + +
+
+

cloneDeterministicWithImmutableArgs(address implementation, bytes args, bytes32 salt, uint256 value) → address instance

+
+

internal

+# +
+
+
+ +Same as [cloneDeterministicWithImmutableArgs](#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-), +but with a `value` parameter to send native currency to the new contract. + + +This function does not check if `implementation` has code. A clone that points to an address +without code cannot be initialized. Initialization calls may appear to be successful when, in reality, they +have no effect and leave the clone uninitialized, allowing a third party to initialize it later. + + + +Using a non-zero value at creation will require the contract using this function (e.g. a factory) +to always have enough balance for new deployments. Consider exposing this function under a payable method. + + +
+
+ + + +
+
+

predictDeterministicAddressWithImmutableArgs(address implementation, bytes args, bytes32 salt, address deployer) → address predicted

+
+

internal

+# +
+
+
+ +Computes the address of a clone deployed using [`Clones.cloneDeterministicWithImmutableArgs`](#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-). + +
+
+ + + +
+
+

predictDeterministicAddressWithImmutableArgs(address implementation, bytes args, bytes32 salt) → address predicted

+
+

internal

+# +
+
+
+ +Computes the address of a clone deployed using [`Clones.cloneDeterministicWithImmutableArgs`](#Clones-cloneDeterministicWithImmutableArgs-address-bytes-bytes32-uint256-). + +
+
+ + + +
+
+

fetchCloneArgs(address instance) → bytes

+
+

internal

+# +
+
+
+ +Get the immutable args attached to a clone. + +- If `instance` is a clone that was deployed using `clone` or `cloneDeterministic`, this + function will return an empty array. +- If `instance` is a clone that was deployed using `cloneWithImmutableArgs` or + `cloneDeterministicWithImmutableArgs`, this function will return the args array used at + creation. +- If `instance` is NOT a clone deployed using this library, the behavior is undefined. This + function should only be used to check addresses that are known to be clones. + +
+
+ + + +
+
+

CloneArgumentsTooLong()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `ERC1967Proxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; +``` + +This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an +implementation address that can be changed. This address is stored in storage in the location specified by +[ERC-1967](https://eips.ethereum.org/EIPS/eip-1967), so that it doesn't conflict with the storage layout of the +implementation behind the proxy. + +
+

Functions

+
+- [constructor(implementation, _data)](#ERC1967Proxy-constructor-address-bytes-) +- [_implementation()](#ERC1967Proxy-_implementation--) +#### Proxy [!toc] +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +
+
+ + + +
+
+

constructor(address implementation, bytes _data)

+
+

public

+# +
+
+
+ +Initializes the upgradeable proxy with an initial implementation specified by `implementation`. + +If `_data` is nonempty, it's used as data in a delegate call to `implementation`. This will typically be an +encoded function call, and allows initializing the storage of the proxy like a Solidity constructor. + +Requirements: + +- If `data` is empty, `msg.value` must be zero. + +
+
+ + + +
+
+

_implementation() → address

+
+

internal

+# +
+
+
+ +Returns the current implementation address. + + +To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using +the [`eth_getStorageAt`](https://eth.wiki/json-rpc/API#eth_getstorageat) RPC call. +`0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` + + +
+
+ + + +
+ +## `ERC1967Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +``` + +This library provides getters and event emitting update functions for +[ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) slots. + +
+

Functions

+
+- [getImplementation()](#ERC1967Utils-getImplementation--) +- [upgradeToAndCall(newImplementation, data)](#ERC1967Utils-upgradeToAndCall-address-bytes-) +- [getAdmin()](#ERC1967Utils-getAdmin--) +- [changeAdmin(newAdmin)](#ERC1967Utils-changeAdmin-address-) +- [getBeacon()](#ERC1967Utils-getBeacon--) +- [upgradeBeaconToAndCall(newBeacon, data)](#ERC1967Utils-upgradeBeaconToAndCall-address-bytes-) +
+
+ +
+

Errors

+
+- [ERC1967InvalidImplementation(implementation)](#ERC1967Utils-ERC1967InvalidImplementation-address-) +- [ERC1967InvalidAdmin(admin)](#ERC1967Utils-ERC1967InvalidAdmin-address-) +- [ERC1967InvalidBeacon(beacon)](#ERC1967Utils-ERC1967InvalidBeacon-address-) +- [ERC1967NonPayable()](#ERC1967Utils-ERC1967NonPayable--) +
+
+ + + +
+
+

getImplementation() → address

+
+

internal

+# +
+
+
+ +Returns the current implementation address. + +
+
+ + + +
+
+

upgradeToAndCall(address newImplementation, bytes data)

+
+

internal

+# +
+
+
+ +Performs implementation upgrade with additional setup call if data is nonempty. +This function is payable only if the setup call is performed, otherwise `msg.value` is rejected +to avoid stuck value in the contract. + +Emits an [`IERC1967.Upgraded`](/contracts/5.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

getAdmin() → address

+
+

internal

+# +
+
+
+ +Returns the current admin. + + +To get this value clients can read directly from the storage slot shown below (specified by ERC-1967) using +the [`eth_getStorageAt`](https://eth.wiki/json-rpc/API#eth_getstorageat) RPC call. +`0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` + + +
+
+ + + +
+
+

changeAdmin(address newAdmin)

+
+

internal

+# +
+
+
+ +Changes the admin of the proxy. + +Emits an [`IERC1967.AdminChanged`](/contracts/5.x/api/interfaces#IERC1967-AdminChanged-address-address-) event. + +
+
+ + + +
+
+

getBeacon() → address

+
+

internal

+# +
+
+
+ +Returns the current beacon. + +
+
+ + + +
+
+

upgradeBeaconToAndCall(address newBeacon, bytes data)

+
+

internal

+# +
+
+
+ +Change the beacon and trigger a setup call if data is nonempty. +This function is payable only if the setup call is performed, otherwise `msg.value` is rejected +to avoid stuck value in the contract. + +Emits an [`IERC1967.BeaconUpgraded`](/contracts/5.x/api/interfaces#IERC1967-BeaconUpgraded-address-) event. + +CAUTION: Invoking this function has no effect on an instance of [`BeaconProxy`](#BeaconProxy) since v5, since +it uses an immutable beacon without looking at the value of the ERC-1967 beacon slot for +efficiency. + +
+
+ + + +
+
+

ERC1967InvalidImplementation(address implementation)

+
+

error

+# +
+
+
+ +The `implementation` of the proxy is invalid. + +
+
+ + + +
+
+

ERC1967InvalidAdmin(address admin)

+
+

error

+# +
+
+
+ +The `admin` of the proxy is invalid. + +
+
+ + + +
+
+

ERC1967InvalidBeacon(address beacon)

+
+

error

+# +
+
+
+ +The `beacon` of the proxy is invalid. + +
+
+ + + +
+
+

ERC1967NonPayable()

+
+

error

+# +
+
+
+ +An upgrade function sees `msg.value > 0` that may be lost. + +
+
+ + + +
+ +## `Proxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/Proxy.sol"; +``` + +This abstract contract provides a fallback function that delegates all calls to another contract using the EVM +instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to +be specified by overriding the virtual [`ERC1967Proxy._implementation`](#ERC1967Proxy-_implementation--) function. + +Additionally, delegation to the implementation can be triggered manually through the [`AccountERC7579._fallback`](/contracts/5.x/api/account#AccountERC7579-_fallback--) function, or to a +different contract through the [`Votes._delegate`](/contracts/5.x/api/governance#Votes-_delegate-address-address-) function. + +The success and return data of the delegated call will be returned back to the caller of the proxy. + +
+

Functions

+
+- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_implementation()](#Proxy-_implementation--) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +
+
+ + + +
+
+

_delegate(address implementation)

+
+

internal

+# +
+
+
+ +Delegates the current call to `implementation`. + +This function does not return to its internal call site, it will return directly to the external caller. + +
+
+ + + +
+
+

_implementation() → address

+
+

internal

+# +
+
+
+ +This is a virtual function that should be overridden so it returns the address to which the fallback +function and [`AccountERC7579._fallback`](/contracts/5.x/api/account#AccountERC7579-_fallback--) should delegate. + +
+
+ + + +
+
+

_fallback()

+
+

internal

+# +
+
+
+ +Delegates the current call to the address returned by `_implementation()`. + +This function does not return to its internal call site, it will return directly to the external caller. + +
+
+ + + +
+
+

fallback()

+
+

external

+# +
+
+
+ +Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other +function in the contract matches the call data. + +
+
+ + + +
+ +## `BeaconProxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; +``` + +This contract implements a proxy that gets the implementation address for each call from an [`UpgradeableBeacon`](#UpgradeableBeacon). + +The beacon address can only be set once during construction, and cannot be changed afterwards. It is stored in an +immutable variable to avoid unnecessary storage reads, and also in the beacon storage slot specified by +[ERC-1967](https://eips.ethereum.org/EIPS/eip-1967) so that it can be accessed externally. + +CAUTION: Since the beacon address can never be changed, you must ensure that you either control the beacon, or trust +the beacon to not upgrade the implementation maliciously. + + +Do not use the implementation logic to modify the beacon storage slot. Doing so would leave the proxy in +an inconsistent state where the beacon storage slot does not match the beacon address. + + +
+

Functions

+
+- [constructor(beacon, data)](#BeaconProxy-constructor-address-bytes-) +- [_implementation()](#BeaconProxy-_implementation--) +- [_getBeacon()](#BeaconProxy-_getBeacon--) +#### Proxy [!toc] +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [_fallback()](#Proxy-_fallback--) +- [fallback()](#Proxy-fallback--) +
+
+ + + +
+
+

constructor(address beacon, bytes data)

+
+

public

+# +
+
+
+ +Initializes the proxy with `beacon`. + +If `data` is nonempty, it's used as data in a delegate call to the implementation returned by the beacon. This +will typically be an encoded function call, and allows initializing the storage of the proxy like a Solidity +constructor. + +Requirements: + +- `beacon` must be a contract with the interface [`IBeacon`](#IBeacon). +- If `data` is empty, `msg.value` must be zero. + +
+
+ + + +
+
+

_implementation() → address

+
+

internal

+# +
+
+
+ +Returns the current implementation address of the associated beacon. + +
+
+ + + +
+
+

_getBeacon() → address

+
+

internal

+# +
+
+
+ +Returns the beacon. + +
+
+ + + +
+ +## `IBeacon` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; +``` + +This is the interface that [`BeaconProxy`](#BeaconProxy) expects of its beacon. + +
+

Functions

+
+- [implementation()](#IBeacon-implementation--) +
+
+ + + +
+
+

implementation() → address

+
+

external

+# +
+
+
+ +Must return an address that can be used as a delegate call target. + +[`UpgradeableBeacon`](#UpgradeableBeacon) will check that this address is a contract. + +
+
+ + + +
+ +## `UpgradeableBeacon` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +``` + +This contract is used in conjunction with one or more instances of [`BeaconProxy`](#BeaconProxy) to determine their +implementation contract, which is where they will delegate all function calls. + +An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon. + +
+

Functions

+
+- [constructor(implementation_, initialOwner)](#UpgradeableBeacon-constructor-address-address-) +- [implementation()](#UpgradeableBeacon-implementation--) +- [upgradeTo(newImplementation)](#UpgradeableBeacon-upgradeTo-address-) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +#### IBeacon [!toc] +
+
+ +
+

Events

+
+- [Upgraded(implementation)](#UpgradeableBeacon-Upgraded-address-) +#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +#### IBeacon [!toc] +
+
+ +
+

Errors

+
+- [BeaconInvalidImplementation(implementation)](#UpgradeableBeacon-BeaconInvalidImplementation-address-) +#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +#### IBeacon [!toc] +
+
+ + + +
+
+

constructor(address implementation_, address initialOwner)

+
+

public

+# +
+
+
+ +Sets the address of the initial implementation, and the initial owner who can upgrade the beacon. + +
+
+ + + +
+
+

implementation() → address

+
+

public

+# +
+
+
+ +Returns the current implementation address. + +
+
+ + + +
+
+

upgradeTo(address newImplementation)

+
+

public

+# +
+
+
+ +Upgrades the beacon to a new implementation. + +Emits an [`IERC1967.Upgraded`](/contracts/5.x/api/interfaces#IERC1967-Upgraded-address-) event. + +Requirements: + +- msg.sender must be the owner of the contract. +- `newImplementation` must be a contract. + +
+
+ + + +
+
+

Upgraded(address indexed implementation)

+
+

event

+# +
+
+ +
+ +Emitted when the implementation returned by the beacon is changed. + +
+
+ + + +
+
+

BeaconInvalidImplementation(address implementation)

+
+

error

+# +
+
+
+ +The `implementation` of the beacon is invalid. + +
+
+ + + +
+ +## `ProxyAdmin` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +``` + +This is an auxiliary contract meant to be assigned as the admin of a [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy). For an +explanation of why you would want to use this see the documentation for [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy). + +
+

Functions

+
+- [constructor(initialOwner)](#ProxyAdmin-constructor-address-) +- [upgradeAndCall(proxy, implementation, data)](#ProxyAdmin-upgradeAndCall-contract-ITransparentUpgradeableProxy-address-bytes-) +- [UPGRADE_INTERFACE_VERSION()](#ProxyAdmin-UPGRADE_INTERFACE_VERSION-string) +#### Ownable [!toc] +- [owner()](#Ownable-owner--) +- [_checkOwner()](#Ownable-_checkOwner--) +- [renounceOwnership()](#Ownable-renounceOwnership--) +- [transferOwnership(newOwner)](#Ownable-transferOwnership-address-) +- [_transferOwnership(newOwner)](#Ownable-_transferOwnership-address-) +
+
+ +
+

Events

+
+#### Ownable [!toc] +- [OwnershipTransferred(previousOwner, newOwner)](#Ownable-OwnershipTransferred-address-address-) +
+
+ +
+

Errors

+
+#### Ownable [!toc] +- [OwnableUnauthorizedAccount(account)](#Ownable-OwnableUnauthorizedAccount-address-) +- [OwnableInvalidOwner(owner)](#Ownable-OwnableInvalidOwner-address-) +
+
+ + + +
+
+

constructor(address initialOwner)

+
+

public

+# +
+
+
+ +Sets the initial owner who can perform upgrades. + +
+
+ + + +
+
+

upgradeAndCall(contract ITransparentUpgradeableProxy proxy, address implementation, bytes data)

+
+

public

+# +
+
+
+ +Upgrades `proxy` to `implementation` and calls a function on the new implementation. +See [`TransparentUpgradeableProxy._dispatchUpgradeToAndCall`](#TransparentUpgradeableProxy-_dispatchUpgradeToAndCall--). + +Requirements: + +- This contract must be the admin of `proxy`. +- If `data` is empty, `msg.value` must be zero. + +
+
+ + + +
+
+

UPGRADE_INTERFACE_VERSION() → string

+
+

public

+# +
+
+
+ +The version of the upgrade interface of the contract. If this getter is missing, both `upgrade(address,address)` +and `upgradeAndCall(address,address,bytes)` are present, and `upgrade` must be used if no function should be called, +while `upgradeAndCall` will invoke the `receive` function if the third argument is the empty byte string. +If the getter returns `"5.0.0"`, only `upgradeAndCall(address,address,bytes)` is present, and the third argument must +be the empty byte string if no function should be called, making it impossible to invoke the `receive` function +during an upgrade. + +
+
+ + + +
+ +## `ITransparentUpgradeableProxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +``` + +Interface for [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy). In order to implement transparency, [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) +does not implement this interface directly, and its upgradeability mechanism is implemented by an internal dispatch +mechanism. The compiler is unaware that these functions are implemented by [`TransparentUpgradeableProxy`](#TransparentUpgradeableProxy) and will not +include them in the ABI so this interface must be used to interact with it. + +
+

Functions

+
+- [upgradeToAndCall(newImplementation, data)](#ITransparentUpgradeableProxy-upgradeToAndCall-address-bytes-) +#### IERC1967 [!toc] +
+
+ +
+

Events

+
+#### IERC1967 [!toc] +- [Upgraded(implementation)](#IERC1967-Upgraded-address-) +- [AdminChanged(previousAdmin, newAdmin)](#IERC1967-AdminChanged-address-address-) +- [BeaconUpgraded(beacon)](#IERC1967-BeaconUpgraded-address-) +
+
+ + + +
+
+

upgradeToAndCall(address newImplementation, bytes data)

+
+

external

+# +
+
+
+ +See [`UUPSUpgradeable.upgradeToAndCall`](#UUPSUpgradeable-upgradeToAndCall-address-bytes-) + +
+
+ + + +
+ +## `TransparentUpgradeableProxy` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +``` + +This contract implements a proxy that is upgradeable through an associated [`ProxyAdmin`](#ProxyAdmin) instance. + +To avoid [proxy selector +clashing](https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357), which can potentially be used in an attack, this contract uses the +[transparent proxy pattern](https://blog.openzeppelin.com/the-transparent-proxy-pattern/). This pattern implies two +things that go hand in hand: + +1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if +that call matches the [`ITransparentUpgradeableProxy.upgradeToAndCall`](#ITransparentUpgradeableProxy-upgradeToAndCall-address-bytes-) function exposed by the proxy itself. +2. If the admin calls the proxy, it can call the `upgradeToAndCall` function, but any other call won't be forwarded to +the implementation. If the admin tries to call a function on the implementation it will fail with an error indicating +the proxy admin cannot fallback to the target implementation. + +These properties mean that the admin account can only be used for upgrading the proxy, so it's best if it's a +dedicated account that is not used for anything else. This will avoid headaches due to sudden errors when trying to +call a function from the proxy implementation. For this reason, the proxy deploys an instance of [`ProxyAdmin`](#ProxyAdmin) and +allows upgrades only if they come through it. You should think of the `ProxyAdmin` instance as the administrative +interface of the proxy, including the ability to change who can trigger upgrades by transferring ownership. + + +The real interface of this proxy is that defined in `ITransparentUpgradeableProxy`. This contract does not +inherit from that interface, and instead `upgradeToAndCall` is implicitly implemented using a custom dispatch +mechanism in `_fallback`. Consequently, the compiler will not produce an ABI for this contract. This is necessary to +fully implement transparency without decoding reverts caused by selector clashes between the proxy and the +implementation. + + + +This proxy does not inherit from [`Context`](/contracts/5.x/api/utils#Context) deliberately. The [`ProxyAdmin`](#ProxyAdmin) of this contract won't send a +meta-transaction in any way, and any other meta-transaction setup should be made in the implementation contract. + + + +This contract avoids unnecessary storage reads by setting the admin only during construction as an +immutable variable, preventing any changes thereafter. However, the admin slot defined in ERC-1967 can still be +overwritten by the implementation logic pointed to by this proxy. In such cases, the contract may end up in an +undesirable state where the admin slot is different from the actual admin. Relying on the value of the admin slot +is generally fine if the implementation is trusted. + + + +It is not recommended to extend this contract to add additional external functions. If you do so, the +compiler will not check that there are no selector conflicts, due to the note above. A selector clash between any new +function and the functions declared in [`ITransparentUpgradeableProxy`](#ITransparentUpgradeableProxy) will be resolved in favor of the new one. This +could render the `upgradeToAndCall` function inaccessible, preventing upgradeability and compromising transparency. + + +
+

Functions

+
+- [constructor(_logic, initialOwner, _data)](#TransparentUpgradeableProxy-constructor-address-address-bytes-) +- [_proxyAdmin()](#TransparentUpgradeableProxy-_proxyAdmin--) +- [_fallback()](#TransparentUpgradeableProxy-_fallback--) +#### ERC1967Proxy [!toc] +- [_implementation()](#ERC1967Proxy-_implementation--) +#### Proxy [!toc] +- [_delegate(implementation)](#Proxy-_delegate-address-) +- [fallback()](#Proxy-fallback--) +
+
+ +
+

Errors

+
+- [ProxyDeniedAdminAccess()](#TransparentUpgradeableProxy-ProxyDeniedAdminAccess--) +#### ERC1967Proxy [!toc] +#### Proxy [!toc] +
+
+ + + +
+
+

constructor(address _logic, address initialOwner, bytes _data)

+
+

public

+# +
+
+
+ +Initializes an upgradeable proxy managed by an instance of a [`ProxyAdmin`](#ProxyAdmin) with an `initialOwner`, +backed by the implementation at `_logic`, and optionally initialized with `_data` as explained in +[`ERC1967Proxy.constructor`](#ERC1967Proxy-constructor-address-bytes-). + +
+
+ + + +
+
+

_proxyAdmin() → address

+
+

internal

+# +
+
+
+ +Returns the admin of this proxy. + +
+
+ + + +
+
+

_fallback()

+
+

internal

+# +
+
+
+ +If caller is the admin process the call internally, otherwise transparently fallback to the proxy behavior. + +
+
+ + + +
+
+

ProxyDeniedAdminAccess()

+
+

error

+# +
+
+
+ +The proxy caller is the current admin, and can't fallback to the proxy target. + +
+
+ + + +
+ +## `Initializable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +``` + +This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed +behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an +external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer +function so it can only be called once. The [`Initializable.initializer`](#Initializable-initializer--) modifier provided by this contract will have this effect. + +The initialization functions use a version number. Once a version number is used, it is consumed and cannot be +reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in +case an upgrade adds a module that needs to be initialized. + +For example: + +[.hljs-theme-light.nopadding] +```solidity +contract MyToken is ERC20Upgradeable { + function initialize() initializer public { + __ERC20_init("MyToken", "MTK"); + } +} + +contract MyTokenV2 is MyToken, ERC20PermitUpgradeable { + function initializeV2() reinitializer(2) public { + __ERC20Permit_init("MyToken"); + } +} +``` + + +To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as +possible by providing the encoded function call as the `_data` argument to [`ERC1967Proxy.constructor`](#ERC1967Proxy-constructor-address-bytes-). + + +CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure +that all initializers are idempotent. This is not verified automatically as constructors are by Solidity. + + +Avoid leaving a contract uninitialized. + +An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation +contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke +the [`Initializable._disableInitializers`](#Initializable-_disableInitializers--) function in the constructor to automatically lock it when it is deployed: + +[.hljs-theme-light.nopadding] +``` +/// @custom:oz-upgrades-unsafe-allow constructor +constructor() { + _disableInitializers(); +} +``` + + +
+

Modifiers

+
+- [initializer()](#Initializable-initializer--) +- [reinitializer(version)](#Initializable-reinitializer-uint64-) +- [onlyInitializing()](#Initializable-onlyInitializing--) +
+
+ +
+

Functions

+
+- [_checkInitializing()](#Initializable-_checkInitializing--) +- [_disableInitializers()](#Initializable-_disableInitializers--) +- [_getInitializedVersion()](#Initializable-_getInitializedVersion--) +- [_isInitializing()](#Initializable-_isInitializing--) +- [_initializableStorageSlot()](#Initializable-_initializableStorageSlot--) +
+
+ +
+

Events

+
+- [Initialized(version)](#Initializable-Initialized-uint64-) +
+
+ +
+

Errors

+
+- [InvalidInitialization()](#Initializable-InvalidInitialization--) +- [NotInitializing()](#Initializable-NotInitializing--) +
+
+ + + +
+
+

initializer()

+
+

internal

+# +
+
+ +
+ +A modifier that defines a protected initializer function that can be invoked at most once. In its scope, +`onlyInitializing` functions can be used to initialize parent contracts. + +Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any +number of times. This behavior in the constructor can be useful during testing and is not expected to be used in +production. + +Emits an [`Initializable.Initialized`](#Initializable-Initialized-uint64-) event. + +
+
+ + + +
+
+

reinitializer(uint64 version)

+
+

internal

+# +
+
+ +
+ +A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the +contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be +used to initialize parent contracts. + +A reinitializer may be used after the original initialization step. This is essential to configure modules that +are added through upgrades and that require initialization. + +When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer` +cannot be nested. If one is invoked in the context of another, execution will revert. + +Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in +a contract, executing them in the right order is up to the developer or operator. + + +Setting the version to 2**64 - 1 will prevent any future reinitialization. + + +Emits an [`Initializable.Initialized`](#Initializable-Initialized-uint64-) event. + +
+
+ + + +
+
+

onlyInitializing()

+
+

internal

+# +
+
+ +
+ +Modifier to protect an initialization function so that it can only be invoked by functions with the +[`Initializable.initializer`](#Initializable-initializer--) and [`Initializable.reinitializer`](#Initializable-reinitializer-uint64-) modifiers, directly or indirectly. + +
+
+ + + +
+
+

_checkInitializing()

+
+

internal

+# +
+
+
+ +Reverts if the contract is not in an initializing state. See [`Initializable.onlyInitializing`](#Initializable-onlyInitializing--). + +
+
+ + + +
+
+

_disableInitializers()

+
+

internal

+# +
+
+
+ +Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call. +Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized +to any version. It is recommended to use this to lock implementation contracts that are designed to be called +through proxies. + +Emits an [`Initializable.Initialized`](#Initializable-Initialized-uint64-) event the first time it is successfully executed. + +
+
+ + + +
+
+

_getInitializedVersion() → uint64

+
+

internal

+# +
+
+
+ +Returns the highest version that has been initialized. See [`Initializable.reinitializer`](#Initializable-reinitializer-uint64-). + +
+
+ + + +
+
+

_isInitializing() → bool

+
+

internal

+# +
+
+
+ +Returns `true` if the contract is currently initializing. See [`Initializable.onlyInitializing`](#Initializable-onlyInitializing--). + +
+
+ + + +
+
+

_initializableStorageSlot() → bytes32

+
+

internal

+# +
+
+
+ +Pointer to storage slot. Allows integrators to override it with a custom storage location. + + +Consider following the ERC-7201 formula to derive storage locations. + + +
+
+ + + +
+
+

Initialized(uint64 version)

+
+

event

+# +
+
+ +
+ +Triggered when the contract has been initialized or reinitialized. + +
+
+ + + +
+
+

InvalidInitialization()

+
+

error

+# +
+
+
+ +The contract is already initialized. + +
+
+ + + +
+
+

NotInitializing()

+
+

error

+# +
+
+
+ +The contract is not initializing. + +
+
+ + + +
+ +## `UUPSUpgradeable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +``` + +An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an +[`ERC1967Proxy`](#ERC1967Proxy), when this contract is set as the implementation behind such a proxy. + +A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is +reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing +`UUPSUpgradeable` with a custom implementation of upgrades. + +The [`UUPSUpgradeable._authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-) function must be overridden to include access restriction to the upgrade mechanism. + +@custom:stateless + +
+

Modifiers

+
+- [onlyProxy()](#UUPSUpgradeable-onlyProxy--) +- [notDelegated()](#UUPSUpgradeable-notDelegated--) +
+
+ +
+

Functions

+
+- [proxiableUUID()](#UUPSUpgradeable-proxiableUUID--) +- [upgradeToAndCall(newImplementation, data)](#UUPSUpgradeable-upgradeToAndCall-address-bytes-) +- [_checkProxy()](#UUPSUpgradeable-_checkProxy--) +- [_checkNotDelegated()](#UUPSUpgradeable-_checkNotDelegated--) +- [_authorizeUpgrade(newImplementation)](#UUPSUpgradeable-_authorizeUpgrade-address-) +- [UPGRADE_INTERFACE_VERSION()](#UUPSUpgradeable-UPGRADE_INTERFACE_VERSION-string) +#### IERC1822Proxiable [!toc] +
+
+ +
+

Errors

+
+- [UUPSUnauthorizedCallContext()](#UUPSUpgradeable-UUPSUnauthorizedCallContext--) +- [UUPSUnsupportedProxiableUUID(slot)](#UUPSUpgradeable-UUPSUnsupportedProxiableUUID-bytes32-) +#### IERC1822Proxiable [!toc] +
+
+ + + +
+
+

onlyProxy()

+
+

internal

+# +
+
+ +
+ +Check that the execution is being performed through a delegatecall call and that the execution context is +a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case +for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a +function through ERC-1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to +fail. + +
+
+ + + +
+
+

notDelegated()

+
+

internal

+# +
+
+ +
+ +Check that the execution is not being performed through a delegate call. This allows a function to be +callable on the implementing contract but not through proxies. + +
+
+ + + +
+
+

proxiableUUID() → bytes32

+
+

external

+# +
+
+
+ +Implementation of the ERC-1822 [`IERC1822Proxiable.proxiableUUID`](/contracts/5.x/api/interfaces#IERC1822Proxiable-proxiableUUID--) function. This returns the storage slot used by the +implementation. It is used to validate the implementation's compatibility when performing an upgrade. + + +A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks +bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this +function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. + + +
+
+ + + +
+
+

upgradeToAndCall(address newImplementation, bytes data)

+
+

public

+# +
+
+
+ +Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call +encoded in `data`. + +Calls [`UUPSUpgradeable._authorizeUpgrade`](#UUPSUpgradeable-_authorizeUpgrade-address-). + +Emits an [`IERC1967.Upgraded`](/contracts/5.x/api/interfaces#IERC1967-Upgraded-address-) event. + +
+
+ + + +
+
+

_checkProxy()

+
+

internal

+# +
+
+
+ +Reverts if the execution is not performed via delegatecall or the execution +context is not of a proxy with an ERC-1967 compliant implementation pointing to self. + +
+
+ + + +
+
+

_checkNotDelegated()

+
+

internal

+# +
+
+
+ +Reverts if the execution is performed via delegatecall. +See [`UUPSUpgradeable.notDelegated`](#UUPSUpgradeable-notDelegated--). + +
+
+ + + +
+
+

_authorizeUpgrade(address newImplementation)

+
+

internal

+# +
+
+
+ +Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by +[`ERC1967Utils.upgradeToAndCall`](#ERC1967Utils-upgradeToAndCall-address-bytes-). + +Normally, this function will use an [access control](/contracts/5.x/api/access) modifier such as [`Ownable.onlyOwner`](/contracts/5.x/api/access#Ownable-onlyOwner--). + +```solidity +function _authorizeUpgrade(address) internal onlyOwner {} +``` + +
+
+ + + +
+
+

UPGRADE_INTERFACE_VERSION() → string

+
+

public

+# +
+
+
+ +The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)` +and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called, +while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string. +If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must +be the empty byte string if no function should be called, making it impossible to invoke the `receive` function +during an upgrade. + +
+
+ + + +
+
+

UUPSUnauthorizedCallContext()

+
+

error

+# +
+
+
+ +The call is from an unauthorized context. + +
+
+ + + +
+
+

UUPSUnsupportedProxiableUUID(bytes32 slot)

+
+

error

+# +
+
+
+ +The storage `slot` is unsupported as a UUID. + +
+
diff --git a/docs/content/contracts/5.x/api/token/ERC1155.mdx b/docs/content/contracts/5.x/api/token/ERC1155.mdx new file mode 100644 index 00000000..25b140de --- /dev/null +++ b/docs/content/contracts/5.x/api/token/ERC1155.mdx @@ -0,0 +1,1747 @@ +--- +title: "ERC1155" +description: "Smart contract ERC1155 utilities and implementations" +--- + +This set of interfaces and contracts are all related to the [ERC-1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155). + +The ERC consists of three interfaces which fulfill different roles, found here as [`IERC1155`](#IERC1155), [`IERC1155MetadataURI`](#IERC1155MetadataURI) and [`IERC1155Receiver`](#IERC1155Receiver). + +[`ERC1155`](#ERC1155) implements the mandatory [`IERC1155`](#IERC1155) interface, as well as the optional extension [`IERC1155MetadataURI`](#IERC1155MetadataURI), by relying on the substitution mechanism to use the same URI for all token types, dramatically reducing gas costs. + +Additionally there are multiple custom extensions, including: + +* designation of addresses that can pause token transfers for all users ([`ERC1155Pausable`](#ERC1155Pausable)). +* destruction of own tokens ([`ERC1155Burnable`](#ERC1155Burnable)). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC-1155 (such as [`_mint`](#ERC1155-_mint-address-uint256-uint256-bytes-)) and expose them as external functions in the way they prefer. + + +## Core + +[`IERC1155`](#IERC1155) + +[`IERC1155MetadataURI`](#IERC1155MetadataURI) + +[`ERC1155`](#ERC1155) + +[`IERC1155Receiver`](#IERC1155Receiver) + +## Extensions + +[`ERC1155Pausable`](#ERC1155Pausable) + +[`ERC1155Burnable`](#ERC1155Burnable) + +[`ERC1155Supply`](#ERC1155Supply) + +[`ERC1155URIStorage`](#ERC1155URIStorage) + +## Utilities + +[`ERC1155Holder`](#ERC1155Holder) + +[`ERC1155Utils`](#ERC1155Utils) + + + +
+ +## `ERC1155` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; +``` + +Implementation of the basic standard multi-token. +See https://eips.ethereum.org/EIPS/eip-1155 +Originally based on code by Enjin: https://github.com/enjin/erc-1155 + +
+

Functions

+
+- [constructor(uri_)](#ERC1155-constructor-string-) +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_update(from, to, ids, values)](#ERC1155-_update-address-address-uint256---uint256---) +- [_updateWithAcceptanceCheck(from, to, ids, values, data)](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, value, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, value, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, values, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, value)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, values)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### IERC1155Errors [!toc] +- [ERC1155InsufficientBalance(sender, balance, needed, tokenId)](#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC1155InvalidSender(sender)](#IERC1155Errors-ERC1155InvalidSender-address-) +- [ERC1155InvalidReceiver(receiver)](#IERC1155Errors-ERC1155InvalidReceiver-address-) +- [ERC1155MissingApprovalForAll(operator, owner)](#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-) +- [ERC1155InvalidApprover(approver)](#IERC1155Errors-ERC1155InvalidApprover-address-) +- [ERC1155InvalidOperator(operator)](#IERC1155Errors-ERC1155InvalidOperator-address-) +- [ERC1155InvalidArrayLength(idsLength, valuesLength)](#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-) +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(string uri_)

+
+

internal

+# +
+
+
+ +See [`ERC1155._setURI`](#ERC1155-_setURI-string-). + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

uri(uint256) → string

+
+

public

+# +
+
+
+ +See [`IERC1155MetadataURI.uri`](#IERC1155MetadataURI-uri-uint256-). + +This implementation returns the same URI for *all* token types. It relies +on the token type ID substitution mechanism +[defined in the ERC](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +Clients calling this function must replace the `\{id\}` substring with the +actual token type ID. + +
+
+ + + +
+
+

balanceOf(address account, uint256 id) → uint256

+
+

public

+# +
+
+
+ +Returns the value of tokens of token type `id` owned by `account`. + +
+
+ + + +
+
+

balanceOfBatch(address[] accounts, uint256[] ids) → uint256[]

+
+

public

+# +
+
+
+ +See [`IERC1155.balanceOfBatch`](#IERC1155-balanceOfBatch-address---uint256---). + +Requirements: + +- `accounts` and `ids` must have the same length. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

public

+# +
+
+
+ +Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + +Emits an [`IERC1155.ApprovalForAll`](#IERC1155-ApprovalForAll-address-address-bool-) event. + +Requirements: + +- `operator` cannot be the zero address. + +
+
+ + + +
+
+

isApprovedForAll(address account, address operator) → bool

+
+

public

+# +
+
+
+ +Returns true if `operator` is approved to transfer ``account``'s tokens. + +See [`ERC1155.setApprovalForAll`](#ERC1155-setApprovalForAll-address-bool-). + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes data)

+
+

public

+# +
+
+
+ +Transfers a `value` amount of tokens of type `id` from `from` to `to`. + + +This function can potentially allow a reentrancy attack when transferring tokens +to an untrusted contract, when invoking [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) on the receiver. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- If the caller is not `from`, it must have been approved to spend ``from``'s tokens via [`ERC1155.setApprovalForAll`](#ERC1155-setApprovalForAll-address-bool-). +- `from` must have a balance of tokens of type `id` of at least `value` amount. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] values, bytes data)

+
+

public

+# +
+
+
+ +[Batched](/contracts/5.x/erc1155) version of [`ERC1155.safeTransferFrom`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-). + + +This function can potentially allow a reentrancy attack when transferring tokens +to an untrusted contract, when invoking [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) on the receiver. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + + +Emits either a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) or a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event, depending on the length of the array arguments. + +Requirements: + +- `ids` and `values` must have the same length. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_update(address from, address to, uint256[] ids, uint256[] values)

+
+

internal

+# +
+
+
+ +Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` +(or `to`) is the zero address. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event if the arrays contain one element, and [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) otherwise. + +Requirements: + +- If `to` refers to a smart contract, it must implement either [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) + or [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the acceptance magic value. +- `ids` and `values` must have the same length. + + +The ERC-1155 acceptance check is not performed in this function. See [`ERC1155._updateWithAcceptanceCheck`](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) instead. + + +
+
+ + + +
+
+

_updateWithAcceptanceCheck(address from, address to, uint256[] ids, uint256[] values, bytes data)

+
+

internal

+# +
+
+
+ +Version of [`ERC1155._update`](#ERC1155-_update-address-address-uint256---uint256---) that performs the token acceptance check by calling +[`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) or [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) on the receiver address if it +contains code (eg. is a smart contract at the moment of execution). + + +Overriding this function is discouraged because it poses a reentrancy risk from the receiver. So any +update to the contract state after this function would break the check-effect-interaction pattern. Consider +overriding [`ERC1155._update`](#ERC1155-_update-address-address-uint256---uint256---) instead. + + +
+
+ + + +
+
+

_safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Transfers a `value` tokens of token type `id` from `from` to `to`. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- `from` must have a balance of tokens of type `id` of at least `value` amount. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] values, bytes data)

+
+

internal

+# +
+
+
+ +[Batched](/contracts/5.x/erc1155) version of [`ERC1155._safeTransferFrom`](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. +- `ids` and `values` must have the same length. + +
+
+ + + +
+
+

_setURI(string newuri)

+
+

internal

+# +
+
+
+ +Sets a new URI for all token types, by relying on the token type ID +substitution mechanism +[defined in the ERC](https://eips.ethereum.org/EIPS/eip-1155#metadata). + +By this mechanism, any occurrence of the `\{id\}` substring in either the +URI or any of the values in the JSON file at said URI will be replaced by +clients with the token type ID. + +For example, the `https://token-cdn-domain/\{id\}.json` URI would be +interpreted by clients as +`https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` +for token type ID 0x4cce0. + +See [`ERC1155.uri`](#ERC1155-uri-uint256-). + +Because these URIs cannot be meaningfully represented by the [`IERC1155.URI`](#IERC1155-URI-string-uint256-) event, +this function emits no events. + +
+
+ + + +
+
+

_mint(address to, uint256 id, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Creates a `value` amount of tokens of type `id`, and assigns them to `to`. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_mintBatch(address to, uint256[] ids, uint256[] values, bytes data)

+
+

internal

+# +
+
+
+ +[Batched](/contracts/5.x/erc1155) version of [`ERC1155._mint`](#ERC1155-_mint-address-uint256-uint256-bytes-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- `ids` and `values` must have the same length. +- `to` cannot be the zero address. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

_burn(address from, uint256 id, uint256 value)

+
+

internal

+# +
+
+
+ +Destroys a `value` amount of tokens of type `id` from `from` + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `id`. + +
+
+ + + +
+
+

_burnBatch(address from, uint256[] ids, uint256[] values)

+
+

internal

+# +
+
+
+ +[Batched](/contracts/5.x/erc1155) version of [`ERC1155._burn`](#ERC1155-_burn-address-uint256-uint256-). + +Emits a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event. + +Requirements: + +- `from` cannot be the zero address. +- `from` must have at least `value` amount of tokens of type `id`. +- `ids` and `values` must have the same length. + +
+
+ + + +
+
+

_setApprovalForAll(address owner, address operator, bool approved)

+
+

internal

+# +
+
+
+ +Approve `operator` to operate on all of `owner` tokens + +Emits an [`IERC1155.ApprovalForAll`](#IERC1155-ApprovalForAll-address-address-bool-) event. + +Requirements: + +- `operator` cannot be the zero address. + +
+
+ + + +
+ +## `IERC1155` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +``` + +Required interface of an ERC-1155 compliant contract, as defined in the +[ERC](https://eips.ethereum.org/EIPS/eip-1155). + +
+

Functions

+
+- [balanceOf(account, id)](#IERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#IERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#IERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#IERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

balanceOf(address account, uint256 id) → uint256

+
+

external

+# +
+
+
+ +Returns the value of tokens of token type `id` owned by `account`. + +
+
+ + + +
+
+

balanceOfBatch(address[] accounts, uint256[] ids) → uint256[]

+
+

external

+# +
+
+
+ +[Batched](/contracts/5.x/erc1155) version of [`IERC6909.balanceOf`](../interfaces#IERC6909-balanceOf-address-uint256-). + +Requirements: + +- `accounts` and `ids` must have the same length. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

external

+# +
+
+
+ +Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`, + +Emits an [`IERC1155.ApprovalForAll`](#IERC1155-ApprovalForAll-address-address-bool-) event. + +Requirements: + +- `operator` cannot be the zero address. + +
+
+ + + +
+
+

isApprovedForAll(address account, address operator) → bool

+
+

external

+# +
+
+
+ +Returns true if `operator` is approved to transfer ``account``'s tokens. + +See [`ERC1155.setApprovalForAll`](#ERC1155-setApprovalForAll-address-bool-). + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes data)

+
+

external

+# +
+
+
+ +Transfers a `value` amount of tokens of type `id` from `from` to `to`. + + +This function can potentially allow a reentrancy attack when transferring tokens +to an untrusted contract, when invoking [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) on the receiver. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event. + +Requirements: + +- `to` cannot be the zero address. +- If the caller is not `from`, it must have been approved to spend ``from``'s tokens via [`ERC1155.setApprovalForAll`](#ERC1155-setApprovalForAll-address-bool-). +- `from` must have a balance of tokens of type `id` of at least `value` amount. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

safeBatchTransferFrom(address from, address to, uint256[] ids, uint256[] values, bytes data)

+
+

external

+# +
+
+
+ +[Batched](/contracts/5.x/erc1155) version of [`ERC1155.safeTransferFrom`](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-). + + +This function can potentially allow a reentrancy attack when transferring tokens +to an untrusted contract, when invoking [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) on the receiver. +Ensure to follow the checks-effects-interactions pattern and consider employing +reentrancy guards when interacting with untrusted contracts. + + +Emits either a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) or a [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) event, depending on the length of the array arguments. + +Requirements: + +- `ids` and `values` must have the same length. +- If `to` refers to a smart contract, it must implement [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the +acceptance magic value. + +
+
+ + + +
+
+

TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value)

+
+

event

+# +
+
+ +
+ +Emitted when `value` amount of tokens of type `id` are transferred from `from` to `to` by `operator`. + +
+
+ + +
+
+

TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values)

+
+

event

+# +
+
+ +
+ +Equivalent to multiple [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) events, where `operator`, `from` and `to` are the same for all +transfers. + +
+
+ + +
+
+

ApprovalForAll(address indexed account, address indexed operator, bool approved)

+
+

event

+# +
+
+ +
+ +Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to +`approved`. + +
+
+ + +
+
+

URI(string value, uint256 indexed id)

+
+

event

+# +
+
+ +
+ +Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI. + +If an [`IERC1155.URI`](#IERC1155-URI-string-uint256-) event was emitted for `id`, the standard +[guarantees](https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions) that `value` will equal the value +returned by [`IERC1155MetadataURI.uri`](#IERC1155MetadataURI-uri-uint256-). + +
+
+ + + +
+ +## `IERC1155Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +``` + +Interface that must be implemented by smart contracts in order to receive +ERC-1155 token transfers. + +
+

Functions

+
+- [onERC1155Received(operator, from, id, value, data)](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(operator, from, ids, values, data)](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ + + +
+
+

onERC1155Received(address operator, address from, uint256 id, uint256 value, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Handles the receipt of a single ERC-1155 token type. This function is +called at the end of a `safeTransferFrom` after the balance has been updated. + + +To accept the transfer, this must return +`bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` +(i.e. 0xf23a6e61, or its own function selector). + + +
+
+ + + +
+
+

onERC1155BatchReceived(address operator, address from, uint256[] ids, uint256[] values, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Handles the receipt of a multiple ERC-1155 token types. This function +is called at the end of a `safeBatchTransferFrom` after the balances have +been updated. + + +To accept the transfer(s), this must return +`bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` +(i.e. 0xbc197c81, or its own function selector). + + +
+
+ + + +
+ +## `ERC1155Burnable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol"; +``` + +Extension of [`ERC1155`](#ERC1155) that allows token holders to destroy both their +own tokens and those that they have been approved to use. + +
+

Functions

+
+- [burn(account, id, value)](#ERC1155Burnable-burn-address-uint256-uint256-) +- [burnBatch(account, ids, values)](#ERC1155Burnable-burnBatch-address-uint256---uint256---) +#### ERC1155 [!toc] +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_update(from, to, ids, values)](#ERC1155-_update-address-address-uint256---uint256---) +- [_updateWithAcceptanceCheck(from, to, ids, values, data)](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, value, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, value, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, values, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, value)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, values)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +- [ERC1155InsufficientBalance(sender, balance, needed, tokenId)](#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC1155InvalidSender(sender)](#IERC1155Errors-ERC1155InvalidSender-address-) +- [ERC1155InvalidReceiver(receiver)](#IERC1155Errors-ERC1155InvalidReceiver-address-) +- [ERC1155MissingApprovalForAll(operator, owner)](#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-) +- [ERC1155InvalidApprover(approver)](#IERC1155Errors-ERC1155InvalidApprover-address-) +- [ERC1155InvalidOperator(operator)](#IERC1155Errors-ERC1155InvalidOperator-address-) +- [ERC1155InvalidArrayLength(idsLength, valuesLength)](#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-) +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

burn(address account, uint256 id, uint256 value)

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

burnBatch(address account, uint256[] ids, uint256[] values)

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC1155Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Pausable.sol"; +``` + +ERC-1155 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + + +This contract does not include public pause and unpause functions. In +addition to inheriting this contract, you must define both functions, invoking the +[`Pausable._pause`](../utils#Pausable-_pause--) and [`Pausable._unpause`](../utils#Pausable-_unpause--) internal functions, with appropriate +access control, e.g. using [`AccessControl`](../access#AccessControl) or [`Ownable`](../access#Ownable). Not doing so will +make the contract pause mechanism of the contract unreachable, and thus unusable. + + +
+

Functions

+
+- [_update(from, to, ids, values)](#ERC1155Pausable-_update-address-address-uint256---uint256---) +#### Pausable [!toc] +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC1155 [!toc] +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_updateWithAcceptanceCheck(from, to, ids, values, data)](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, value, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, value, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, values, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, value)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, values)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Pausable [!toc] +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Pausable [!toc] +- [EnforcedPause()](#Pausable-EnforcedPause--) +- [ExpectedPause()](#Pausable-ExpectedPause--) +#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +- [ERC1155InsufficientBalance(sender, balance, needed, tokenId)](#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC1155InvalidSender(sender)](#IERC1155Errors-ERC1155InvalidSender-address-) +- [ERC1155InvalidReceiver(receiver)](#IERC1155Errors-ERC1155InvalidReceiver-address-) +- [ERC1155MissingApprovalForAll(operator, owner)](#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-) +- [ERC1155InvalidApprover(approver)](#IERC1155Errors-ERC1155InvalidApprover-address-) +- [ERC1155InvalidOperator(operator)](#IERC1155Errors-ERC1155InvalidOperator-address-) +- [ERC1155InvalidArrayLength(idsLength, valuesLength)](#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-) +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

_update(address from, address to, uint256[] ids, uint256[] values)

+
+

internal

+# +
+
+
+ +See [`ERC1155._update`](#ERC1155-_update-address-address-uint256---uint256---). + +Requirements: + +- the contract must not be paused. + +
+
+ + + +
+ +## `ERC1155Supply` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; +``` + +Extension of ERC-1155 that adds tracking of total supply per id. + +Useful for scenarios where Fungible and Non-fungible tokens have to be +clearly identified. Note: While a totalSupply of 1 might mean the +corresponding is an NFT, there is no guarantees that no other token with the +same id are not going to be minted. + + +This contract implies a global limit of 2**256 - 1 to the number of tokens +that can be minted. + + +CAUTION: This extension should not be added in an upgrade to an already deployed contract. + +
+

Functions

+
+- [totalSupply(id)](#ERC1155Supply-totalSupply-uint256-) +- [totalSupply()](#ERC1155Supply-totalSupply--) +- [exists(id)](#ERC1155Supply-exists-uint256-) +- [_update(from, to, ids, values)](#ERC1155Supply-_update-address-address-uint256---uint256---) +#### ERC1155 [!toc] +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [uri()](#ERC1155-uri-uint256-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_updateWithAcceptanceCheck(from, to, ids, values, data)](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, value, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, value, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, values, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, value)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, values)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +- [ERC1155InsufficientBalance(sender, balance, needed, tokenId)](#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC1155InvalidSender(sender)](#IERC1155Errors-ERC1155InvalidSender-address-) +- [ERC1155InvalidReceiver(receiver)](#IERC1155Errors-ERC1155InvalidReceiver-address-) +- [ERC1155MissingApprovalForAll(operator, owner)](#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-) +- [ERC1155InvalidApprover(approver)](#IERC1155Errors-ERC1155InvalidApprover-address-) +- [ERC1155InvalidOperator(operator)](#IERC1155Errors-ERC1155InvalidOperator-address-) +- [ERC1155InvalidArrayLength(idsLength, valuesLength)](#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-) +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

totalSupply(uint256 id) → uint256

+
+

public

+# +
+
+
+ +Total value of tokens in with a given id. + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

public

+# +
+
+
+ +Total value of tokens. + +
+
+ + + +
+
+

exists(uint256 id) → bool

+
+

public

+# +
+
+
+ +Indicates whether any token exist with a given id, or not. + +
+
+ + + +
+
+

_update(address from, address to, uint256[] ids, uint256[] values)

+
+

internal

+# +
+
+
+ +Transfers a `value` amount of tokens of type `id` from `from` to `to`. Will mint (or burn) if `from` +(or `to`) is the zero address. + +Emits a [`IERC1155.TransferSingle`](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) event if the arrays contain one element, and [`IERC1155.TransferBatch`](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) otherwise. + +Requirements: + +- If `to` refers to a smart contract, it must implement either [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) + or [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) and return the acceptance magic value. +- `ids` and `values` must have the same length. + + +The ERC-1155 acceptance check is not performed in this function. See [`ERC1155._updateWithAcceptanceCheck`](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) instead. + + +
+
+ + + +
+ +## `ERC1155URIStorage` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol"; +``` + +ERC-1155 token with storage based token URI management. +Inspired by the [`ERC721URIStorage`](/contracts/5.x/api/token/ERC721#ERC721URIStorage) extension + +
+

Functions

+
+- [uri(tokenId)](#ERC1155URIStorage-uri-uint256-) +- [_setURI(tokenId, tokenURI)](#ERC1155URIStorage-_setURI-uint256-string-) +- [_setBaseURI(baseURI)](#ERC1155URIStorage-_setBaseURI-string-) +#### ERC1155 [!toc] +- [supportsInterface(interfaceId)](#ERC1155-supportsInterface-bytes4-) +- [balanceOf(account, id)](#ERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#ERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#ERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#ERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_update(from, to, ids, values)](#ERC1155-_update-address-address-uint256---uint256---) +- [_updateWithAcceptanceCheck(from, to, ids, values, data)](#ERC1155-_updateWithAcceptanceCheck-address-address-uint256---uint256---bytes-) +- [_safeTransferFrom(from, to, id, value, data)](#ERC1155-_safeTransferFrom-address-address-uint256-uint256-bytes-) +- [_safeBatchTransferFrom(from, to, ids, values, data)](#ERC1155-_safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +- [_setURI(newuri)](#ERC1155-_setURI-string-) +- [_mint(to, id, value, data)](#ERC1155-_mint-address-uint256-uint256-bytes-) +- [_mintBatch(to, ids, values, data)](#ERC1155-_mintBatch-address-uint256---uint256---bytes-) +- [_burn(from, id, value)](#ERC1155-_burn-address-uint256-uint256-) +- [_burnBatch(from, ids, values)](#ERC1155-_burnBatch-address-uint256---uint256---) +- [_setApprovalForAll(owner, operator, approved)](#ERC1155-_setApprovalForAll-address-address-bool-) +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### ERC1155 [!toc] +#### IERC1155Errors [!toc] +- [ERC1155InsufficientBalance(sender, balance, needed, tokenId)](#IERC1155Errors-ERC1155InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC1155InvalidSender(sender)](#IERC1155Errors-ERC1155InvalidSender-address-) +- [ERC1155InvalidReceiver(receiver)](#IERC1155Errors-ERC1155InvalidReceiver-address-) +- [ERC1155MissingApprovalForAll(operator, owner)](#IERC1155Errors-ERC1155MissingApprovalForAll-address-address-) +- [ERC1155InvalidApprover(approver)](#IERC1155Errors-ERC1155InvalidApprover-address-) +- [ERC1155InvalidOperator(operator)](#IERC1155Errors-ERC1155InvalidOperator-address-) +- [ERC1155InvalidArrayLength(idsLength, valuesLength)](#IERC1155Errors-ERC1155InvalidArrayLength-uint256-uint256-) +#### IERC1155MetadataURI [!toc] +#### IERC1155 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

uri(uint256 tokenId) → string

+
+

public

+# +
+
+
+ +See [`IERC1155MetadataURI.uri`](#IERC1155MetadataURI-uri-uint256-). + +This implementation returns the concatenation of the `_baseURI` +and the token-specific uri if the latter is set + +This enables the following behaviors: + +- if `_tokenURIs[tokenId]` is set, then the result is the concatenation + of `_baseURI` and `_tokenURIs[tokenId]` (keep in mind that `_baseURI` + is empty per default); + +- if `_tokenURIs[tokenId]` is NOT set then we fallback to `super.uri()` + which in most cases will contain `ERC1155._uri`; + +- if `_tokenURIs[tokenId]` is NOT set, and if the parents do not have a + uri value set, then the result is empty. + +
+
+ + + +
+
+

_setURI(uint256 tokenId, string tokenURI)

+
+

internal

+# +
+
+
+ +Sets `tokenURI` as the tokenURI of `tokenId`. + +
+
+ + + +
+
+

_setBaseURI(string baseURI)

+
+

internal

+# +
+
+
+ +Sets `baseURI` as the `_baseURI` for all tokens + +
+
+ + + +
+ +## `IERC1155MetadataURI` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; +``` + +Interface of the optional ERC1155MetadataExtension interface, as defined +in the [ERC](https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions). + +
+

Functions

+
+- [uri(id)](#IERC1155MetadataURI-uri-uint256-) +#### IERC1155 [!toc] +- [balanceOf(account, id)](#IERC1155-balanceOf-address-uint256-) +- [balanceOfBatch(accounts, ids)](#IERC1155-balanceOfBatch-address---uint256---) +- [setApprovalForAll(operator, approved)](#IERC1155-setApprovalForAll-address-bool-) +- [isApprovedForAll(account, operator)](#IERC1155-isApprovedForAll-address-address-) +- [safeTransferFrom(from, to, id, value, data)](#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) +- [safeBatchTransferFrom(from, to, ids, values, data)](#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC1155 [!toc] +- [TransferSingle(operator, from, to, id, value)](#IERC1155-TransferSingle-address-address-address-uint256-uint256-) +- [TransferBatch(operator, from, to, ids, values)](#IERC1155-TransferBatch-address-address-address-uint256---uint256---) +- [ApprovalForAll(account, operator, approved)](#IERC1155-ApprovalForAll-address-address-bool-) +- [URI(value, id)](#IERC1155-URI-string-uint256-) +#### IERC165 [!toc] +
+
+ + + +
+
+

uri(uint256 id) → string

+
+

external

+# +
+
+
+ +Returns the URI for token type `id`. + +If the `\{id\}` substring is present in the URI, it must be replaced by +clients with the actual token type ID. + +
+
+ + + +
+ +## `ERC1155Holder` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +``` + +Simple implementation of `IERC1155Receiver` that will allow a contract to hold ERC-1155 tokens. + + +When inheriting this contract, you must include a way to use the received tokens, otherwise they will be +stuck. + + +@custom:stateless + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC1155Holder-supportsInterface-bytes4-) +- [onERC1155Received(, , , , )](#ERC1155Holder-onERC1155Received-address-address-uint256-uint256-bytes-) +- [onERC1155BatchReceived(, , , , )](#ERC1155Holder-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +#### IERC1155Receiver [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

onERC1155Received(address, address, uint256, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

onERC1155BatchReceived(address, address, uint256[], uint256[], bytes) → bytes4

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC1155Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Utils.sol"; +``` + +Library that provide common ERC-1155 utility functions. + +See [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155). + +_Available since v5.1._ + +
+

Functions

+
+- [checkOnERC1155Received(operator, from, to, id, value, data)](#ERC1155Utils-checkOnERC1155Received-address-address-address-uint256-uint256-bytes-) +- [checkOnERC1155BatchReceived(operator, from, to, ids, values, data)](#ERC1155Utils-checkOnERC1155BatchReceived-address-address-address-uint256---uint256---bytes-) +
+
+ + + +
+
+

checkOnERC1155Received(address operator, address from, address to, uint256 id, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Performs an acceptance check for the provided `operator` by calling [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) +on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). + +The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). +Otherwise, the recipient must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the acceptance magic value to accept +the transfer. + +
+
+ + + +
+
+

checkOnERC1155BatchReceived(address operator, address from, address to, uint256[] ids, uint256[] values, bytes data)

+
+

internal

+# +
+
+
+ +Performs a batch acceptance check for the provided `operator` by calling [`IERC1155Receiver.onERC1155BatchReceived`](#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) +on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). + +The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). +Otherwise, the recipient must implement [`IERC1155Receiver.onERC1155Received`](#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and return the acceptance magic value to accept +the transfer. + +
+
diff --git a/docs/content/contracts/5.x/api/token/ERC20.mdx b/docs/content/contracts/5.x/api/token/ERC20.mdx new file mode 100644 index 00000000..b8e4b2f2 --- /dev/null +++ b/docs/content/contracts/5.x/api/token/ERC20.mdx @@ -0,0 +1,4192 @@ +--- +title: "ERC20" +description: "Smart contract ERC20 utilities and implementations" +--- + +This set of interfaces, contracts, and utilities are all related to the [ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20). + + +For an overview of ERC-20 tokens and a walk through on how to create a token contract read our [ERC-20 guide](/contracts/5.x/erc20). + + +There are a few core contracts that implement the behavior specified in the ERC-20 standard: + +* [`IERC20`](#IERC20): the interface all ERC-20 implementations should conform to. +* [`IERC20Metadata`](#IERC20Metadata): the extended ERC-20 interface including the [`name`](#ERC20-name--), [`symbol`](#ERC20-symbol--) and [`decimals`](#ERC20-decimals--) functions. +* [`ERC20`](#ERC20): the implementation of the ERC-20 interface, including the [`name`](#ERC20-name--), [`symbol`](#ERC20-symbol--) and [`decimals`](#ERC20-decimals--) optional extensions to the standard interface. + +Additionally there are multiple custom extensions, including: + +* [`ERC20Permit`](#ERC20Permit): gasless approval of tokens (standardized as ERC-2612). +* [`ERC20Bridgeable`](#ERC20Bridgeable): compatibility with crosschain bridges through ERC-7802. +* [`ERC20Burnable`](#ERC20Burnable): destruction of own tokens. +* [`ERC20Capped`](#ERC20Capped): enforcement of a cap to the total supply when minting tokens. +* [`ERC20Pausable`](#ERC20Pausable): ability to pause token transfers. +* [`ERC20FlashMint`](#ERC20FlashMint): token level support for flash loans through the minting and burning of ephemeral tokens (standardized as ERC-3156). +* [`ERC20Votes`](#ERC20Votes): support for voting and vote delegation. [See the governance guide for a minimal example (with the required overrides when combining ERC20 + ERC20Permit + ERC20Votes)](/contracts/5.x/api/governance#token). +* [`ERC20Wrapper`](#ERC20Wrapper): wrapper to create an ERC-20 backed by another ERC-20, with deposit and withdraw methods. Useful in conjunction with [`ERC20Votes`](#ERC20Votes). +* [`ERC20TemporaryApproval`](#ERC20TemporaryApproval): support for approvals lasting for only one transaction, as defined in ERC-7674. +* [`ERC1363`](#ERC1363): support for calling the target of a transfer or approval, enabling code execution on the receiver within a single transaction. +* [`ERC4626`](#ERC4626): tokenized vault that manages shares (represented as ERC-20) that are backed by assets (another ERC-20). + +Finally, there are some utilities to interact with ERC-20 contracts in various ways: + +* [`SafeERC20`](#SafeERC20): a wrapper around the interface that eliminates the need to handle boolean return values. + +Other utilities that support ERC-20 assets can be found in the codebase: + +* ERC-20 tokens can be timelocked (held for a beneficiary until a specified time) or vested (released following a given schedule) using a [`VestingWallet`](../finance#VestingWallet). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC-20 (such as [`_mint`](#ERC20-_mint-address-uint256-)) and expose them as external functions in the way they prefer. + + +## Core + +[`IERC20`](#IERC20) + +[`IERC20Metadata`](#IERC20Metadata) + +[`ERC20`](#ERC20) + +## Extensions + +[`IERC20Permit`](#IERC20Permit) + +[`ERC20Permit`](#ERC20Permit) + +[`ERC20Bridgeable`](#ERC20Bridgeable) + +[`ERC20Burnable`](#ERC20Burnable) + +[`ERC20Capped`](#ERC20Capped) + +[`ERC20Pausable`](#ERC20Pausable) + +[`ERC20Votes`](#ERC20Votes) + +[`ERC20Wrapper`](#ERC20Wrapper) + +[`ERC20FlashMint`](#ERC20FlashMint) + +[`ERC20TemporaryApproval`](#ERC20TemporaryApproval) + +[`ERC1363`](#ERC1363) + +[`ERC4626`](#ERC4626) + +## Utilities + +[`SafeERC20`](#SafeERC20) + +[`ERC1363Utils`](#ERC1363Utils) + + + +
+ +## `ERC20` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +``` + +Implementation of the [`IERC20`](#IERC20) interface. + +This implementation is agnostic to the way tokens are created. This means +that a supply mechanism has to be added in a derived contract using [`ERC1155._mint`](/contracts/5.x/api/token/ERC1155#ERC1155-_mint-address-uint256-uint256-bytes-). + + +For a detailed writeup see our guide +[How +to implement supply mechanisms](https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226). + + +The default value of [`IERC6909Metadata.decimals`](../interfaces#IERC6909Metadata-decimals-uint256-) is 18. To change this, you should override +this function so it returns a different value. + +We have followed general OpenZeppelin Contracts guidelines: functions revert +instead returning `false` on failure. This behavior is nonetheless +conventional and does not conflict with the expectations of ERC-20 +applications. + +
+

Functions

+
+- [constructor(name_, symbol_)](#ERC20-constructor-string-string-) +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

constructor(string name_, string symbol_)

+
+

internal

+# +
+
+
+ +Sets the values for [`Governor.name`](../governance#Governor-name--) and [`IERC6909Metadata.symbol`](../interfaces#IERC6909Metadata-symbol-uint256-). + +Both values are immutable: they can only be set once during construction. + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

public

+# +
+
+
+ +Returns the symbol of the token, usually a shorter version of the +name. + +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +Returns the number of decimals used to get its user representation. +For example, if `decimals` equals `2`, a balance of `505` tokens should +be displayed to a user as `5.05` (`505 / 10 ** 2`). + +Tokens usually opt for a value of 18, imitating the relationship between +Ether and Wei. This is the default value returned by this function, unless +it's overridden. + + +This information is only used for _display_ purposes: it in +no way affects any of the arithmetic of the contract, including +[`IERC20.balanceOf`](#IERC20-balanceOf-address-) and [`IERC20.transfer`](#IERC20-transfer-address-uint256-). + + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

public

+# +
+
+
+ +Returns the value of tokens in existence. + +
+
+ + + +
+
+

balanceOf(address account) → uint256

+
+

public

+# +
+
+
+ +Returns the value of tokens owned by `account`. + +
+
+ + + +
+
+

transfer(address to, uint256 value) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.transfer`](#IERC20-transfer-address-uint256-). + +Requirements: + +- `to` cannot be the zero address. +- the caller must have a balance of at least `value`. + +
+
+ + + +
+
+

allowance(address owner, address spender) → uint256

+
+

public

+# +
+
+
+ +Returns the remaining number of tokens that `spender` will be +allowed to spend on behalf of `owner` through [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-). This is +zero by default. + +This value changes when [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-) are called. + +
+
+ + + +
+
+

approve(address spender, uint256 value) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.approve`](#IERC20-approve-address-uint256-). + + +If `value` is the maximum `uint256`, the allowance is not updated on +`transferFrom`. This is semantically equivalent to an infinite approval. + + +Requirements: + +- `spender` cannot be the zero address. + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 value) → bool

+
+

public

+# +
+
+
+ +See [`IERC20.transferFrom`](#IERC20-transferFrom-address-address-uint256-). + +Skips emitting an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event indicating an allowance update. This is not +required by the ERC. See [_approve](#ERC20-_approve-address-address-uint256-bool-). + + +Does not update the allowance if the current allowance +is the maximum `uint256`. + + +Requirements: + +- `from` and `to` cannot be the zero address. +- `from` must have a balance of at least `value`. +- the caller must have allowance for ``from``'s tokens of at least +`value`. + +
+
+ + + +
+
+

_transfer(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Moves a `value` amount of tokens from `from` to `to`. + +This internal function is equivalent to [`IERC6909.transfer`](../interfaces#IERC6909-transfer-address-uint256-uint256-), and can be used to +e.g. implement automatic token fees, slashing mechanisms, etc. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + + +This function is not virtual, [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) should be overridden instead. + + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` +(or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding +this function. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_mint(address account, uint256 value)

+
+

internal

+# +
+
+
+ +Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0). +Relies on the `_update` mechanism + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event with `from` set to the zero address. + + +This function is not virtual, [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) should be overridden instead. + + +
+
+ + + +
+
+

_burn(address account, uint256 value)

+
+

internal

+# +
+
+
+ +Destroys a `value` amount of tokens from `account`, lowering the total supply. +Relies on the `_update` mechanism. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event with `to` set to the zero address. + + +This function is not virtual, [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) should be overridden instead + + +
+
+ + + +
+
+

_approve(address owner, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Sets `value` as the allowance of `spender` over the `owner`'s tokens. + +This internal function is equivalent to `approve`, and can be used to +e.g. set automatic allowances for certain subsystems, etc. + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + +Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + +
+
+ + + +
+
+

_approve(address owner, address spender, uint256 value, bool emitEvent)

+
+

internal

+# +
+
+
+ +Variant of [`ERC20._approve`](#ERC20-_approve-address-address-uint256-bool-) with an optional flag to enable or disable the [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +By default (when calling [`ERC20._approve`](#ERC20-_approve-address-address-uint256-bool-)) the flag is set to true. On the other hand, approval changes made by +`_spendAllowance` during the `transferFrom` operation sets the flag to false. This saves gas by not emitting any +`Approval` event during `transferFrom` operations. + +Anyone who wishes to continue emitting `Approval` events on the `transferFrom` operation can force the flag to +true using the following override: + +```solidity +function _approve(address owner, address spender, uint256 value, bool) internal virtual override { + super._approve(owner, spender, value, true); +} +``` + +Requirements are the same as [`ERC20._approve`](#ERC20-_approve-address-address-uint256-bool-). + +
+
+ + + +
+
+

_spendAllowance(address owner, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Updates `owner`'s allowance for `spender` based on spent `value`. + +Does not update the allowance value in case of infinite allowance. +Revert if not enough allowance is available. + +Does not emit an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+ +## `IERC20` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +``` + +Interface of the ERC-20 standard as defined in the ERC. + +
+

Functions

+
+- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, value)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, value)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

totalSupply() → uint256

+
+

external

+# +
+
+
+ +Returns the value of tokens in existence. + +
+
+ + + +
+
+

balanceOf(address account) → uint256

+
+

external

+# +
+
+
+ +Returns the value of tokens owned by `account`. + +
+
+ + + +
+
+

transfer(address to, uint256 value) → bool

+
+

external

+# +
+
+
+ +Moves a `value` amount of tokens from the caller's account to `to`. + +Returns a boolean value indicating whether the operation succeeded. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

allowance(address owner, address spender) → uint256

+
+

external

+# +
+
+
+ +Returns the remaining number of tokens that `spender` will be +allowed to spend on behalf of `owner` through [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-). This is +zero by default. + +This value changes when [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-) are called. + +
+
+ + + +
+
+

approve(address spender, uint256 value) → bool

+
+

external

+# +
+
+
+ +Sets a `value` amount of tokens as the allowance of `spender` over the +caller's tokens. + +Returns a boolean value indicating whether the operation succeeded. + + +Beware that changing an allowance with this method brings the risk +that someone may use both the old and the new allowance by unfortunate +transaction ordering. One possible solution to mitigate this race +condition is to first reduce the spender's allowance to 0 and set the +desired value afterwards: +https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 value) → bool

+
+

external

+# +
+
+
+ +Moves a `value` amount of tokens from `from` to `to` using the +allowance mechanism. `value` is then deducted from the caller's +allowance. + +Returns a boolean value indicating whether the operation succeeded. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

Transfer(address indexed from, address indexed to, uint256 value)

+
+

event

+# +
+
+ +
+ +Emitted when `value` tokens are moved from one account (`from`) to +another (`to`). + +Note that `value` may be zero. + +
+
+ + +
+
+

Approval(address indexed owner, address indexed spender, uint256 value)

+
+

event

+# +
+
+ +
+ +Emitted when the allowance of a `spender` for an `owner` is set by +a call to [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-). `value` is the new allowance. + +
+
+ + + +
+ +## `ERC1363` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol"; +``` + +Extension of [`ERC20`](#ERC20) tokens that adds support for code execution after transfers and approvals +on recipient contracts. Calls after transfers are enabled through the [`ERC1363.transferAndCall`](#ERC1363-transferAndCall-address-uint256-bytes-) and +[`ERC1363.transferFromAndCall`](#ERC1363-transferFromAndCall-address-address-uint256-bytes-) methods while calls after approvals can be made with [`ERC1363.approveAndCall`](#ERC1363-approveAndCall-address-uint256-bytes-) + +_Available since v5.1._ + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC1363-supportsInterface-bytes4-) +- [transferAndCall(to, value)](#ERC1363-transferAndCall-address-uint256-) +- [transferAndCall(to, value, data)](#ERC1363-transferAndCall-address-uint256-bytes-) +- [transferFromAndCall(from, to, value)](#ERC1363-transferFromAndCall-address-address-uint256-) +- [transferFromAndCall(from, to, value, data)](#ERC1363-transferFromAndCall-address-address-uint256-bytes-) +- [approveAndCall(spender, value)](#ERC1363-approveAndCall-address-uint256-) +- [approveAndCall(spender, value, data)](#ERC1363-approveAndCall-address-uint256-bytes-) +#### IERC1363 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC1363 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC1363TransferFailed(receiver, value)](#ERC1363-ERC1363TransferFailed-address-uint256-) +- [ERC1363TransferFromFailed(sender, receiver, value)](#ERC1363-ERC1363TransferFromFailed-address-address-uint256-) +- [ERC1363ApproveFailed(spender, value)](#ERC1363-ERC1363ApproveFailed-address-uint256-) +#### IERC1363 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

transferAndCall(address to, uint256 value) → bool

+
+

public

+# +
+
+
+ +Moves a `value` amount of tokens from the caller's account to `to` +and then calls [`IERC1363Receiver.onTransferReceived`](../interfaces#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on `to`. Returns a flag that indicates +if the call succeeded. + +Requirements: + +- The target has code (i.e. is a contract). +- The target `to` must implement the [`IERC1363Receiver`](../interfaces#IERC1363Receiver) interface. +- The target must return the [`IERC1363Receiver.onTransferReceived`](../interfaces#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) selector to accept the transfer. +- The internal [`IERC6909.transfer`](../interfaces#IERC6909-transfer-address-uint256-uint256-) must succeed (returned `true`). + +
+
+ + + +
+
+

transferAndCall(address to, uint256 value, bytes data) → bool

+
+

public

+# +
+
+
+ +Variant of [`IERC1363.transferAndCall`](../interfaces#IERC1363-transferAndCall-address-uint256-bytes-) that accepts an additional `data` parameter with +no specified format. + +
+
+ + + +
+
+

transferFromAndCall(address from, address to, uint256 value) → bool

+
+

public

+# +
+
+
+ +Moves a `value` amount of tokens from `from` to `to` using the allowance mechanism +and then calls [`IERC1363Receiver.onTransferReceived`](../interfaces#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on `to`. Returns a flag that indicates +if the call succeeded. + +Requirements: + +- The target has code (i.e. is a contract). +- The target `to` must implement the [`IERC1363Receiver`](../interfaces#IERC1363Receiver) interface. +- The target must return the [`IERC1363Receiver.onTransferReceived`](../interfaces#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) selector to accept the transfer. +- The internal [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-) must succeed (returned `true`). + +
+
+ + + +
+
+

transferFromAndCall(address from, address to, uint256 value, bytes data) → bool

+
+

public

+# +
+
+
+ +Variant of [`IERC1363.transferFromAndCall`](../interfaces#IERC1363-transferFromAndCall-address-address-uint256-bytes-) that accepts an additional `data` parameter with +no specified format. + +
+
+ + + +
+
+

approveAndCall(address spender, uint256 value) → bool

+
+

public

+# +
+
+
+ +Sets a `value` amount of tokens as the allowance of `spender` over the +caller's tokens and then calls [`IERC1363Spender.onApprovalReceived`](../interfaces#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) on `spender`. +Returns a flag that indicates if the call succeeded. + +Requirements: + +- The target has code (i.e. is a contract). +- The target `spender` must implement the [`IERC1363Spender`](../interfaces#IERC1363Spender) interface. +- The target must return the [`IERC1363Spender.onApprovalReceived`](../interfaces#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) selector to accept the approval. +- The internal [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) must succeed (returned `true`). + +
+
+ + + +
+
+

approveAndCall(address spender, uint256 value, bytes data) → bool

+
+

public

+# +
+
+
+ +Variant of [`IERC1363.approveAndCall`](../interfaces#IERC1363-approveAndCall-address-uint256-bytes-) that accepts an additional `data` parameter with +no specified format. + +
+
+ + + +
+
+

ERC1363TransferFailed(address receiver, uint256 value)

+
+

error

+# +
+
+
+ +Indicates a failure within the [`IERC6909.transfer`](../interfaces#IERC6909-transfer-address-uint256-uint256-) part of a transferAndCall operation. + +
+
+ + + +
+
+

ERC1363TransferFromFailed(address sender, address receiver, uint256 value)

+
+

error

+# +
+
+
+ +Indicates a failure within the [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-) part of a transferFromAndCall operation. + +
+
+ + + +
+
+

ERC1363ApproveFailed(address spender, uint256 value)

+
+

error

+# +
+
+
+ +Indicates a failure within the [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) part of a approveAndCall operation. + +
+
+ + + +
+ +## `ERC20Burnable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +``` + +Extension of [`ERC20`](#ERC20) that allows token holders to destroy both their own +tokens and those that they have an allowance for, in a way that can be +recognized off-chain (via event analysis). + +
+

Functions

+
+- [burn(value)](#ERC20Burnable-burn-uint256-) +- [burnFrom(account, value)](#ERC20Burnable-burnFrom-address-uint256-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

burn(uint256 value)

+
+

public

+# +
+
+
+ +Destroys a `value` amount of tokens from the caller. + +See [`ERC20._burn`](#ERC20-_burn-address-uint256-). + +
+
+ + + +
+
+

burnFrom(address account, uint256 value)

+
+

public

+# +
+
+
+ +Destroys a `value` amount of tokens from `account`, deducting from +the caller's allowance. + +See [`ERC20._burn`](#ERC20-_burn-address-uint256-) and [`ERC20.allowance`](#ERC20-allowance-address-address-). + +Requirements: + +- the caller must have allowance for ``accounts``'s tokens of at least +`value`. + +
+
+ + + +
+ +## `ERC20Capped` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol"; +``` + +Extension of [`ERC20`](#ERC20) that adds a cap to the supply of tokens. + +
+

Functions

+
+- [constructor(cap_)](#ERC20Capped-constructor-uint256-) +- [cap()](#ERC20Capped-cap--) +- [_update(from, to, value)](#ERC20Capped-_update-address-address-uint256-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20ExceededCap(increasedSupply, cap)](#ERC20Capped-ERC20ExceededCap-uint256-uint256-) +- [ERC20InvalidCap(cap)](#ERC20Capped-ERC20InvalidCap-uint256-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

constructor(uint256 cap_)

+
+

internal

+# +
+
+
+ +Sets the value of the `cap`. This value is immutable, it can only be +set once during construction. + +
+
+ + + +
+
+

cap() → uint256

+
+

public

+# +
+
+
+ +Returns the cap on the token's total supply. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from` +(or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding +this function. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

ERC20ExceededCap(uint256 increasedSupply, uint256 cap)

+
+

error

+# +
+
+
+ +Total supply cap has been exceeded. + +
+
+ + + +
+
+

ERC20InvalidCap(uint256 cap)

+
+

error

+# +
+
+
+ +The supplied cap is not a valid cap. + +
+
+ + + +
+ +## `ERC20FlashMint` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20FlashMint.sol"; +``` + +Implementation of the ERC-3156 Flash loans extension, as defined in +[ERC-3156](https://eips.ethereum.org/EIPS/eip-3156). + +Adds the [`IERC3156FlashLender.flashLoan`](../interfaces#IERC3156FlashLender-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-) method, which provides flash loan support at the token +level. By default there is no fee, but this can be changed by overriding [`IERC3156FlashLender.flashFee`](../interfaces#IERC3156FlashLender-flashFee-address-uint256-). + + +When this extension is used along with the [`ERC20Capped`](#ERC20Capped) or [`ERC20Votes`](#ERC20Votes) extensions, +[`IERC3156FlashLender.maxFlashLoan`](../interfaces#IERC3156FlashLender-maxFlashLoan-address-) will not correctly reflect the maximum that can be flash minted. We recommend +overriding [`IERC3156FlashLender.maxFlashLoan`](../interfaces#IERC3156FlashLender-maxFlashLoan-address-) so that it correctly reflects the supply cap. + + +
+

Functions

+
+- [maxFlashLoan(token)](#ERC20FlashMint-maxFlashLoan-address-) +- [flashFee(token, value)](#ERC20FlashMint-flashFee-address-uint256-) +- [_flashFee(token, value)](#ERC20FlashMint-_flashFee-address-uint256-) +- [_flashFeeReceiver()](#ERC20FlashMint-_flashFeeReceiver--) +- [flashLoan(receiver, token, value, data)](#ERC20FlashMint-flashLoan-contract-IERC3156FlashBorrower-address-uint256-bytes-) +#### IERC3156FlashLender [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC3156FlashLender [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC3156UnsupportedToken(token)](#ERC20FlashMint-ERC3156UnsupportedToken-address-) +- [ERC3156ExceededMaxLoan(maxLoan)](#ERC20FlashMint-ERC3156ExceededMaxLoan-uint256-) +- [ERC3156InvalidReceiver(receiver)](#ERC20FlashMint-ERC3156InvalidReceiver-address-) +#### IERC3156FlashLender [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

maxFlashLoan(address token) → uint256

+
+

public

+# +
+
+
+ +Returns the maximum amount of tokens available for loan. + +
+
+ + + +
+
+

flashFee(address token, uint256 value) → uint256

+
+

public

+# +
+
+
+ +Returns the fee applied when doing flash loans. This function calls +the [`ERC20FlashMint._flashFee`](#ERC20FlashMint-_flashFee-address-uint256-) function which returns the fee applied when doing flash +loans. + +
+
+ + + +
+
+

_flashFee(address token, uint256 value) → uint256

+
+

internal

+# +
+
+
+ +Returns the fee applied when doing flash loans. By default this +implementation has 0 fees. This function can be overloaded to make +the flash loan mechanism deflationary. + +
+
+ + + +
+
+

_flashFeeReceiver() → address

+
+

internal

+# +
+
+
+ +Returns the receiver address of the flash fee. By default this +implementation returns the address(0) which means the fee amount will be burnt. +This function can be overloaded to change the fee receiver. + +
+
+ + + +
+
+

flashLoan(contract IERC3156FlashBorrower receiver, address token, uint256 value, bytes data) → bool

+
+

public

+# +
+
+
+ +Performs a flash loan. New tokens are minted and sent to the +`receiver`, who is required to implement the [`IERC3156FlashBorrower`](../interfaces#IERC3156FlashBorrower) +interface. By the end of the flash loan, the receiver is expected to own +value + fee tokens and have them approved back to the token contract itself so +they can be burned. + +
+
+ + + +
+
+

ERC3156UnsupportedToken(address token)

+
+

error

+# +
+
+
+ +The loan token is not valid. + +
+
+ + + +
+
+

ERC3156ExceededMaxLoan(uint256 maxLoan)

+
+

error

+# +
+
+
+ +The requested loan exceeds the max loan value for `token`. + +
+
+ + + +
+
+

ERC3156InvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +The receiver of a flashloan is not a valid [`IERC3156FlashBorrower.onFlashLoan`](../interfaces#IERC3156FlashBorrower-onFlashLoan-address-address-uint256-uint256-bytes-) implementer. + +
+
+ + + +
+ +## `ERC20Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Pausable.sol"; +``` + +ERC-20 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + + +This contract does not include public pause and unpause functions. In +addition to inheriting this contract, you must define both functions, invoking the +[`Pausable._pause`](../utils#Pausable-_pause--) and [`Pausable._unpause`](../utils#Pausable-_unpause--) internal functions, with appropriate +access control, e.g. using [`AccessControl`](../access#AccessControl) or [`Ownable`](../access#Ownable). Not doing so will +make the contract pause mechanism of the contract unreachable, and thus unusable. + + +
+

Functions

+
+- [_update(from, to, value)](#ERC20Pausable-_update-address-address-uint256-) +#### Pausable [!toc] +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### Pausable [!toc] +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### Pausable [!toc] +- [EnforcedPause()](#Pausable-EnforcedPause--) +- [ExpectedPause()](#Pausable-ExpectedPause--) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +See [`ERC20._update`](#ERC20-_update-address-address-uint256-). + +Requirements: + +- the contract must not be paused. + +
+
+ + + +
+ +## `ERC20Permit` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +``` + +Implementation of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in +[ERC-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Adds the [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) method, which can be used to change an account's ERC-20 allowance (see [`IERC20.allowance`](#IERC20-allowance-address-address-)) by +presenting a message signed by the account. By not relying on `[`IERC20.approve`](#IERC20-approve-address-uint256-)`, the token holder account doesn't +need to send a transaction, and thus is not required to hold Ether at all. + +
+

Functions

+
+- [constructor(name)](#ERC20Permit-constructor-string-) +- [permit(owner, spender, value, deadline, v, r, s)](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#ERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#ERC20Permit-DOMAIN_SEPARATOR--) +#### Nonces [!toc] +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### IERC20Permit [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### IERC20Permit [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC2612ExpiredSignature(deadline)](#ERC20Permit-ERC2612ExpiredSignature-uint256-) +- [ERC2612InvalidSigner(signer, owner)](#ERC20Permit-ERC2612InvalidSigner-address-address-) +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### IERC20Permit [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

constructor(string name)

+
+

internal

+# +
+
+
+ +Initializes the [`EIP712`](../utils/cryptography#EIP712) domain separator using the `name` parameter, and setting `version` to `"1"`. + +It's a good idea to use the same `name` that is defined as the ERC-20 token name. + +
+
+ + + +
+
+

permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)

+
+

public

+# +
+
+
+ +Sets `value` as the allowance of `spender` over ``owner``'s tokens, +given ``owner``'s signed approval. + + +The same issues [`IERC20.approve`](#IERC20-approve-address-uint256-) has related to transaction +ordering also applies here. + + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +Requirements: + +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use ``owner``'s current nonce (see [`ERC20Permit.nonces`](#ERC20Permit-nonces-address-)). + +For more information on the signature format, see the +[relevant EIP +section](https://eips.ethereum.org/EIPS/eip-2612#specification). + +CAUTION: See Security Considerations above. + +
+
+ + + +
+
+

nonces(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-). + +Every successful call to [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) increases ``owner``'s nonce by one. This +prevents a signature from being used multiple times. + +
+
+ + + +
+
+

DOMAIN_SEPARATOR() → bytes32

+
+

external

+# +
+
+
+ +Returns the domain separator used in the encoding of the signature for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-), as defined by [`EIP712`](../utils/cryptography#EIP712). + +
+
+ + + +
+
+

ERC2612ExpiredSignature(uint256 deadline)

+
+

error

+# +
+
+
+ +Permit deadline has expired. + +
+
+ + + +
+
+

ERC2612InvalidSigner(address signer, address owner)

+
+

error

+# +
+
+
+ +Mismatched signature. + +
+
+ + + +
+ +## `ERC20Votes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +``` + +Extension of ERC-20 to support Compound-like voting and delegation. This version is more generic than Compound's, +and supports token supply up to 2^208^ - 1, while COMP is limited to 2^96^ - 1. + + +This contract does not provide interface compatibility with Compound's COMP token. + + +This extension keeps a history (checkpoints) of each account's vote power. Vote power can be delegated either +by calling the [`Votes.delegate`](../governance#Votes-delegate-address-) function directly, or by providing a signature to be used with [`Votes.delegateBySig`](../governance#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-). Voting +power can be queried through the public accessors [`Votes.getVotes`](../governance#Votes-getVotes-address-) and [`Votes.getPastVotes`](../governance#Votes-getPastVotes-address-uint256-). + +By default, token balance does not account for voting power. This makes transfers cheaper. The downside is that it +requires users to delegate to themselves in order to activate checkpoints and have their voting power tracked. + +
+

Functions

+
+- [_maxSupply()](#ERC20Votes-_maxSupply--) +- [_update(from, to, value)](#ERC20Votes-_update-address-address-uint256-) +- [_getVotingUnits(account)](#ERC20Votes-_getVotingUnits-address-) +- [numCheckpoints(account)](#ERC20Votes-numCheckpoints-address-) +- [checkpoints(account, pos)](#ERC20Votes-checkpoints-address-uint32-) +#### Votes [!toc] +- [clock()](#Votes-clock--) +- [CLOCK_MODE()](#Votes-CLOCK_MODE--) +- [_validateTimepoint(timepoint)](#Votes-_validateTimepoint-uint256-) +- [getVotes(account)](#Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#Votes-getPastTotalSupply-uint256-) +- [_getTotalSupply()](#Votes-_getTotalSupply--) +- [delegates(account)](#Votes-delegates-address-) +- [delegate(delegatee)](#Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#Votes-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#Votes-_transferVotingUnits-address-address-uint256-) +- [_moveDelegateVotes(from, to, amount)](#Votes-_moveDelegateVotes-address-address-uint256-) +- [_numCheckpoints(account)](#Votes-_numCheckpoints-address-) +- [_checkpoints(account, pos)](#Votes-_checkpoints-address-uint32-) +#### IERC5805 [!toc] +#### IVotes [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### Votes [!toc] +#### IERC5805 [!toc] +#### IVotes [!toc] +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20ExceededSafeSupply(increasedSupply, cap)](#ERC20Votes-ERC20ExceededSafeSupply-uint256-uint256-) +#### Votes [!toc] +- [ERC6372InconsistentClock()](#Votes-ERC6372InconsistentClock--) +- [ERC5805FutureLookup(timepoint, clock)](#Votes-ERC5805FutureLookup-uint256-uint48-) +#### IERC5805 [!toc] +#### IVotes [!toc] +- [VotesExpiredSignature(expiry)](#IVotes-VotesExpiredSignature-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

_maxSupply() → uint256

+
+

internal

+# +
+
+
+ +Maximum token supply. Defaults to `type(uint208).max` (2^208^ - 1). + +This maximum is enforced in [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---). It limits the total supply of the token, which is otherwise a uint256, +so that checkpoints can be stored in the Trace208 structure used by [`Votes`](../governance#Votes). Increasing this value will not +remove the underlying limitation, and will cause [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) to fail because of a math overflow in +[`Votes._transferVotingUnits`](../governance#Votes-_transferVotingUnits-address-address-uint256-). An override could be used to further restrict the total supply (to a lower value) if +additional logic requires it. When resolving override conflicts on this function, the minimum should be +returned. + +
+
+ + + +
+
+

_update(address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Move voting power when tokens are transferred. + +Emits a [`IVotes.DelegateVotesChanged`](../governance#IVotes-DelegateVotesChanged-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_getVotingUnits(address account) → uint256

+
+

internal

+# +
+
+
+ +Returns the voting units of an `account`. + + +Overriding this function may compromise the internal vote accounting. +`ERC20Votes` assumes tokens map to voting units 1:1 and this is not easy to change. + + +
+
+ + + +
+
+

numCheckpoints(address account) → uint32

+
+

public

+# +
+
+
+ +Get number of checkpoints for `account`. + +
+
+ + + +
+
+

checkpoints(address account, uint32 pos) → struct Checkpoints.Checkpoint208

+
+

public

+# +
+
+
+ +Get the `pos`-th checkpoint for `account`. + +
+
+ + + +
+
+

ERC20ExceededSafeSupply(uint256 increasedSupply, uint256 cap)

+
+

error

+# +
+
+
+ +Total supply cap has been exceeded, introducing a risk of votes overflowing. + +
+
+ + + +
+ +## `ERC20Wrapper` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +``` + +Extension of the ERC-20 token contract to support token wrapping. + +Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". This is useful +in conjunction with other modules. For example, combining this wrapping mechanism with [`ERC20Votes`](#ERC20Votes) will allow the +wrapping of an existing "basic" ERC-20 into a governance token. + + +Any mechanism in which the underlying token changes the [`IERC6909.balanceOf`](../interfaces#IERC6909-balanceOf-address-uint256-) of an account without an explicit transfer +may desynchronize this contract's supply and its underlying balance. Please exercise caution when wrapping tokens that +may undercollateralize the wrapper (i.e. wrapper's total supply is higher than its underlying balance). See [`ERC20Wrapper._recover`](#ERC20Wrapper-_recover-address-) +for recovering value accrued to the wrapper. + + +
+

Functions

+
+- [constructor(underlyingToken)](#ERC20Wrapper-constructor-contract-IERC20-) +- [decimals()](#ERC20Wrapper-decimals--) +- [underlying()](#ERC20Wrapper-underlying--) +- [depositFor(account, value)](#ERC20Wrapper-depositFor-address-uint256-) +- [withdrawTo(account, value)](#ERC20Wrapper-withdrawTo-address-uint256-) +- [_recover(account)](#ERC20Wrapper-_recover-address-) +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC20InvalidUnderlying(token)](#ERC20Wrapper-ERC20InvalidUnderlying-address-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

constructor(contract IERC20 underlyingToken)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

underlying() → contract IERC20

+
+

public

+# +
+
+
+ +Returns the address of the underlying ERC-20 token that is being wrapped. + +
+
+ + + +
+
+

depositFor(address account, uint256 value) → bool

+
+

public

+# +
+
+
+ +Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens. + +
+
+ + + +
+
+

withdrawTo(address account, uint256 value) → bool

+
+

public

+# +
+
+
+ +Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. + +
+
+ + + +
+
+

_recover(address account) → uint256

+
+

internal

+# +
+
+
+ +Mint wrapped token to cover any underlyingTokens that would have been transferred by mistake or acquired from +rebasing mechanisms. Internal function that can be exposed with access control if desired. + +
+
+ + + +
+
+

ERC20InvalidUnderlying(address token)

+
+

error

+# +
+
+
+ +The underlying token couldn't be wrapped. + +
+
+ + + +
+ +## `ERC4626` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +``` + +Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in +[ERC-4626](https://eips.ethereum.org/EIPS/eip-4626). + +This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for +underlying "assets" through standardized [`IERC4626.deposit`](../interfaces#IERC4626-deposit-uint256-address-), [`IERC4626.mint`](../interfaces#IERC4626-mint-uint256-address-), [`IERC4626.redeem`](../interfaces#IERC4626-redeem-uint256-address-address-) and [`IERC777.burn`](../interfaces#IERC777-burn-uint256-bytes-) workflows. This contract extends +the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this +contract and not the "assets" token which is an independent contract. + + +In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning +with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation +attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial +deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may +similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by +verifying the amount received is as expected, using a wrapper that performs these checks such as +[ERC4626Router](https://github.com/fei-protocol/ERC4626#erc4626router-and-base). + +Since v4.9, this implementation introduces configurable virtual assets and shares to help developers mitigate that risk. +The `_decimalsOffset()` corresponds to an offset in the decimal representation between the underlying asset's decimals +and the vault decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which +itself determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default +offset (0) makes it non-profitable even if an attacker is able to capture value from multiple user deposits, as a result +of the value being captured by the virtual shares (out of the attacker's donation) matching the attacker's expected gains. +With a larger offset, the attack becomes orders of magnitude more expensive than it is profitable. More details about the +underlying math can be found [here](/contracts/5.x/erc4626#inflation-attack). + +The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued +to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets +will cause the first user to exit to experience reduced losses in detriment to the last users that will experience +bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the +`_convertToShares` and `_convertToAssets` functions. + +To learn more, check out our [ERC-4626 guide](/contracts/5.x/erc4626). + + +When overriding this contract, some elements must be considered: + +* When overriding the behavior of the deposit or withdraw mechanisms, it is recommended to override the internal +functions. Overriding [`ERC4626._deposit`](#ERC4626-_deposit-address-address-uint256-uint256-) automatically affects both [`IERC4626.deposit`](../interfaces#IERC4626-deposit-uint256-address-) and [`IERC4626.mint`](../interfaces#IERC4626-mint-uint256-address-). Similarly, overriding [`ERC4626._withdraw`](#ERC4626-_withdraw-address-address-address-uint256-uint256-) +automatically affects both [`IERC4626.withdraw`](../interfaces#IERC4626-withdraw-uint256-address-address-) and [`IERC4626.redeem`](../interfaces#IERC4626-redeem-uint256-address-address-). Overall it is not recommended to override the public facing +functions since that could lead to inconsistent behaviors between the [`IERC4626.deposit`](../interfaces#IERC4626-deposit-uint256-address-) and [`IERC4626.mint`](../interfaces#IERC4626-mint-uint256-address-) or between [`IERC4626.withdraw`](../interfaces#IERC4626-withdraw-uint256-address-address-) and +[`IERC4626.redeem`](../interfaces#IERC4626-redeem-uint256-address-address-), which is documented to have lead to loss of funds. + +* Overrides to the deposit or withdraw mechanism must be reflected in the preview functions as well. + +* [`IERC4626.maxWithdraw`](../interfaces#IERC4626-maxWithdraw-address-) depends on [`IERC4626.maxRedeem`](../interfaces#IERC4626-maxRedeem-address-). Therefore, overriding [`IERC4626.maxRedeem`](../interfaces#IERC4626-maxRedeem-address-) only is enough. On the other hand, +overriding [`IERC4626.maxWithdraw`](../interfaces#IERC4626-maxWithdraw-address-) only would have no effect on [`IERC4626.maxRedeem`](../interfaces#IERC4626-maxRedeem-address-), and could create an inconsistency between the two +functions. + +* If [`IERC4626.previewRedeem`](../interfaces#IERC4626-previewRedeem-uint256-) is overridden to revert, [`IERC4626.maxWithdraw`](../interfaces#IERC4626-maxWithdraw-address-) must be overridden as necessary to ensure it +always return successfully. + + +
+

Functions

+
+- [constructor(asset_)](#ERC4626-constructor-contract-IERC20-) +- [decimals()](#ERC4626-decimals--) +- [asset()](#ERC4626-asset--) +- [totalAssets()](#ERC4626-totalAssets--) +- [convertToShares(assets)](#ERC4626-convertToShares-uint256-) +- [convertToAssets(shares)](#ERC4626-convertToAssets-uint256-) +- [maxDeposit()](#ERC4626-maxDeposit-address-) +- [maxMint()](#ERC4626-maxMint-address-) +- [maxWithdraw(owner)](#ERC4626-maxWithdraw-address-) +- [maxRedeem(owner)](#ERC4626-maxRedeem-address-) +- [previewDeposit(assets)](#ERC4626-previewDeposit-uint256-) +- [previewMint(shares)](#ERC4626-previewMint-uint256-) +- [previewWithdraw(assets)](#ERC4626-previewWithdraw-uint256-) +- [previewRedeem(shares)](#ERC4626-previewRedeem-uint256-) +- [deposit(assets, receiver)](#ERC4626-deposit-uint256-address-) +- [mint(shares, receiver)](#ERC4626-mint-uint256-address-) +- [withdraw(assets, receiver, owner)](#ERC4626-withdraw-uint256-address-address-) +- [redeem(shares, receiver, owner)](#ERC4626-redeem-uint256-address-address-) +- [_convertToShares(assets, rounding)](#ERC4626-_convertToShares-uint256-enum-Math-Rounding-) +- [_convertToAssets(shares, rounding)](#ERC4626-_convertToAssets-uint256-enum-Math-Rounding-) +- [_deposit(caller, receiver, assets, shares)](#ERC4626-_deposit-address-address-uint256-uint256-) +- [_withdraw(caller, receiver, owner, assets, shares)](#ERC4626-_withdraw-address-address-address-uint256-uint256-) +- [_decimalsOffset()](#ERC4626-_decimalsOffset--) +#### IERC4626 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC4626 [!toc] +- [Deposit(sender, owner, assets, shares)](#IERC4626-Deposit-address-address-uint256-uint256-) +- [Withdraw(sender, receiver, owner, assets, shares)](#IERC4626-Withdraw-address-address-address-uint256-uint256-) +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+- [ERC4626ExceededMaxDeposit(receiver, assets, max)](#ERC4626-ERC4626ExceededMaxDeposit-address-uint256-uint256-) +- [ERC4626ExceededMaxMint(receiver, shares, max)](#ERC4626-ERC4626ExceededMaxMint-address-uint256-uint256-) +- [ERC4626ExceededMaxWithdraw(owner, assets, max)](#ERC4626-ERC4626ExceededMaxWithdraw-address-uint256-uint256-) +- [ERC4626ExceededMaxRedeem(owner, shares, max)](#ERC4626-ERC4626ExceededMaxRedeem-address-uint256-uint256-) +#### IERC4626 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

constructor(contract IERC20 asset_)

+
+

internal

+# +
+
+
+ +Set the underlying asset contract. This must be an ERC20-compatible contract (ERC-20 or ERC-777). + +
+
+ + + +
+
+

decimals() → uint8

+
+

public

+# +
+
+
+ +Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This +"original" value is cached during construction of the vault contract. If this read operation fails (e.g., the +asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. + +See [`IERC20Metadata.decimals`](#IERC20Metadata-decimals--). + +
+
+ + + +
+
+

asset() → address

+
+

public

+# +
+
+
+ +Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. + +- MUST be an ERC-20 token contract. +- MUST NOT revert. + +
+
+ + + +
+
+

totalAssets() → uint256

+
+

public

+# +
+
+
+ +Returns the total amount of the underlying asset that is “managed” by Vault. + +- SHOULD include any compounding that occurs from yield. +- MUST be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT revert. + +
+
+ + + +
+
+

convertToShares(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + + +This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the +“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + + +
+
+ + + +
+
+

convertToAssets(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal +scenario where all the conditions are met. + +- MUST NOT be inclusive of any fees that are charged against assets in the Vault. +- MUST NOT show any variations depending on the caller. +- MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. +- MUST NOT revert. + + +This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the +“average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and +from. + + +
+
+ + + +
+
+

maxDeposit(address) → uint256

+
+

public

+# +
+
+
+ +Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, +through a deposit call. + +- MUST return a limited value if receiver is subject to some deposit limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. +- MUST NOT revert. + +
+
+ + + +
+
+

maxMint(address) → uint256

+
+

public

+# +
+
+
+ +Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. +- MUST return a limited value if receiver is subject to some mint limit. +- MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. +- MUST NOT revert. + +
+
+ + + +
+
+

maxWithdraw(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the +Vault, through a withdraw call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST NOT revert. + +
+
+ + + +
+
+

maxRedeem(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, +through a redeem call. + +- MUST return a limited value if owner is subject to some withdrawal limit or timelock. +- MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. +- MUST NOT revert. + +
+
+ + + +
+
+

previewDeposit(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given +current on-chain conditions. + +- MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit + call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called + in the same transaction. +- MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the + deposit would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + + +
+
+ + + +
+
+

previewMint(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given +current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call + in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the + same transaction. +- MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint + would be accepted, regardless if the user has enough tokens approved, etc. +- MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by minting. + + +
+
+ + + +
+
+

previewWithdraw(uint256 assets) → uint256

+
+

public

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, +given current on-chain conditions. + +- MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw + call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if + called + in the same transaction. +- MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though + the withdrawal would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by depositing. + + +
+
+ + + +
+
+

previewRedeem(uint256 shares) → uint256

+
+

public

+# +
+
+
+ +Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block, +given current on-chain conditions. + +- MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call + in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the + same transaction. +- MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the + redemption would be accepted, regardless if the user has enough shares, etc. +- MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. +- MUST NOT revert. + + +any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in +share price or some other type of condition, meaning the depositor will lose assets by redeeming. + + +
+
+ + + +
+
+

deposit(uint256 assets, address receiver) → uint256

+
+

public

+# +
+
+
+ +Deposit `assets` underlying tokens and send the corresponding number of vault shares (`shares`) to `receiver`. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + deposit execution, and are accounted for during deposit. +- MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + + +most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + + +
+
+ + + +
+
+

mint(uint256 shares, address receiver) → uint256

+
+

public

+# +
+
+
+ +Mints exactly `shares` vault shares to `receiver` in exchange for `assets` underlying tokens. + +- MUST emit the Deposit event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint + execution, and are accounted for during mint. +- MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not + approving enough underlying tokens to the Vault contract, etc). + + +most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. + + +
+
+ + + +
+
+

withdraw(uint256 assets, address receiver, address owner) → uint256

+
+

public

+# +
+
+
+ +Burns shares from owner and sends exactly assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + withdraw execution, and are accounted for during withdraw. +- MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + +Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + +
+
+ + + +
+
+

redeem(uint256 shares, address receiver, address owner) → uint256

+
+

public

+# +
+
+
+ +Burns exactly shares from owner and sends assets of underlying tokens to receiver. + +- MUST emit the Withdraw event. +- MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the + redeem execution, and are accounted for during redeem. +- MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner + not having enough shares, etc). + + +some implementations will require pre-requesting to the Vault before a withdrawal may be performed. +Those methods should be performed separately. + + +
+
+ + + +
+
+

_convertToShares(uint256 assets, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Internal conversion function (from assets to shares) with support for rounding direction. + +
+
+ + + +
+
+

_convertToAssets(uint256 shares, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Internal conversion function (from shares to assets) with support for rounding direction. + +
+
+ + + +
+
+

_deposit(address caller, address receiver, uint256 assets, uint256 shares)

+
+

internal

+# +
+
+
+ +Deposit/mint common workflow. + +
+
+ + + +
+
+

_withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares)

+
+

internal

+# +
+
+
+ +Withdraw/redeem common workflow. + +
+
+ + + +
+
+

_decimalsOffset() → uint8

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max)

+
+

error

+# +
+
+
+ +Attempted to deposit more assets than the max amount for `receiver`. + +
+
+ + + +
+
+

ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max)

+
+

error

+# +
+
+
+ +Attempted to mint more shares than the max amount for `receiver`. + +
+
+ + + +
+
+

ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max)

+
+

error

+# +
+
+
+ +Attempted to withdraw more assets than the max amount for `receiver`. + +
+
+ + + +
+
+

ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max)

+
+

error

+# +
+
+
+ +Attempted to redeem more shares than the max amount for `receiver`. + +
+
+ + + +
+ +## `IERC20Metadata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +``` + +Interface for the optional metadata functions from the ERC-20 standard. + +
+

Functions

+
+- [name()](#IERC20Metadata-name--) +- [symbol()](#IERC20Metadata-symbol--) +- [decimals()](#IERC20Metadata-decimals--) +#### IERC20 [!toc] +- [totalSupply()](#IERC20-totalSupply--) +- [balanceOf(account)](#IERC20-balanceOf-address-) +- [transfer(to, value)](#IERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#IERC20-allowance-address-address-) +- [approve(spender, value)](#IERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#IERC20-transferFrom-address-address-uint256-) +
+
+ +
+

Events

+
+#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the name of the token. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the symbol of the token. + +
+
+ + + +
+
+

decimals() → uint8

+
+

external

+# +
+
+
+ +Returns the decimals places of the token. + +
+
+ + + +
+ +## `IERC20Permit` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; +``` + +Interface of the ERC-20 Permit extension allowing approvals to be made via signatures, as defined in +[ERC-2612](https://eips.ethereum.org/EIPS/eip-2612). + +Adds the [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) method, which can be used to change an account's ERC-20 allowance (see [`IERC20.allowance`](#IERC20-allowance-address-address-)) by +presenting a message signed by the account. By not relying on [`IERC20.approve`](#IERC20-approve-address-uint256-), the token holder account doesn't +need to send a transaction, and thus is not required to hold Ether at all. + +==== Security Considerations + +There are two important considerations concerning the use of `permit`. The first is that a valid permit signature +expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be +considered as an intention to spend the allowance in any specific way. The second is that because permits have +built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should +take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be +generally recommended is: + +```solidity +function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public { + try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {} + doThing(..., value); +} + +function doThing(..., uint256 value) public { + token.safeTransferFrom(msg.sender, address(this), value); + ... +} +``` + +Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of +`try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also +[`SafeERC20.safeTransferFrom`](#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-)). + +Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so +contracts should have entry points that don't rely on permit. + +
+

Functions

+
+- [permit(owner, spender, value, deadline, v, r, s)](#IERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) +- [nonces(owner)](#IERC20Permit-nonces-address-) +- [DOMAIN_SEPARATOR()](#IERC20Permit-DOMAIN_SEPARATOR--) +
+
+ + + +
+
+

permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)

+
+

external

+# +
+
+
+ +Sets `value` as the allowance of `spender` over ``owner``'s tokens, +given ``owner``'s signed approval. + + +The same issues [`IERC20.approve`](#IERC20-approve-address-uint256-) has related to transaction +ordering also applies here. + + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +Requirements: + +- `spender` cannot be the zero address. +- `deadline` must be a timestamp in the future. +- `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` +over the EIP712-formatted function arguments. +- the signature must use ``owner``'s current nonce (see [`ERC20Permit.nonces`](#ERC20Permit-nonces-address-)). + +For more information on the signature format, see the +[relevant EIP +section](https://eips.ethereum.org/EIPS/eip-2612#specification). + +CAUTION: See Security Considerations above. + +
+
+ + + +
+
+

nonces(address owner) → uint256

+
+

external

+# +
+
+
+ +Returns the current nonce for `owner`. This value must be +included whenever a signature is generated for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-). + +Every successful call to [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-) increases ``owner``'s nonce by one. This +prevents a signature from being used multiple times. + +
+
+ + + +
+
+

DOMAIN_SEPARATOR() → bytes32

+
+

external

+# +
+
+
+ +Returns the domain separator used in the encoding of the signature for [`ERC20Permit.permit`](#ERC20Permit-permit-address-address-uint256-uint256-uint8-bytes32-bytes32-), as defined by [`EIP712`](../utils/cryptography#EIP712). + +
+
+ + + +
+ +## `ERC20Bridgeable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Bridgeable.sol"; +``` + +ERC20 extension that implements the standard token interface according to +[ERC-7802](https://eips.ethereum.org/EIPS/eip-7802). + +
+

Modifiers

+
+- [onlyTokenBridge()](#ERC20Bridgeable-onlyTokenBridge--) +
+
+ +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC20Bridgeable-supportsInterface-bytes4-) +- [crosschainMint(to, value)](#ERC20Bridgeable-crosschainMint-address-uint256-) +- [crosschainBurn(from, value)](#ERC20Bridgeable-crosschainBurn-address-uint256-) +- [_checkTokenBridge(caller)](#ERC20Bridgeable-_checkTokenBridge-address-) +#### IERC7802 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [allowance(owner, spender)](#ERC20-allowance-address-address-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +- [_spendAllowance(owner, spender, value)](#ERC20-_spendAllowance-address-address-uint256-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC7802 [!toc] +- [CrosschainMint(to, amount, sender)](#IERC7802-CrosschainMint-address-uint256-address-) +- [CrosschainBurn(from, amount, sender)](#IERC7802-CrosschainBurn-address-uint256-address-) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### IERC7802 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

onlyTokenBridge()

+
+

internal

+# +
+
+ +
+ +Modifier to restrict access to the token bridge. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

crosschainMint(address to, uint256 value)

+
+

public

+# +
+
+
+ +See [`IERC7802.crosschainMint`](../interfaces#IERC7802-crosschainMint-address-uint256-). Emits a [`IERC7802.CrosschainMint`](../interfaces#IERC7802-CrosschainMint-address-uint256-address-) event. + +
+
+ + + +
+
+

crosschainBurn(address from, uint256 value)

+
+

public

+# +
+
+
+ +See [`IERC7802.crosschainBurn`](../interfaces#IERC7802-crosschainBurn-address-uint256-). Emits a [`IERC7802.CrosschainBurn`](../interfaces#IERC7802-CrosschainBurn-address-uint256-address-) event. + +
+
+ + + +
+
+

_checkTokenBridge(address caller)

+
+

internal

+# +
+
+
+ +Checks if the caller is a trusted token bridge. MUST revert otherwise. + +Developers should implement this function using an access control mechanism that allows +customizing the list of allowed senders. Consider using [`AccessControl`](../access#AccessControl) or [`AccessManaged`](../access#AccessManaged). + +
+
+ + + +
+ +## `ERC20TemporaryApproval` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20TemporaryApproval.sol"; +``` + +Extension of [`ERC20`](#ERC20) that adds support for temporary allowances following ERC-7674. + + +This is a draft contract. The corresponding ERC is still subject to changes. + + +_Available since v5.1._ + +
+

Functions

+
+- [allowance(owner, spender)](#ERC20TemporaryApproval-allowance-address-address-) +- [_temporaryAllowance(owner, spender)](#ERC20TemporaryApproval-_temporaryAllowance-address-address-) +- [temporaryApprove(spender, value)](#ERC20TemporaryApproval-temporaryApprove-address-uint256-) +- [_temporaryApprove(owner, spender, value)](#ERC20TemporaryApproval-_temporaryApprove-address-address-uint256-) +- [_spendAllowance(owner, spender, value)](#ERC20TemporaryApproval-_spendAllowance-address-address-uint256-) +#### IERC7674 [!toc] +#### ERC20 [!toc] +- [name()](#ERC20-name--) +- [symbol()](#ERC20-symbol--) +- [decimals()](#ERC20-decimals--) +- [totalSupply()](#ERC20-totalSupply--) +- [balanceOf(account)](#ERC20-balanceOf-address-) +- [transfer(to, value)](#ERC20-transfer-address-uint256-) +- [approve(spender, value)](#ERC20-approve-address-uint256-) +- [transferFrom(from, to, value)](#ERC20-transferFrom-address-address-uint256-) +- [_transfer(from, to, value)](#ERC20-_transfer-address-address-uint256-) +- [_update(from, to, value)](#ERC20-_update-address-address-uint256-) +- [_mint(account, value)](#ERC20-_mint-address-uint256-) +- [_burn(account, value)](#ERC20-_burn-address-uint256-) +- [_approve(owner, spender, value)](#ERC20-_approve-address-address-uint256-) +- [_approve(owner, spender, value, emitEvent)](#ERC20-_approve-address-address-uint256-bool-) +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ +
+

Events

+
+#### IERC7674 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +- [Transfer(from, to, value)](#IERC20-Transfer-address-address-uint256-) +- [Approval(owner, spender, value)](#IERC20-Approval-address-address-uint256-) +
+
+ +
+

Errors

+
+#### IERC7674 [!toc] +#### ERC20 [!toc] +#### IERC20Errors [!toc] +- [ERC20InsufficientBalance(sender, balance, needed)](#IERC20Errors-ERC20InsufficientBalance-address-uint256-uint256-) +- [ERC20InvalidSender(sender)](#IERC20Errors-ERC20InvalidSender-address-) +- [ERC20InvalidReceiver(receiver)](#IERC20Errors-ERC20InvalidReceiver-address-) +- [ERC20InsufficientAllowance(spender, allowance, needed)](#IERC20Errors-ERC20InsufficientAllowance-address-uint256-uint256-) +- [ERC20InvalidApprover(approver)](#IERC20Errors-ERC20InvalidApprover-address-) +- [ERC20InvalidSpender(spender)](#IERC20Errors-ERC20InvalidSpender-address-) +#### IERC20Metadata [!toc] +#### IERC20 [!toc] +
+
+ + + +
+
+

allowance(address owner, address spender) → uint256

+
+

public

+# +
+
+
+ +[`IERC6909.allowance`](../interfaces#IERC6909-allowance-address-address-uint256-) override that includes the temporary allowance when looking up the current allowance. If +adding up the persistent and the temporary allowances result in an overflow, type(uint256).max is returned. + +
+
+ + + +
+
+

_temporaryAllowance(address owner, address spender) → uint256

+
+

internal

+# +
+
+
+ +Internal getter for the current temporary allowance that `spender` has over `owner` tokens. + +
+
+ + + +
+
+

temporaryApprove(address spender, uint256 value) → bool

+
+

public

+# +
+
+
+ +Alternative to [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) that sets a `value` amount of tokens as the temporary allowance of `spender` over +the caller's tokens. + +Returns a boolean value indicating whether the operation succeeded. + +Requirements: +- `spender` cannot be the zero address. + +Does NOT emit an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_temporaryApprove(address owner, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Sets `value` as the temporary allowance of `spender` over the `owner`'s tokens. + +This internal function is equivalent to `temporaryApprove`, and can be used to e.g. set automatic allowances +for certain subsystems, etc. + +Requirements: +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + +Does NOT emit an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_spendAllowance(address owner, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +[`ERC20._spendAllowance`](#ERC20-_spendAllowance-address-address-uint256-) override that consumes the temporary allowance (if any) before eventually falling back +to consuming the persistent allowance. + +This function skips calling `super._spendAllowance` if the temporary allowance +is enough to cover the spending. + + +
+
+ + + +
+ +## `ERC1363Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/utils/ERC1363Utils.sol"; +``` + +Library that provides common ERC-1363 utility functions. + +See [ERC-1363](https://eips.ethereum.org/EIPS/eip-1363). + +
+

Functions

+
+- [checkOnERC1363TransferReceived(operator, from, to, value, data)](#ERC1363Utils-checkOnERC1363TransferReceived-address-address-address-uint256-bytes-) +- [checkOnERC1363ApprovalReceived(operator, spender, value, data)](#ERC1363Utils-checkOnERC1363ApprovalReceived-address-address-uint256-bytes-) +
+
+ +
+

Errors

+
+- [ERC1363InvalidReceiver(receiver)](#ERC1363Utils-ERC1363InvalidReceiver-address-) +- [ERC1363InvalidSpender(spender)](#ERC1363Utils-ERC1363InvalidSpender-address-) +
+
+ + + +
+
+

checkOnERC1363TransferReceived(address operator, address from, address to, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Performs a call to [`IERC1363Receiver.onTransferReceived`](../interfaces#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) on a target address. + +Requirements: + +- The target has code (i.e. is a contract). +- The target `to` must implement the [`IERC1363Receiver`](../interfaces#IERC1363Receiver) interface. +- The target must return the [`IERC1363Receiver.onTransferReceived`](../interfaces#IERC1363Receiver-onTransferReceived-address-address-uint256-bytes-) selector to accept the transfer. + +
+
+ + + +
+
+

checkOnERC1363ApprovalReceived(address operator, address spender, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Performs a call to [`IERC1363Spender.onApprovalReceived`](../interfaces#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) on a target address. + +Requirements: + +- The target has code (i.e. is a contract). +- The target `spender` must implement the [`IERC1363Spender`](../interfaces#IERC1363Spender) interface. +- The target must return the [`IERC1363Spender.onApprovalReceived`](../interfaces#IERC1363Spender-onApprovalReceived-address-uint256-bytes-) selector to accept the approval. + +
+
+ + + +
+
+

ERC1363InvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `receiver`. Used in transfers. + +
+
+ + + +
+
+

ERC1363InvalidSpender(address spender)

+
+

error

+# +
+
+
+ +Indicates a failure with the token `spender`. Used in approvals. + +
+
+ + + +
+ +## `SafeERC20` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +``` + +Wrappers around ERC-20 operations that throw on failure (when the token +contract returns false). Tokens that return no value (and instead revert or +throw on failure) are also supported, non-reverting calls are assumed to be +successful. +To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, +which allows you to call the safe operations as `token.safeTransfer(...)`, etc. + +
+

Functions

+
+- [safeTransfer(token, to, value)](#SafeERC20-safeTransfer-contract-IERC20-address-uint256-) +- [safeTransferFrom(token, from, to, value)](#SafeERC20-safeTransferFrom-contract-IERC20-address-address-uint256-) +- [trySafeTransfer(token, to, value)](#SafeERC20-trySafeTransfer-contract-IERC20-address-uint256-) +- [trySafeTransferFrom(token, from, to, value)](#SafeERC20-trySafeTransferFrom-contract-IERC20-address-address-uint256-) +- [safeIncreaseAllowance(token, spender, value)](#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-) +- [safeDecreaseAllowance(token, spender, requestedDecrease)](#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-) +- [forceApprove(token, spender, value)](#SafeERC20-forceApprove-contract-IERC20-address-uint256-) +- [transferAndCallRelaxed(token, to, value, data)](#SafeERC20-transferAndCallRelaxed-contract-IERC1363-address-uint256-bytes-) +- [transferFromAndCallRelaxed(token, from, to, value, data)](#SafeERC20-transferFromAndCallRelaxed-contract-IERC1363-address-address-uint256-bytes-) +- [approveAndCallRelaxed(token, to, value, data)](#SafeERC20-approveAndCallRelaxed-contract-IERC1363-address-uint256-bytes-) +
+
+ +
+

Errors

+
+- [SafeERC20FailedOperation(token)](#SafeERC20-SafeERC20FailedOperation-address-) +- [SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease)](#SafeERC20-SafeERC20FailedDecreaseAllowance-address-uint256-uint256-) +
+
+ + + +
+
+

safeTransfer(contract IERC20 token, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, +non-reverting calls are assumed to be successful. + +
+
+ + + +
+
+

safeTransferFrom(contract IERC20 token, address from, address to, uint256 value)

+
+

internal

+# +
+
+
+ +Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the +calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. + +
+
+ + + +
+
+

trySafeTransfer(contract IERC20 token, address to, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Variant of [`SafeERC20.safeTransfer`](#SafeERC20-safeTransfer-contract-IERC20-address-uint256-) that returns a bool instead of reverting if the operation is not successful. + +
+
+ + + +
+
+

trySafeTransferFrom(contract IERC20 token, address from, address to, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Variant of [`ERC1155.safeTransferFrom`](/contracts/5.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) that returns a bool instead of reverting if the operation is not successful. + +
+
+ + + +
+
+

safeIncreaseAllowance(contract IERC20 token, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, +non-reverting calls are assumed to be successful. + + +If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" +smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using +this function. Performing a [`SafeERC20.safeIncreaseAllowance`](#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-) or [`SafeERC20.safeDecreaseAllowance`](#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-) operation on a token contract +that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. + + +
+
+ + + +
+
+

safeDecreaseAllowance(contract IERC20 token, address spender, uint256 requestedDecrease)

+
+

internal

+# +
+
+
+ +Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no +value, non-reverting calls are assumed to be successful. + + +If the token implements ERC-7674 (ERC-20 with temporary allowance), and if the "client" +smart contract uses ERC-7674 to set temporary allowances, then the "client" smart contract should avoid using +this function. Performing a [`SafeERC20.safeIncreaseAllowance`](#SafeERC20-safeIncreaseAllowance-contract-IERC20-address-uint256-) or [`SafeERC20.safeDecreaseAllowance`](#SafeERC20-safeDecreaseAllowance-contract-IERC20-address-uint256-) operation on a token contract +that has a non-zero temporary allowance (for that particular owner-spender) will result in unexpected behavior. + + +
+
+ + + +
+
+

forceApprove(contract IERC20 token, address spender, uint256 value)

+
+

internal

+# +
+
+
+ +Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, +non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval +to be set to zero before setting it to a non-zero value, such as USDT. + + +If the token implements ERC-7674, this function will not modify any temporary allowance. This function +only sets the "standard" allowance. Any temporary allowance will remain active, in addition to the value being +set here. + + +
+
+ + + +
+
+

transferAndCallRelaxed(contract IERC1363 token, address to, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Performs an [`ERC1363`](#ERC1363) transferAndCall, with a fallback to the simple [`ERC20`](#ERC20) transfer if the target has no +code. This can be used to implement an [`ERC721`](/contracts/5.x/api/token/ERC721#ERC721)-like safe transfer that relies on [`ERC1363`](#ERC1363) checks when +targeting contracts. + +Reverts if the returned value is other than `true`. + +
+
+ + + +
+
+

transferFromAndCallRelaxed(contract IERC1363 token, address from, address to, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Performs an [`ERC1363`](#ERC1363) transferFromAndCall, with a fallback to the simple [`ERC20`](#ERC20) transferFrom if the target +has no code. This can be used to implement an [`ERC721`](/contracts/5.x/api/token/ERC721#ERC721)-like safe transfer that relies on [`ERC1363`](#ERC1363) checks when +targeting contracts. + +Reverts if the returned value is other than `true`. + +
+
+ + + +
+
+

approveAndCallRelaxed(contract IERC1363 token, address to, uint256 value, bytes data)

+
+

internal

+# +
+
+
+ +Performs an [`ERC1363`](#ERC1363) approveAndCall, with a fallback to the simple [`ERC20`](#ERC20) approve if the target has no +code. This can be used to implement an [`ERC721`](/contracts/5.x/api/token/ERC721#ERC721)-like safe transfer that rely on [`ERC1363`](#ERC1363) checks when +targeting contracts. + + +When the recipient address (`to`) has no code (i.e. is an EOA), this function behaves as [`SafeERC20.forceApprove`](#SafeERC20-forceApprove-contract-IERC20-address-uint256-). +Oppositely, when the recipient address (`to`) has code, this function only attempts to call [`ERC1363.approveAndCall`](#ERC1363-approveAndCall-address-uint256-bytes-) +once without retrying, and relies on the returned value to be true. + + +Reverts if the returned value is other than `true`. + +
+
+ + + +
+
+

SafeERC20FailedOperation(address token)

+
+

error

+# +
+
+
+ +An operation with an ERC-20 token failed. + +
+
+ + + +
+
+

SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease)

+
+

error

+# +
+
+
+ +Indicates a failed `decreaseAllowance` request. + +
+
diff --git a/docs/content/contracts/5.x/api/token/ERC6909.mdx b/docs/content/contracts/5.x/api/token/ERC6909.mdx new file mode 100644 index 00000000..9d96396b --- /dev/null +++ b/docs/content/contracts/5.x/api/token/ERC6909.mdx @@ -0,0 +1,1054 @@ +--- +title: "ERC6909" +description: "Smart contract ERC6909 utilities and implementations" +--- + +This set of interfaces and contracts are all related to the [ERC-6909 Minimal Multi-Token Interface](https://eips.ethereum.org/EIPS/eip-6909). + +The ERC consists of four interfaces which fulfill different roles--the interfaces are as follows: + +1. [`IERC6909`](../interfaces#IERC6909): Base interface for a vanilla ERC6909 token. +2. [`IERC6909ContentURI`](../interfaces#IERC6909ContentURI): Extends the base interface and adds content URI (contract and token level) functionality. +3. [`IERC6909Metadata`](../interfaces#IERC6909Metadata): Extends the base interface and adds metadata functionality, which exposes a name, symbol, and decimals for each token id. +4. [`IERC6909TokenSupply`](../interfaces#IERC6909TokenSupply): Extends the base interface and adds total supply functionality for each token id. + +Implementations are provided for each of the 4 interfaces defined in the ERC. + +## Core + +[`ERC6909`](#ERC6909) + +## Extensions + +[`ERC6909ContentURI`](#ERC6909ContentURI) + +[`ERC6909Metadata`](#ERC6909Metadata) + +[`ERC6909TokenSupply`](#ERC6909TokenSupply) + + + +
+ +## `ERC6909` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC6909/ERC6909.sol"; +``` + +Implementation of ERC-6909. +See https://eips.ethereum.org/EIPS/eip-6909 + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC6909-supportsInterface-bytes4-) +- [balanceOf(owner, id)](#ERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#ERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#ERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#ERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#ERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#ERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#ERC6909-transferFrom-address-address-uint256-uint256-) +- [_mint(to, id, amount)](#ERC6909-_mint-address-uint256-uint256-) +- [_transfer(from, to, id, amount)](#ERC6909-_transfer-address-address-uint256-uint256-) +- [_burn(from, id, amount)](#ERC6909-_burn-address-uint256-uint256-) +- [_update(from, to, id, amount)](#ERC6909-_update-address-address-uint256-uint256-) +- [_approve(owner, spender, id, amount)](#ERC6909-_approve-address-address-uint256-uint256-) +- [_setOperator(owner, spender, approved)](#ERC6909-_setOperator-address-address-bool-) +- [_spendAllowance(owner, spender, id, amount)](#ERC6909-_spendAllowance-address-address-uint256-uint256-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [ERC6909InsufficientBalance(sender, balance, needed, id)](#ERC6909-ERC6909InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC6909InsufficientAllowance(spender, allowance, needed, id)](#ERC6909-ERC6909InsufficientAllowance-address-uint256-uint256-uint256-) +- [ERC6909InvalidApprover(approver)](#ERC6909-ERC6909InvalidApprover-address-) +- [ERC6909InvalidReceiver(receiver)](#ERC6909-ERC6909InvalidReceiver-address-) +- [ERC6909InvalidSender(sender)](#ERC6909-ERC6909InvalidSender-address-) +- [ERC6909InvalidSpender(spender)](#ERC6909-ERC6909InvalidSpender-address-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

balanceOf(address owner, uint256 id) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of tokens of type `id` owned by `owner`. + +
+
+ + + +
+
+

allowance(address owner, address spender, uint256 id) → uint256

+
+

public

+# +
+
+
+ +Returns the amount of tokens of type `id` that `spender` is allowed to spend on behalf of `owner`. + + +Does not include operator allowances. + + +
+
+ + + +
+
+

isOperator(address owner, address spender) → bool

+
+

public

+# +
+
+
+ +Returns true if `spender` is set as an operator for `owner`. + +
+
+ + + +
+
+

approve(address spender, uint256 id, uint256 amount) → bool

+
+

public

+# +
+
+
+ +Sets an approval to `spender` for `amount` of tokens of type `id` from the caller's tokens. An `amount` of +`type(uint256).max` signifies an unlimited approval. + +Must return true. + +
+
+ + + +
+
+

setOperator(address spender, bool approved) → bool

+
+

public

+# +
+
+
+ +Grants or revokes unlimited transfer permission of any token id to `spender` for the caller's tokens. + +Must return true. + +
+
+ + + +
+
+

transfer(address receiver, uint256 id, uint256 amount) → bool

+
+

public

+# +
+
+
+ +Transfers `amount` of token type `id` from the caller's account to `receiver`. + +Must return true. + +
+
+ + + +
+
+

transferFrom(address sender, address receiver, uint256 id, uint256 amount) → bool

+
+

public

+# +
+
+
+ +Transfers `amount` of token type `id` from `sender` to `receiver`. + +Must return true. + +
+
+ + + +
+
+

_mint(address to, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Creates `amount` of token `id` and assigns them to `account`, by transferring it from address(0). +Relies on the `_update` mechanism. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event with `from` set to the zero address. + + +This function is not virtual, [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) should be overridden instead. + + +
+
+ + + +
+
+

_transfer(address from, address to, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Moves `amount` of token `id` from `from` to `to` without checking for approvals. This function verifies +that neither the sender nor the receiver are address(0), which means it cannot mint or burn tokens. +Relies on the `_update` mechanism. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + + +This function is not virtual, [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) should be overridden instead. + + +
+
+ + + +
+
+

_burn(address from, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Destroys a `amount` of token `id` from `account`. +Relies on the `_update` mechanism. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event with `to` set to the zero address. + + +This function is not virtual, [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) should be overridden instead + + +
+
+ + + +
+
+

_update(address from, address to, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Transfers `amount` of token `id` from `from` to `to`, or alternatively mints (or burns) if `from` +(or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding +this function. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_approve(address owner, address spender, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Sets `amount` as the allowance of `spender` over the `owner`'s `id` tokens. + +This internal function is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain +subsystems, etc. + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + +
+
+ + + +
+
+

_setOperator(address owner, address spender, bool approved)

+
+

internal

+# +
+
+
+ +Approve `spender` to operate on all of `owner`'s tokens + +This internal function is equivalent to `setOperator`, and can be used to e.g. set automatic allowances for +certain subsystems, etc. + +Emits an [`IERC6909.OperatorSet`](../interfaces#IERC6909-OperatorSet-address-address-bool-) event. + +Requirements: + +- `owner` cannot be the zero address. +- `spender` cannot be the zero address. + +
+
+ + + +
+
+

_spendAllowance(address owner, address spender, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Updates `owner`'s allowance for `spender` based on spent `amount`. + +Does not update the allowance value in case of infinite allowance. +Revert if not enough allowance is available. + +Does not emit an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

ERC6909InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 id)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC6909InsufficientAllowance(address spender, uint256 allowance, uint256 needed, uint256 id)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC6909InvalidApprover(address approver)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC6909InvalidReceiver(address receiver)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC6909InvalidSender(address sender)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

ERC6909InvalidSpender(address spender)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `ERC6909ContentURI` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC6909/extensions/ERC6909ContentURI.sol"; +``` + +Implementation of the Content URI extension defined in ERC6909. + +
+

Functions

+
+- [contractURI()](#ERC6909ContentURI-contractURI--) +- [tokenURI(id)](#ERC6909ContentURI-tokenURI-uint256-) +- [_setContractURI(newContractURI)](#ERC6909ContentURI-_setContractURI-string-) +- [_setTokenURI(id, newTokenURI)](#ERC6909ContentURI-_setTokenURI-uint256-string-) +#### IERC6909ContentURI [!toc] +#### ERC6909 [!toc] +- [supportsInterface(interfaceId)](#ERC6909-supportsInterface-bytes4-) +- [balanceOf(owner, id)](#ERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#ERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#ERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#ERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#ERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#ERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#ERC6909-transferFrom-address-address-uint256-uint256-) +- [_mint(to, id, amount)](#ERC6909-_mint-address-uint256-uint256-) +- [_transfer(from, to, id, amount)](#ERC6909-_transfer-address-address-uint256-uint256-) +- [_burn(from, id, amount)](#ERC6909-_burn-address-uint256-uint256-) +- [_update(from, to, id, amount)](#ERC6909-_update-address-address-uint256-uint256-) +- [_approve(owner, spender, id, amount)](#ERC6909-_approve-address-address-uint256-uint256-) +- [_setOperator(owner, spender, approved)](#ERC6909-_setOperator-address-address-bool-) +- [_spendAllowance(owner, spender, id, amount)](#ERC6909-_spendAllowance-address-address-uint256-uint256-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [ContractURIUpdated()](#ERC6909ContentURI-ContractURIUpdated--) +- [URI(value, id)](#ERC6909ContentURI-URI-string-uint256-) +#### IERC6909ContentURI [!toc] +#### ERC6909 [!toc] +#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### IERC6909ContentURI [!toc] +#### ERC6909 [!toc] +- [ERC6909InsufficientBalance(sender, balance, needed, id)](#ERC6909-ERC6909InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC6909InsufficientAllowance(spender, allowance, needed, id)](#ERC6909-ERC6909InsufficientAllowance-address-uint256-uint256-uint256-) +- [ERC6909InvalidApprover(approver)](#ERC6909-ERC6909InvalidApprover-address-) +- [ERC6909InvalidReceiver(receiver)](#ERC6909-ERC6909InvalidReceiver-address-) +- [ERC6909InvalidSender(sender)](#ERC6909-ERC6909InvalidSender-address-) +- [ERC6909InvalidSpender(spender)](#ERC6909-ERC6909InvalidSpender-address-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

contractURI() → string

+
+

public

+# +
+
+
+ +Returns URI for the contract. + +
+
+ + + +
+
+

tokenURI(uint256 id) → string

+
+

public

+# +
+
+
+ +Returns the URI for the token of type `id`. + +
+
+ + + +
+
+

_setContractURI(string newContractURI)

+
+

internal

+# +
+
+
+ +Sets the [`IERC6909ContentURI.contractURI`](../interfaces#IERC6909ContentURI-contractURI--) for the contract. + +Emits a [`ERC6909ContentURI.ContractURIUpdated`](#ERC6909ContentURI-ContractURIUpdated--) event. + +
+
+ + + +
+
+

_setTokenURI(uint256 id, string newTokenURI)

+
+

internal

+# +
+
+
+ +Sets the [`IERC6909ContentURI.tokenURI`](../interfaces#IERC6909ContentURI-tokenURI-uint256-) for a given token of type `id`. + +Emits a [`IERC1155.URI`](/contracts/5.x/api/token/ERC1155#IERC1155-URI-string-uint256-) event. + +
+
+ + + +
+
+

ContractURIUpdated()

+
+

event

+# +
+
+ +
+ +Event emitted when the contract URI is changed. See [ERC-7572](https://eips.ethereum.org/EIPS/eip-7572) for details. + +
+
+ + +
+
+

URI(string value, uint256 indexed id)

+
+

event

+# +
+
+ +
+ +See [`IERC1155.URI`](/contracts/5.x/api/token/ERC1155#IERC1155-URI-string-uint256-) + +
+
+ + + +
+ +## `ERC6909Metadata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC6909/extensions/ERC6909Metadata.sol"; +``` + +Implementation of the Metadata extension defined in ERC6909. Exposes the name, symbol, and decimals of each token id. + +
+

Functions

+
+- [name(id)](#ERC6909Metadata-name-uint256-) +- [symbol(id)](#ERC6909Metadata-symbol-uint256-) +- [decimals(id)](#ERC6909Metadata-decimals-uint256-) +- [_setName(id, newName)](#ERC6909Metadata-_setName-uint256-string-) +- [_setSymbol(id, newSymbol)](#ERC6909Metadata-_setSymbol-uint256-string-) +- [_setDecimals(id, newDecimals)](#ERC6909Metadata-_setDecimals-uint256-uint8-) +#### IERC6909Metadata [!toc] +#### ERC6909 [!toc] +- [supportsInterface(interfaceId)](#ERC6909-supportsInterface-bytes4-) +- [balanceOf(owner, id)](#ERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#ERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#ERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#ERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#ERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#ERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#ERC6909-transferFrom-address-address-uint256-uint256-) +- [_mint(to, id, amount)](#ERC6909-_mint-address-uint256-uint256-) +- [_transfer(from, to, id, amount)](#ERC6909-_transfer-address-address-uint256-uint256-) +- [_burn(from, id, amount)](#ERC6909-_burn-address-uint256-uint256-) +- [_update(from, to, id, amount)](#ERC6909-_update-address-address-uint256-uint256-) +- [_approve(owner, spender, id, amount)](#ERC6909-_approve-address-address-uint256-uint256-) +- [_setOperator(owner, spender, approved)](#ERC6909-_setOperator-address-address-bool-) +- [_spendAllowance(owner, spender, id, amount)](#ERC6909-_spendAllowance-address-address-uint256-uint256-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+- [ERC6909NameUpdated(id, newName)](#ERC6909Metadata-ERC6909NameUpdated-uint256-string-) +- [ERC6909SymbolUpdated(id, newSymbol)](#ERC6909Metadata-ERC6909SymbolUpdated-uint256-string-) +- [ERC6909DecimalsUpdated(id, newDecimals)](#ERC6909Metadata-ERC6909DecimalsUpdated-uint256-uint8-) +#### IERC6909Metadata [!toc] +#### ERC6909 [!toc] +#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### IERC6909Metadata [!toc] +#### ERC6909 [!toc] +- [ERC6909InsufficientBalance(sender, balance, needed, id)](#ERC6909-ERC6909InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC6909InsufficientAllowance(spender, allowance, needed, id)](#ERC6909-ERC6909InsufficientAllowance-address-uint256-uint256-uint256-) +- [ERC6909InvalidApprover(approver)](#ERC6909-ERC6909InvalidApprover-address-) +- [ERC6909InvalidReceiver(receiver)](#ERC6909-ERC6909InvalidReceiver-address-) +- [ERC6909InvalidSender(sender)](#ERC6909-ERC6909InvalidSender-address-) +- [ERC6909InvalidSpender(spender)](#ERC6909-ERC6909InvalidSpender-address-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

name(uint256 id) → string

+
+

public

+# +
+
+
+ +Returns the name of the token of type `id`. + +
+
+ + + +
+
+

symbol(uint256 id) → string

+
+

public

+# +
+
+
+ +Returns the ticker symbol of the token of type `id`. + +
+
+ + + +
+
+

decimals(uint256 id) → uint8

+
+

public

+# +
+
+
+ +Returns the number of decimals for the token of type `id`. + +
+
+ + + +
+
+

_setName(uint256 id, string newName)

+
+

internal

+# +
+
+
+ +Sets the `name` for a given token of type `id`. + +Emits an [`ERC6909Metadata.ERC6909NameUpdated`](#ERC6909Metadata-ERC6909NameUpdated-uint256-string-) event. + +
+
+ + + +
+
+

_setSymbol(uint256 id, string newSymbol)

+
+

internal

+# +
+
+
+ +Sets the `symbol` for a given token of type `id`. + +Emits an [`ERC6909Metadata.ERC6909SymbolUpdated`](#ERC6909Metadata-ERC6909SymbolUpdated-uint256-string-) event. + +
+
+ + + +
+
+

_setDecimals(uint256 id, uint8 newDecimals)

+
+

internal

+# +
+
+
+ +Sets the `decimals` for a given token of type `id`. + +Emits an [`ERC6909Metadata.ERC6909DecimalsUpdated`](#ERC6909Metadata-ERC6909DecimalsUpdated-uint256-uint8-) event. + +
+
+ + + +
+
+

ERC6909NameUpdated(uint256 indexed id, string newName)

+
+

event

+# +
+
+ +
+ +The name of the token of type `id` was updated to `newName`. + +
+
+ + +
+
+

ERC6909SymbolUpdated(uint256 indexed id, string newSymbol)

+
+

event

+# +
+
+ +
+ +The symbol for the token of type `id` was updated to `newSymbol`. + +
+
+ + +
+
+

ERC6909DecimalsUpdated(uint256 indexed id, uint8 newDecimals)

+
+

event

+# +
+
+ +
+ +The decimals value for token of type `id` was updated to `newDecimals`. + +
+
+ + + +
+ +## `ERC6909TokenSupply` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC6909/extensions/ERC6909TokenSupply.sol"; +``` + +Implementation of the Token Supply extension defined in ERC6909. +Tracks the total supply of each token id individually. + +
+

Functions

+
+- [totalSupply(id)](#ERC6909TokenSupply-totalSupply-uint256-) +- [_update(from, to, id, amount)](#ERC6909TokenSupply-_update-address-address-uint256-uint256-) +#### IERC6909TokenSupply [!toc] +#### ERC6909 [!toc] +- [supportsInterface(interfaceId)](#ERC6909-supportsInterface-bytes4-) +- [balanceOf(owner, id)](#ERC6909-balanceOf-address-uint256-) +- [allowance(owner, spender, id)](#ERC6909-allowance-address-address-uint256-) +- [isOperator(owner, spender)](#ERC6909-isOperator-address-address-) +- [approve(spender, id, amount)](#ERC6909-approve-address-uint256-uint256-) +- [setOperator(spender, approved)](#ERC6909-setOperator-address-bool-) +- [transfer(receiver, id, amount)](#ERC6909-transfer-address-uint256-uint256-) +- [transferFrom(sender, receiver, id, amount)](#ERC6909-transferFrom-address-address-uint256-uint256-) +- [_mint(to, id, amount)](#ERC6909-_mint-address-uint256-uint256-) +- [_transfer(from, to, id, amount)](#ERC6909-_transfer-address-address-uint256-uint256-) +- [_burn(from, id, amount)](#ERC6909-_burn-address-uint256-uint256-) +- [_approve(owner, spender, id, amount)](#ERC6909-_approve-address-address-uint256-uint256-) +- [_setOperator(owner, spender, approved)](#ERC6909-_setOperator-address-address-bool-) +- [_spendAllowance(owner, spender, id, amount)](#ERC6909-_spendAllowance-address-address-uint256-uint256-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC6909TokenSupply [!toc] +#### ERC6909 [!toc] +#### IERC6909 [!toc] +- [Approval(owner, spender, id, amount)](#IERC6909-Approval-address-address-uint256-uint256-) +- [OperatorSet(owner, spender, approved)](#IERC6909-OperatorSet-address-address-bool-) +- [Transfer(caller, sender, receiver, id, amount)](#IERC6909-Transfer-address-address-address-uint256-uint256-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### IERC6909TokenSupply [!toc] +#### ERC6909 [!toc] +- [ERC6909InsufficientBalance(sender, balance, needed, id)](#ERC6909-ERC6909InsufficientBalance-address-uint256-uint256-uint256-) +- [ERC6909InsufficientAllowance(spender, allowance, needed, id)](#ERC6909-ERC6909InsufficientAllowance-address-uint256-uint256-uint256-) +- [ERC6909InvalidApprover(approver)](#ERC6909-ERC6909InvalidApprover-address-) +- [ERC6909InvalidReceiver(receiver)](#ERC6909-ERC6909InvalidReceiver-address-) +- [ERC6909InvalidSender(sender)](#ERC6909-ERC6909InvalidSender-address-) +- [ERC6909InvalidSpender(spender)](#ERC6909-ERC6909InvalidSpender-address-) +#### IERC6909 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

totalSupply(uint256 id) → uint256

+
+

public

+# +
+
+
+ +Returns the total supply of the token of type `id`. + +
+
+ + + +
+
+

_update(address from, address to, uint256 id, uint256 amount)

+
+

internal

+# +
+
+
+ +Override the `_update` function to update the total supply of each token id as necessary. + +
+
diff --git a/docs/content/contracts/5.x/api/token/ERC721.mdx b/docs/content/contracts/5.x/api/token/ERC721.mdx new file mode 100644 index 00000000..3ed1321e --- /dev/null +++ b/docs/content/contracts/5.x/api/token/ERC721.mdx @@ -0,0 +1,3140 @@ +--- +title: "ERC721" +description: "Smart contract ERC721 utilities and implementations" +--- + +This set of interfaces, contracts, and utilities is all related to the [ERC-721 Non-Fungible Token Standard](https://eips.ethereum.org/EIPS/eip-721). + + +For a walk through on how to create an ERC-721 token read our [ERC-721 guide](/contracts/5.x/erc721). + + +The ERC specifies four interfaces: + +* [`IERC721`](#IERC721): Core functionality required in all compliant implementation. +* [`IERC721Metadata`](#IERC721Metadata): Optional extension that adds name, symbol, and token URI, almost always included. +* [`IERC721Enumerable`](#IERC721Enumerable): Optional extension that allows enumerating the tokens on chain, often not included since it requires large gas overhead. +* [`IERC721Receiver`](#IERC721Receiver): An interface that must be implemented by contracts if they want to accept tokens through `safeTransferFrom`. + +OpenZeppelin Contracts provides implementations of all four interfaces: + +* [`ERC721`](#ERC721): The core and metadata extensions, with a base URI mechanism. +* [`ERC721Enumerable`](#ERC721Enumerable): The enumerable extension. +* [`ERC721Holder`](#ERC721Holder): A bare bones implementation of the receiver interface. + +Additionally there are a few of other extensions: + +* [`ERC721Consecutive`](#ERC721Consecutive): An implementation of [ERC-2309](https://eips.ethereum.org/EIPS/eip-2309) for minting batches of tokens during construction, in accordance with ERC-721. +* [`ERC721URIStorage`](#ERC721URIStorage): A more flexible but more expensive way of storing metadata. +* [`ERC721Votes`](#ERC721Votes): Support for voting and vote delegation. +* [`ERC721Royalty`](#ERC721Royalty): A way to signal royalty information following ERC-2981. +* [`ERC721Pausable`](#ERC721Pausable): A primitive to pause contract operation. +* [`ERC721Burnable`](#ERC721Burnable): A way for token holders to burn their own tokens. +* [`ERC721Wrapper`](#ERC721Wrapper): Wrapper to create an ERC-721 backed by another ERC-721, with deposit and withdraw methods. Useful in conjunction with [`ERC721Votes`](#ERC721Votes). + + +This core set of contracts is designed to be unopinionated, allowing developers to access the internal functions in ERC-721 (such as [`_mint`](#ERC721-_mint-address-uint256-)) and expose them as external functions in the way they prefer. + + +## Core + +[`IERC721`](#IERC721) + +[`IERC721Metadata`](#IERC721Metadata) + +[`IERC721Enumerable`](#IERC721Enumerable) + +[`ERC721`](#ERC721) + +[`ERC721Enumerable`](#ERC721Enumerable) + +[`IERC721Receiver`](#IERC721Receiver) + +## Extensions + +[`ERC721Pausable`](#ERC721Pausable) + +[`ERC721Burnable`](#ERC721Burnable) + +[`ERC721Consecutive`](#ERC721Consecutive) + +[`ERC721URIStorage`](#ERC721URIStorage) + +[`ERC721Votes`](#ERC721Votes) + +[`ERC721Royalty`](#ERC721Royalty) + +[`ERC721Wrapper`](#ERC721Wrapper) + +## Utilities + +[`ERC721Holder`](#ERC721Holder) + +[`ERC721Utils`](#ERC721Utils) + + + +
+ +## `ERC721` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +``` + +Implementation of [ERC-721](https://eips.ethereum.org/EIPS/eip-721) Non-Fungible Token Standard, including +the Metadata extension, but not including the Enumerable extension, which is available separately as +[`ERC721Enumerable`](#ERC721Enumerable). + +
+

Functions

+
+- [constructor(name_, symbol_)](#ERC721-constructor-string-string-) +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_update(to, tokenId, auth)](#ERC721-_update-address-uint256-address-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(string name_, string symbol_)

+
+

internal

+# +
+
+
+ +Initializes the contract by setting a `name` and a `symbol` to the token collection. + +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

balanceOf(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns the number of tokens in ``owner``'s account. + +
+
+ + + +
+
+

ownerOf(uint256 tokenId) → address

+
+

public

+# +
+
+
+ +Returns the owner of the `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

name() → string

+
+

public

+# +
+
+
+ +Returns the token collection name. + +
+
+ + + +
+
+

symbol() → string

+
+

public

+# +
+
+
+ +Returns the token collection symbol. + +
+
+ + + +
+
+

tokenURI(uint256 tokenId) → string

+
+

public

+# +
+
+
+ +Returns the Uniform Resource Identifier (URI) for `tokenId` token. + +
+
+ + + +
+
+

_baseURI() → string

+
+

internal

+# +
+
+
+ +Base URI for computing [`IERC6909ContentURI.tokenURI`](../interfaces#IERC6909ContentURI-tokenURI-uint256-). If set, the resulting URI for each +token will be the concatenation of the `baseURI` and the `tokenId`. Empty +by default, can be overridden in child contracts. + +
+
+ + + +
+
+

approve(address to, uint256 tokenId)

+
+

public

+# +
+
+
+ +Gives permission to `to` to transfer `tokenId` token to another account. +The approval is cleared when the token is transferred. + +Only a single account can be approved at a time, so approving the zero address clears previous approvals. + +Requirements: + +- The caller must own the token or be an approved operator. +- `tokenId` must exist. + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

getApproved(uint256 tokenId) → address

+
+

public

+# +
+
+
+ +Returns the account approved for `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

public

+# +
+
+
+ +Approve or remove `operator` as an operator for the caller. +Operators can call [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-) or [`ERC1155.safeTransferFrom`](/contracts/5.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) for any token owned by the caller. + +Requirements: + +- The `operator` cannot be the address zero. + +Emits an [`IERC1155.ApprovalForAll`](/contracts/5.x/api/token/ERC1155#IERC1155-ApprovalForAll-address-address-bool-) event. + +
+
+ + + +
+
+

isApprovedForAll(address owner, address operator) → bool

+
+

public

+# +
+
+
+ +Returns if the `operator` is allowed to manage all of the assets of `owner`. + +See [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-) + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 tokenId)

+
+

public

+# +
+
+
+ +Transfers `tokenId` token from `from` to `to`. + + +Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721 +or else they may be permanently lost. Usage of [`ERC1155.safeTransferFrom`](/contracts/5.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) prevents loss, though the caller must +understand this adds an external call which potentially creates a reentrancy vulnerability. + + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId)

+
+

public

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC-721 protocol to prevent tokens from being forever locked. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must have been allowed to move this token by either [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or + [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon + a safe transfer. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId, bytes data)

+
+

public

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon + a safe transfer. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_ownerOf(uint256 tokenId) → address

+
+

internal

+# +
+
+
+ +Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + + +Any overrides to this function that add ownership of tokens not tracked by the +core ERC-721 logic MUST be matched with the use of [`ERC721._increaseBalance`](#ERC721-_increaseBalance-address-uint128-) to keep balances +consistent with ownership. The invariant to preserve is that for any address `a` the value returned by +`balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`. + + +
+
+ + + +
+
+

_getApproved(uint256 tokenId) → address

+
+

internal

+# +
+
+
+ +Returns the approved address for `tokenId`. Returns 0 if `tokenId` is not minted. + +
+
+ + + +
+
+

_isAuthorized(address owner, address spender, uint256 tokenId) → bool

+
+

internal

+# +
+
+
+ +Returns whether `spender` is allowed to manage `owner`'s tokens, or `tokenId` in +particular (ignoring whether it is owned by `owner`). + + +This function assumes that `owner` is the actual owner of `tokenId` and does not verify this +assumption. + + +
+
+ + + +
+
+

_checkAuthorized(address owner, address spender, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. +Reverts if: +- `spender` does not have approval from `owner` for `tokenId`. +- `spender` does not have approval to manage all of `owner`'s assets. + + +This function assumes that `owner` is the actual owner of `tokenId` and does not verify this +assumption. + + +
+
+ + + +
+
+

_increaseBalance(address account, uint128 value)

+
+

internal

+# +
+
+
+ +Unsafe write access to the balances, used by extensions that "mint" tokens using an [`ERC721.ownerOf`](#ERC721-ownerOf-uint256-) override. + + +the value is limited to type(uint128).max. This protect against _balance overflow. It is unrealistic that +a uint256 would ever overflow from increments when these increments are bounded to uint128 values. + + + +Increasing an account's balance using this function tends to be paired with an override of the +[`ERC721._ownerOf`](#ERC721-_ownerOf-uint256-) function to resolve the ownership of the corresponding tokens so that balances and ownership +remain consistent with one another. + + +
+
+ + + +
+
+

_update(address to, uint256 tokenId, address auth) → address

+
+

internal

+# +
+
+
+ +Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner +(or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + +The `auth` argument is optional. If the value passed is non 0, then this function will check that +`auth` is either the owner of the token, or approved to operate on the token (by the owner). + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + + +If overriding this function in a way that tracks balances, see also [`ERC721._increaseBalance`](#ERC721-_increaseBalance-address-uint128-). + + +
+
+ + + +
+
+

_mint(address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Mints `tokenId` and transfers it to `to`. + + +Usage of this method is discouraged, use [`ERC721._safeMint`](#ERC721-_safeMint-address-uint256-bytes-) whenever possible + + +Requirements: + +- `tokenId` must not exist. +- `to` cannot be the zero address. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_safeMint(address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Mints `tokenId`, transfers it to `to` and checks for `to` acceptance. + +Requirements: + +- `tokenId` must not exist. +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon a safe transfer. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_safeMint(address to, uint256 tokenId, bytes data)

+
+

internal

+# +
+
+
+ +Same as [`_safeMint`](#ERC721-_safeMint-address-uint256-), with an additional `data` parameter which is +forwarded in [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) to contract recipients. + +
+
+ + + +
+
+

_burn(uint256 tokenId)

+
+

internal

+# +
+
+
+ +Destroys `tokenId`. +The approval is cleared when the token is burned. +This is an internal function that does not check if the sender is authorized to operate on the token. + +Requirements: + +- `tokenId` must exist. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_transfer(address from, address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Transfers `tokenId` from `from` to `to`. + As opposed to [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-), this imposes no restrictions on msg.sender. + +Requirements: + +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_safeTransfer(address from, address to, uint256 tokenId)

+
+

internal

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`, checking that contract recipients +are aware of the ERC-721 standard to prevent tokens from being forever locked. + +`data` is additional data, it has no specified format and it is sent in call to `to`. + +This internal function is like [`ERC1155.safeTransferFrom`](/contracts/5.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) in the sense that it invokes +[`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) on the receiver, and can be used to e.g. +implement alternative mechanisms to perform token transfer, such as signature-based. + +Requirements: + +- `tokenId` token must exist and be owned by `from`. +- `to` cannot be the zero address. +- `from` cannot be the zero address. +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon a safe transfer. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_safeTransfer(address from, address to, uint256 tokenId, bytes data)

+
+

internal

+# +
+
+
+ +Same as [`_safeTransfer`](#ERC721-_safeTransfer-address-address-uint256-), with an additional `data` parameter which is +forwarded in [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) to contract recipients. + +
+
+ + + +
+
+

_approve(address to, uint256 tokenId, address auth)

+
+

internal

+# +
+
+
+ +Approve `to` to operate on `tokenId` + +The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is +either the owner of the token, or approved to operate on all tokens held by this owner. + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + +
+
+ + + +
+
+

_approve(address to, uint256 tokenId, address auth, bool emitEvent)

+
+

internal

+# +
+
+
+ +Variant of `_approve` with an optional flag to enable or disable the [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. The event is not +emitted in the context of transfers. + +
+
+ + + +
+
+

_setApprovalForAll(address owner, address operator, bool approved)

+
+

internal

+# +
+
+
+ +Approve `operator` to operate on all of `owner` tokens + +Requirements: +- operator can't be the address zero. + +Emits an [`IERC1155.ApprovalForAll`](/contracts/5.x/api/token/ERC1155#IERC1155-ApprovalForAll-address-address-bool-) event. + +
+
+ + + +
+
+

_requireOwned(uint256 tokenId) → address

+
+

internal

+# +
+
+
+ +Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). +Returns the owner. + +Overrides to ownership logic should be done to [`ERC721._ownerOf`](#ERC721-_ownerOf-uint256-). + +
+
+ + + +
+ +## `IERC721` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +``` + +Required interface of an ERC-721 compliant contract. + +
+

Functions

+
+- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 [!toc] +
+
+ + + +
+
+

balanceOf(address owner) → uint256 balance

+
+

external

+# +
+
+
+ +Returns the number of tokens in ``owner``'s account. + +
+
+ + + +
+
+

ownerOf(uint256 tokenId) → address owner

+
+

external

+# +
+
+
+ +Returns the owner of the `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId, bytes data)

+
+

external

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon + a safe transfer. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

safeTransferFrom(address from, address to, uint256 tokenId)

+
+

external

+# +
+
+
+ +Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients +are aware of the ERC-721 protocol to prevent tokens from being forever locked. + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must exist and be owned by `from`. +- If the caller is not `from`, it must have been allowed to move this token by either [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or + [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). +- If `to` refers to a smart contract, it must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-), which is called upon + a safe transfer. + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

transferFrom(address from, address to, uint256 tokenId)

+
+

external

+# +
+
+
+ +Transfers `tokenId` token from `from` to `to`. + + +Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721 +or else they may be permanently lost. Usage of [`ERC1155.safeTransferFrom`](/contracts/5.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) prevents loss, though the caller must +understand this adds an external call which potentially creates a reentrancy vulnerability. + + +Requirements: + +- `from` cannot be the zero address. +- `to` cannot be the zero address. +- `tokenId` token must be owned by `from`. +- If the caller is not `from`, it must be approved to move this token by either [`IERC6909.approve`](../interfaces#IERC6909-approve-address-uint256-uint256-) or [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-). + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

approve(address to, uint256 tokenId)

+
+

external

+# +
+
+
+ +Gives permission to `to` to transfer `tokenId` token to another account. +The approval is cleared when the token is transferred. + +Only a single account can be approved at a time, so approving the zero address clears previous approvals. + +Requirements: + +- The caller must own the token or be an approved operator. +- `tokenId` must exist. + +Emits an [`IERC6909.Approval`](../interfaces#IERC6909-Approval-address-address-uint256-uint256-) event. + +
+
+ + + +
+
+

setApprovalForAll(address operator, bool approved)

+
+

external

+# +
+
+
+ +Approve or remove `operator` as an operator for the caller. +Operators can call [`IERC6909.transferFrom`](../interfaces#IERC6909-transferFrom-address-address-uint256-uint256-) or [`ERC1155.safeTransferFrom`](/contracts/5.x/api/token/ERC1155#ERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) for any token owned by the caller. + +Requirements: + +- The `operator` cannot be the address zero. + +Emits an [`IERC1155.ApprovalForAll`](/contracts/5.x/api/token/ERC1155#IERC1155-ApprovalForAll-address-address-bool-) event. + +
+
+ + + +
+
+

getApproved(uint256 tokenId) → address operator

+
+

external

+# +
+
+
+ +Returns the account approved for `tokenId` token. + +Requirements: + +- `tokenId` must exist. + +
+
+ + + +
+
+

isApprovedForAll(address owner, address operator) → bool

+
+

external

+# +
+
+
+ +Returns if the `operator` is allowed to manage all of the assets of `owner`. + +See [`ERC1155.setApprovalForAll`](/contracts/5.x/api/token/ERC1155#ERC1155-setApprovalForAll-address-bool-) + +
+
+ + + +
+
+

Transfer(address indexed from, address indexed to, uint256 indexed tokenId)

+
+

event

+# +
+
+ +
+ +Emitted when `tokenId` token is transferred from `from` to `to`. + +
+
+ + +
+
+

Approval(address indexed owner, address indexed approved, uint256 indexed tokenId)

+
+

event

+# +
+
+ +
+ +Emitted when `owner` enables `approved` to manage the `tokenId` token. + +
+
+ + +
+
+

ApprovalForAll(address indexed owner, address indexed operator, bool approved)

+
+

event

+# +
+
+ +
+ +Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets. + +
+
+ + + +
+ +## `IERC721Receiver` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +``` + +Interface for any contract that wants to support safeTransfers +from ERC-721 asset contracts. + +
+

Functions

+
+- [onERC721Received(operator, from, tokenId, data)](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) +
+
+ + + +
+
+

onERC721Received(address operator, address from, uint256 tokenId, bytes data) → bytes4

+
+

external

+# +
+
+
+ +Whenever an [`IERC721`](#IERC721) `tokenId` token is transferred to this contract via [`IERC721.safeTransferFrom`](#IERC721-safeTransferFrom-address-address-uint256-) +by `operator` from `from`, this function is called. + +It must return its Solidity selector to confirm the token transfer. +If any other value is returned or the interface is not implemented by the recipient, the transfer will be +reverted. + +The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`. + +
+
+ + + +
+ +## `ERC721Burnable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol"; +``` + +ERC-721 Token that can be burned (destroyed). + +
+

Functions

+
+- [burn(tokenId)](#ERC721Burnable-burn-uint256-) +#### ERC721 [!toc] +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_update(to, tokenId, auth)](#ERC721-_update-address-uint256-address-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

burn(uint256 tokenId)

+
+

public

+# +
+
+
+ +Burns `tokenId`. See [`ERC721._burn`](#ERC721-_burn-uint256-). + +Requirements: + +- The caller must own `tokenId` or be an approved operator. + +
+
+ + + +
+ +## `ERC721Consecutive` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Consecutive.sol"; +``` + +Implementation of the ERC-2309 "Consecutive Transfer Extension" as defined in +[ERC-2309](https://eips.ethereum.org/EIPS/eip-2309). + +This extension allows the minting of large batches of tokens, during contract construction only. For upgradeable +contracts this implies that batch minting is only available during proxy deployment, and not in subsequent upgrades. +These batches are limited to 5000 tokens at a time by default to accommodate off-chain indexers. + +Using this extension removes the ability to mint single tokens during contract construction. This ability is +regained after construction. During construction, only batch minting is allowed. + + +This extension does not call the [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) function for tokens minted in batch. Any logic added to this +function through overrides will not be triggered when tokens are minted in batch. You may want to also override +[`ERC721._increaseBalance`](#ERC721-_increaseBalance-address-uint128-) or [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-) to account for these mints. + + + +When overriding [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-), be careful about call ordering. [`ERC721.ownerOf`](#ERC721-ownerOf-uint256-) may return invalid +values during the [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-) execution if the super call is not called first. To be safe, execute the +super call before your custom logic. + + +
+

Functions

+
+- [_maxBatchSize()](#ERC721Consecutive-_maxBatchSize--) +- [_ownerOf(tokenId)](#ERC721Consecutive-_ownerOf-uint256-) +- [_mintConsecutive(to, batchSize)](#ERC721Consecutive-_mintConsecutive-address-uint96-) +- [_update(to, tokenId, auth)](#ERC721Consecutive-_update-address-uint256-address-) +- [_firstConsecutiveId()](#ERC721Consecutive-_firstConsecutiveId--) +#### ERC721 [!toc] +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IERC2309 [!toc] +
+
+ +
+

Events

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IERC2309 [!toc] +- [ConsecutiveTransfer(fromTokenId, toTokenId, fromAddress, toAddress)](#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-) +
+
+ +
+

Errors

+
+- [ERC721ForbiddenBatchMint()](#ERC721Consecutive-ERC721ForbiddenBatchMint--) +- [ERC721ExceededMaxBatchMint(batchSize, maxBatch)](#ERC721Consecutive-ERC721ExceededMaxBatchMint-uint256-uint256-) +- [ERC721ForbiddenMint()](#ERC721Consecutive-ERC721ForbiddenMint--) +- [ERC721ForbiddenBatchBurn()](#ERC721Consecutive-ERC721ForbiddenBatchBurn--) +#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +#### IERC2309 [!toc] +
+
+ + + +
+
+

_maxBatchSize() → uint96

+
+

internal

+# +
+
+
+ +Maximum size of a batch of consecutive tokens. This is designed to limit stress on off-chain indexing +services that have to record one entry per token, and have protections against "unreasonably large" batches of +tokens. + + +Overriding the default value of 5000 will not cause on-chain issues, but may result in the asset not being +correctly supported by off-chain indexing services (including marketplaces). + + +
+
+ + + +
+
+

_ownerOf(uint256 tokenId) → address

+
+

internal

+# +
+
+
+ +See [`ERC721._ownerOf`](#ERC721-_ownerOf-uint256-). Override that checks the sequential ownership structure for tokens that have +been minted as part of a batch, and not yet transferred. + +
+
+ + + +
+
+

_mintConsecutive(address to, uint96 batchSize) → uint96

+
+

internal

+# +
+
+
+ +Mint a batch of tokens of length `batchSize` for `to`. Returns the token id of the first token minted in the +batch; if `batchSize` is 0, returns the number of consecutive ids minted so far. + +Requirements: + +- `batchSize` must not be greater than [`ERC721Consecutive._maxBatchSize`](#ERC721Consecutive-_maxBatchSize--). +- The function is called in the constructor of the contract (directly or indirectly). + +CAUTION: Does not emit a `Transfer` event. This is ERC-721 compliant as long as it is done inside of the +constructor, which is enforced by this function. + +CAUTION: Does not invoke `onERC721Received` on the receiver. + +Emits a [`IERC2309.ConsecutiveTransfer`](../interfaces#IERC2309-ConsecutiveTransfer-uint256-uint256-address-address-) event. + +
+
+ + + +
+
+

_update(address to, uint256 tokenId, address auth) → address

+
+

internal

+# +
+
+
+ +See [`ERC721._update`](#ERC721-_update-address-uint256-address-). Override version that restricts normal minting to after construction. + + +Using [`ERC721Consecutive`](#ERC721Consecutive) prevents minting during construction in favor of [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-). +After construction, [`ERC721Consecutive._mintConsecutive`](#ERC721Consecutive-_mintConsecutive-address-uint96-) is no longer available and minting through [`ERC1155._update`](/contracts/5.x/api/token/ERC1155#ERC1155-_update-address-address-uint256---uint256---) becomes available. + + +
+
+ + + +
+
+

_firstConsecutiveId() → uint96

+
+

internal

+# +
+
+
+ +Used to offset the first token id in `_nextConsecutiveId` + +
+
+ + + +
+
+

ERC721ForbiddenBatchMint()

+
+

error

+# +
+
+
+ +Batch mint is restricted to the constructor. +Any batch mint not emitting the [`IERC721.Transfer`](#IERC721-Transfer-address-address-uint256-) event outside of the constructor +is non ERC-721 compliant. + +
+
+ + + +
+
+

ERC721ExceededMaxBatchMint(uint256 batchSize, uint256 maxBatch)

+
+

error

+# +
+
+
+ +Exceeds the max amount of mints per batch. + +
+
+ + + +
+
+

ERC721ForbiddenMint()

+
+

error

+# +
+
+
+ +Individual minting is not allowed. + +
+
+ + + +
+
+

ERC721ForbiddenBatchBurn()

+
+

error

+# +
+
+
+ +Batch burn is not supported. + +
+
+ + + +
+ +## `ERC721Enumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; +``` + +This implements an optional extension of [`ERC721`](#ERC721) defined in the ERC that adds enumerability +of all the token ids in the contract as well as all token ids owned by each account. + +CAUTION: [`ERC721`](#ERC721) extensions that implement custom `balanceOf` logic, such as [`ERC721Consecutive`](#ERC721Consecutive), +interfere with enumerability and should not be used together with [`ERC721Enumerable`](#ERC721Enumerable). + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC721Enumerable-supportsInterface-bytes4-) +- [tokenOfOwnerByIndex(owner, index)](#ERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) +- [totalSupply()](#ERC721Enumerable-totalSupply--) +- [tokenByIndex(index)](#ERC721Enumerable-tokenByIndex-uint256-) +- [_update(to, tokenId, auth)](#ERC721Enumerable-_update-address-uint256-address-) +- [_increaseBalance(account, amount)](#ERC721Enumerable-_increaseBalance-address-uint128-) +#### IERC721Enumerable [!toc] +#### ERC721 [!toc] +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC721Enumerable [!toc] +#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [ERC721OutOfBoundsIndex(owner, index)](#ERC721Enumerable-ERC721OutOfBoundsIndex-address-uint256-) +- [ERC721EnumerableForbiddenBatchMint()](#ERC721Enumerable-ERC721EnumerableForbiddenBatchMint--) +#### IERC721Enumerable [!toc] +#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

tokenOfOwnerByIndex(address owner, uint256 index) → uint256

+
+

public

+# +
+
+
+ +Returns a token ID owned by `owner` at a given `index` of its token list. +Use along with [`IERC6909.balanceOf`](../interfaces#IERC6909-balanceOf-address-uint256-) to enumerate all of ``owner``'s tokens. + +
+
+ + + +
+
+

totalSupply() → uint256

+
+

public

+# +
+
+
+ +Returns the total amount of tokens stored by the contract. + +
+
+ + + +
+
+

tokenByIndex(uint256 index) → uint256

+
+

public

+# +
+
+
+ +Returns a token ID at a given `index` of all the tokens stored by the contract. +Use along with [`IERC6909TokenSupply.totalSupply`](../interfaces#IERC6909TokenSupply-totalSupply-uint256-) to enumerate all tokens. + +
+
+ + + +
+
+

_update(address to, uint256 tokenId, address auth) → address

+
+

internal

+# +
+
+
+ +Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner +(or `to`) is the zero address. Returns the owner of the `tokenId` before the update. + +The `auth` argument is optional. If the value passed is non 0, then this function will check that +`auth` is either the owner of the token, or approved to operate on the token (by the owner). + +Emits a [`IERC6909.Transfer`](../interfaces#IERC6909-Transfer-address-address-address-uint256-uint256-) event. + + +If overriding this function in a way that tracks balances, see also [`ERC721._increaseBalance`](#ERC721-_increaseBalance-address-uint128-). + + +
+
+ + + +
+
+

_increaseBalance(address account, uint128 amount)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

ERC721OutOfBoundsIndex(address owner, uint256 index)

+
+

error

+# +
+
+
+ +An `owner`'s token query was out of bounds for `index`. + + +The owner being `address(0)` indicates a global out of bounds index. + + +
+
+ + + +
+
+

ERC721EnumerableForbiddenBatchMint()

+
+

error

+# +
+
+
+ +Batch mint is not allowed. + +
+
+ + + +
+ +## `ERC721Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol"; +``` + +ERC-721 token with pausable token transfers, minting and burning. + +Useful for scenarios such as preventing trades until the end of an evaluation +period, or having an emergency switch for freezing all token transfers in the +event of a large bug. + + +This contract does not include public pause and unpause functions. In +addition to inheriting this contract, you must define both functions, invoking the +[`Pausable._pause`](../utils#Pausable-_pause--) and [`Pausable._unpause`](../utils#Pausable-_unpause--) internal functions, with appropriate +access control, e.g. using [`AccessControl`](../access#AccessControl) or [`Ownable`](../access#Ownable). Not doing so will +make the contract pause mechanism of the contract unreachable, and thus unusable. + + +
+

Functions

+
+- [_update(to, tokenId, auth)](#ERC721Pausable-_update-address-uint256-address-) +#### Pausable [!toc] +- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +#### ERC721 [!toc] +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Pausable [!toc] +- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Pausable [!toc] +- [EnforcedPause()](#Pausable-EnforcedPause--) +- [ExpectedPause()](#Pausable-ExpectedPause--) +#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

_update(address to, uint256 tokenId, address auth) → address

+
+

internal

+# +
+
+
+ +See [`ERC721._update`](#ERC721-_update-address-uint256-address-). + +Requirements: + +- the contract must not be paused. + +
+
+ + + +
+ +## `ERC721Royalty` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol"; +``` + +Extension of ERC-721 with the ERC-2981 NFT Royalty Standard, a standardized way to retrieve royalty payment +information. + +Royalty information can be specified globally for all token ids via [`ERC2981._setDefaultRoyalty`](/contracts/5.x/api/token/common#ERC2981-_setDefaultRoyalty-address-uint96-), and/or individually +for specific token ids via [`ERC2981._setTokenRoyalty`](/contracts/5.x/api/token/common#ERC2981-_setTokenRoyalty-uint256-address-uint96-). The latter takes precedence over the first. + + +ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See +[Rationale](https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments) in the ERC. Marketplaces are expected to +voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. + + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC721Royalty-supportsInterface-bytes4-) +#### ERC721 [!toc] +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_update(to, tokenId, auth)](#ERC721-_update-address-uint256-address-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC2981 [!toc] +- [royaltyInfo(tokenId, salePrice)](#ERC2981-royaltyInfo-uint256-uint256-) +- [_feeDenominator()](#ERC2981-_feeDenominator--) +- [_setDefaultRoyalty(receiver, feeNumerator)](#ERC2981-_setDefaultRoyalty-address-uint96-) +- [_deleteDefaultRoyalty()](#ERC2981-_deleteDefaultRoyalty--) +- [_setTokenRoyalty(tokenId, receiver, feeNumerator)](#ERC2981-_setTokenRoyalty-uint256-address-uint96-) +- [_resetTokenRoyalty(tokenId)](#ERC2981-_resetTokenRoyalty-uint256-) +#### ERC165 [!toc] +#### IERC2981 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC2981 [!toc] +#### ERC165 [!toc] +#### IERC2981 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC2981 [!toc] +- [ERC2981InvalidDefaultRoyalty(numerator, denominator)](#ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-) +- [ERC2981InvalidDefaultRoyaltyReceiver(receiver)](#ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-) +- [ERC2981InvalidTokenRoyalty(tokenId, numerator, denominator)](#ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-) +- [ERC2981InvalidTokenRoyaltyReceiver(tokenId, receiver)](#ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-) +#### ERC165 [!toc] +#### IERC2981 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +
+
+ + + +
+ +## `ERC721URIStorage` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +``` + +ERC-721 token with storage based token URI management. + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC721URIStorage-supportsInterface-bytes4-) +- [tokenURI(tokenId)](#ERC721URIStorage-tokenURI-uint256-) +- [_setTokenURI(tokenId, _tokenURI)](#ERC721URIStorage-_setTokenURI-uint256-string-) +#### ERC721 [!toc] +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_update(to, tokenId, auth)](#ERC721-_update-address-uint256-address-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC4906 [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC4906 [!toc] +- [MetadataUpdate(_tokenId)](#IERC4906-MetadataUpdate-uint256-) +- [BatchMetadataUpdate(_fromTokenId, _toTokenId)](#IERC4906-BatchMetadataUpdate-uint256-uint256-) +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC4906 [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

tokenURI(uint256 tokenId) → string

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_setTokenURI(uint256 tokenId, string _tokenURI)

+
+

internal

+# +
+
+
+ +Sets `_tokenURI` as the tokenURI of `tokenId`. + +Emits [`IERC4906.MetadataUpdate`](../interfaces#IERC4906-MetadataUpdate-uint256-). + +
+
+ + + +
+ +## `ERC721Votes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol"; +``` + +Extension of ERC-721 to support voting and delegation as implemented by [`Votes`](../governance#Votes), where each individual NFT counts +as 1 vote unit. + +Tokens do not count as votes until they are delegated, because votes must be tracked which incurs an additional cost +on every transfer. Token holders can either delegate to a trusted representative who will decide how to make use of +the votes in governance decisions, or they can delegate to themselves to be their own representative. + +
+

Functions

+
+- [_update(to, tokenId, auth)](#ERC721Votes-_update-address-uint256-address-) +- [_getVotingUnits(account)](#ERC721Votes-_getVotingUnits-address-) +- [_increaseBalance(account, amount)](#ERC721Votes-_increaseBalance-address-uint128-) +#### Votes [!toc] +- [clock()](#Votes-clock--) +- [CLOCK_MODE()](#Votes-CLOCK_MODE--) +- [_validateTimepoint(timepoint)](#Votes-_validateTimepoint-uint256-) +- [getVotes(account)](#Votes-getVotes-address-) +- [getPastVotes(account, timepoint)](#Votes-getPastVotes-address-uint256-) +- [getPastTotalSupply(timepoint)](#Votes-getPastTotalSupply-uint256-) +- [_getTotalSupply()](#Votes-_getTotalSupply--) +- [delegates(account)](#Votes-delegates-address-) +- [delegate(delegatee)](#Votes-delegate-address-) +- [delegateBySig(delegatee, nonce, expiry, v, r, s)](#Votes-delegateBySig-address-uint256-uint256-uint8-bytes32-bytes32-) +- [_delegate(account, delegatee)](#Votes-_delegate-address-address-) +- [_transferVotingUnits(from, to, amount)](#Votes-_transferVotingUnits-address-address-uint256-) +- [_moveDelegateVotes(from, to, amount)](#Votes-_moveDelegateVotes-address-address-uint256-) +- [_numCheckpoints(account)](#Votes-_numCheckpoints-address-) +- [_checkpoints(account, pos)](#Votes-_checkpoints-address-uint32-) +#### IERC5805 [!toc] +#### IVotes [!toc] +#### IERC6372 [!toc] +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### ERC721 [!toc] +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### Votes [!toc] +#### IERC5805 [!toc] +#### IVotes [!toc] +- [DelegateChanged(delegator, fromDelegate, toDelegate)](#IVotes-DelegateChanged-address-address-address-) +- [DelegateVotesChanged(delegate, previousVotes, newVotes)](#IVotes-DelegateVotesChanged-address-uint256-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+#### Votes [!toc] +- [ERC6372InconsistentClock()](#Votes-ERC6372InconsistentClock--) +- [ERC5805FutureLookup(timepoint, clock)](#Votes-ERC5805FutureLookup-uint256-uint48-) +#### IERC5805 [!toc] +#### IVotes [!toc] +- [VotesExpiredSignature(expiry)](#IVotes-VotesExpiredSignature-uint256-) +#### IERC6372 [!toc] +#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +#### EIP712 [!toc] +#### IERC5267 [!toc] +#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

_update(address to, uint256 tokenId, address auth) → address

+
+

internal

+# +
+
+
+ +See [`ERC721._update`](#ERC721-_update-address-uint256-address-). Adjusts votes when tokens are transferred. + +Emits a [`IVotes.DelegateVotesChanged`](../governance#IVotes-DelegateVotesChanged-address-uint256-uint256-) event. + +
+
+ + + +
+
+

_getVotingUnits(address account) → uint256

+
+

internal

+# +
+
+
+ +Returns the balance of `account`. + + +Overriding this function will likely result in incorrect vote tracking. + + +
+
+ + + +
+
+

_increaseBalance(address account, uint128 amount)

+
+

internal

+# +
+
+
+ +See [`ERC721._increaseBalance`](#ERC721-_increaseBalance-address-uint128-). We need that to account tokens that were minted in batch. + +
+
+ + + +
+ +## `ERC721Wrapper` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Wrapper.sol"; +``` + +Extension of the ERC-721 token contract to support token wrapping. + +Users can deposit and withdraw an "underlying token" and receive a "wrapped token" with a matching tokenId. This is +useful in conjunction with other modules. For example, combining this wrapping mechanism with [`ERC721Votes`](#ERC721Votes) will allow +the wrapping of an existing "basic" ERC-721 into a governance token. + +
+

Functions

+
+- [constructor(underlyingToken)](#ERC721Wrapper-constructor-contract-IERC721-) +- [depositFor(account, tokenIds)](#ERC721Wrapper-depositFor-address-uint256---) +- [withdrawTo(account, tokenIds)](#ERC721Wrapper-withdrawTo-address-uint256---) +- [onERC721Received(, from, tokenId, )](#ERC721Wrapper-onERC721Received-address-address-uint256-bytes-) +- [_recover(account, tokenId)](#ERC721Wrapper-_recover-address-uint256-) +- [underlying()](#ERC721Wrapper-underlying--) +#### IERC721Receiver [!toc] +#### ERC721 [!toc] +- [supportsInterface(interfaceId)](#ERC721-supportsInterface-bytes4-) +- [balanceOf(owner)](#ERC721-balanceOf-address-) +- [ownerOf(tokenId)](#ERC721-ownerOf-uint256-) +- [name()](#ERC721-name--) +- [symbol()](#ERC721-symbol--) +- [tokenURI(tokenId)](#ERC721-tokenURI-uint256-) +- [_baseURI()](#ERC721-_baseURI--) +- [approve(to, tokenId)](#ERC721-approve-address-uint256-) +- [getApproved(tokenId)](#ERC721-getApproved-uint256-) +- [setApprovalForAll(operator, approved)](#ERC721-setApprovalForAll-address-bool-) +- [isApprovedForAll(owner, operator)](#ERC721-isApprovedForAll-address-address-) +- [transferFrom(from, to, tokenId)](#ERC721-transferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId)](#ERC721-safeTransferFrom-address-address-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#ERC721-safeTransferFrom-address-address-uint256-bytes-) +- [_ownerOf(tokenId)](#ERC721-_ownerOf-uint256-) +- [_getApproved(tokenId)](#ERC721-_getApproved-uint256-) +- [_isAuthorized(owner, spender, tokenId)](#ERC721-_isAuthorized-address-address-uint256-) +- [_checkAuthorized(owner, spender, tokenId)](#ERC721-_checkAuthorized-address-address-uint256-) +- [_increaseBalance(account, value)](#ERC721-_increaseBalance-address-uint128-) +- [_update(to, tokenId, auth)](#ERC721-_update-address-uint256-address-) +- [_mint(to, tokenId)](#ERC721-_mint-address-uint256-) +- [_safeMint(to, tokenId)](#ERC721-_safeMint-address-uint256-) +- [_safeMint(to, tokenId, data)](#ERC721-_safeMint-address-uint256-bytes-) +- [_burn(tokenId)](#ERC721-_burn-uint256-) +- [_transfer(from, to, tokenId)](#ERC721-_transfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId)](#ERC721-_safeTransfer-address-address-uint256-) +- [_safeTransfer(from, to, tokenId, data)](#ERC721-_safeTransfer-address-address-uint256-bytes-) +- [_approve(to, tokenId, auth)](#ERC721-_approve-address-uint256-address-) +- [_approve(to, tokenId, auth, emitEvent)](#ERC721-_approve-address-uint256-address-bool-) +- [_setApprovalForAll(owner, operator, approved)](#ERC721-_setApprovalForAll-address-address-bool-) +- [_requireOwned(tokenId)](#ERC721-_requireOwned-uint256-) +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Events

+
+#### IERC721Receiver [!toc] +#### ERC721 [!toc] +#### IERC721Errors [!toc] +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [ERC721UnsupportedToken(token)](#ERC721Wrapper-ERC721UnsupportedToken-address-) +#### IERC721Receiver [!toc] +#### ERC721 [!toc] +#### IERC721Errors [!toc] +- [ERC721InvalidOwner(owner)](#IERC721Errors-ERC721InvalidOwner-address-) +- [ERC721NonexistentToken(tokenId)](#IERC721Errors-ERC721NonexistentToken-uint256-) +- [ERC721IncorrectOwner(sender, tokenId, owner)](#IERC721Errors-ERC721IncorrectOwner-address-uint256-address-) +- [ERC721InvalidSender(sender)](#IERC721Errors-ERC721InvalidSender-address-) +- [ERC721InvalidReceiver(receiver)](#IERC721Errors-ERC721InvalidReceiver-address-) +- [ERC721InsufficientApproval(operator, tokenId)](#IERC721Errors-ERC721InsufficientApproval-address-uint256-) +- [ERC721InvalidApprover(approver)](#IERC721Errors-ERC721InvalidApprover-address-) +- [ERC721InvalidOperator(operator)](#IERC721Errors-ERC721InvalidOperator-address-) +#### IERC721Metadata [!toc] +#### IERC721 [!toc] +#### ERC165 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

constructor(contract IERC721 underlyingToken)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

depositFor(address account, uint256[] tokenIds) → bool

+
+

public

+# +
+
+
+ +Allow a user to deposit underlying tokens and mint the corresponding tokenIds. + +
+
+ + + +
+
+

withdrawTo(address account, uint256[] tokenIds) → bool

+
+

public

+# +
+
+
+ +Allow a user to burn wrapped tokens and withdraw the corresponding tokenIds of the underlying tokens. + +
+
+ + + +
+
+

onERC721Received(address, address from, uint256 tokenId, bytes) → bytes4

+
+

public

+# +
+
+
+ +Overrides [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) to allow minting on direct ERC-721 transfers to +this contract. + +In case there's data attached, it validates that the operator is this contract, so only trusted data +is accepted from [`ERC20Wrapper.depositFor`](/contracts/5.x/api/token/ERC20#ERC20Wrapper-depositFor-address-uint256-). + + +Doesn't work with unsafe transfers (eg. [`IERC721.transferFrom`](#IERC721-transferFrom-address-address-uint256-)). Use [`ERC721Wrapper._recover`](#ERC721Wrapper-_recover-address-uint256-) +for recovering in that scenario. + + +
+
+ + + +
+
+

_recover(address account, uint256 tokenId) → uint256

+
+

internal

+# +
+
+
+ +Mint a wrapped token to cover any underlyingToken that would have been transferred by mistake. Internal +function that can be exposed with access control if desired. + +
+
+ + + +
+
+

underlying() → contract IERC721

+
+

public

+# +
+
+
+ +Returns the underlying token. + +
+
+ + + +
+
+

ERC721UnsupportedToken(address token)

+
+

error

+# +
+
+
+ +The received ERC-721 token couldn't be wrapped. + +
+
+ + + +
+ +## `IERC721Enumerable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; +``` + +See https://eips.ethereum.org/EIPS/eip-721 + +
+

Functions

+
+- [totalSupply()](#IERC721Enumerable-totalSupply--) +- [tokenOfOwnerByIndex(owner, index)](#IERC721Enumerable-tokenOfOwnerByIndex-address-uint256-) +- [tokenByIndex(index)](#IERC721Enumerable-tokenByIndex-uint256-) +#### IERC721 [!toc] +- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 [!toc] +
+
+ + + +
+
+

totalSupply() → uint256

+
+

external

+# +
+
+
+ +Returns the total amount of tokens stored by the contract. + +
+
+ + + +
+
+

tokenOfOwnerByIndex(address owner, uint256 index) → uint256

+
+

external

+# +
+
+
+ +Returns a token ID owned by `owner` at a given `index` of its token list. +Use along with [`IERC6909.balanceOf`](../interfaces#IERC6909-balanceOf-address-uint256-) to enumerate all of ``owner``'s tokens. + +
+
+ + + +
+
+

tokenByIndex(uint256 index) → uint256

+
+

external

+# +
+
+
+ +Returns a token ID at a given `index` of all the tokens stored by the contract. +Use along with [`IERC6909TokenSupply.totalSupply`](../interfaces#IERC6909TokenSupply-totalSupply-uint256-) to enumerate all tokens. + +
+
+ + + +
+ +## `IERC721Metadata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +``` + +See https://eips.ethereum.org/EIPS/eip-721 + +
+

Functions

+
+- [name()](#IERC721Metadata-name--) +- [symbol()](#IERC721Metadata-symbol--) +- [tokenURI(tokenId)](#IERC721Metadata-tokenURI-uint256-) +#### IERC721 [!toc] +- [balanceOf(owner)](#IERC721-balanceOf-address-) +- [ownerOf(tokenId)](#IERC721-ownerOf-uint256-) +- [safeTransferFrom(from, to, tokenId, data)](#IERC721-safeTransferFrom-address-address-uint256-bytes-) +- [safeTransferFrom(from, to, tokenId)](#IERC721-safeTransferFrom-address-address-uint256-) +- [transferFrom(from, to, tokenId)](#IERC721-transferFrom-address-address-uint256-) +- [approve(to, tokenId)](#IERC721-approve-address-uint256-) +- [setApprovalForAll(operator, approved)](#IERC721-setApprovalForAll-address-bool-) +- [getApproved(tokenId)](#IERC721-getApproved-uint256-) +- [isApprovedForAll(owner, operator)](#IERC721-isApprovedForAll-address-address-) +#### IERC165 [!toc] +- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ +
+

Events

+
+#### IERC721 [!toc] +- [Transfer(from, to, tokenId)](#IERC721-Transfer-address-address-uint256-) +- [Approval(owner, approved, tokenId)](#IERC721-Approval-address-address-uint256-) +- [ApprovalForAll(owner, operator, approved)](#IERC721-ApprovalForAll-address-address-bool-) +#### IERC165 [!toc] +
+
+ + + +
+
+

name() → string

+
+

external

+# +
+
+
+ +Returns the token collection name. + +
+
+ + + +
+
+

symbol() → string

+
+

external

+# +
+
+
+ +Returns the token collection symbol. + +
+
+ + + +
+
+

tokenURI(uint256 tokenId) → string

+
+

external

+# +
+
+
+ +Returns the Uniform Resource Identifier (URI) for `tokenId` token. + +
+
+ + + +
+ +## `ERC721Holder` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +``` + +Implementation of the [`IERC721Receiver`](#IERC721Receiver) interface. + +Accepts all token transfers. +Make sure the contract is able to use its token with [`IERC721.safeTransferFrom`](#IERC721-safeTransferFrom-address-address-uint256-), [`IERC721.approve`](#IERC721-approve-address-uint256-) or +[`IERC721.setApprovalForAll`](#IERC721-setApprovalForAll-address-bool-). + +@custom:stateless + +
+

Functions

+
+- [onERC721Received(, , , )](#ERC721Holder-onERC721Received-address-address-uint256-bytes-) +#### IERC721Receiver [!toc] +
+
+ + + +
+
+

onERC721Received(address, address, uint256, bytes) → bytes4

+
+

public

+# +
+
+
+ +See [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-). + +Always returns `IERC721Receiver.onERC721Received.selector`. + +
+
+ + + +
+ +## `ERC721Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol"; +``` + +Library that provides common ERC-721 utility functions. + +See [ERC-721](https://eips.ethereum.org/EIPS/eip-721). + +_Available since v5.1._ + +
+

Functions

+
+- [checkOnERC721Received(operator, from, to, tokenId, data)](#ERC721Utils-checkOnERC721Received-address-address-address-uint256-bytes-) +
+
+ + + +
+
+

checkOnERC721Received(address operator, address from, address to, uint256 tokenId, bytes data)

+
+

internal

+# +
+
+
+ +Performs an acceptance check for the provided `operator` by calling [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) +on the `to` address. The `operator` is generally the address that initiated the token transfer (i.e. `msg.sender`). + +The acceptance call is not executed and treated as a no-op if the target address doesn't contain code (i.e. an EOA). +Otherwise, the recipient must implement [`IERC721Receiver.onERC721Received`](#IERC721Receiver-onERC721Received-address-address-uint256-bytes-) and return the acceptance magic value to accept +the transfer. + +
+
diff --git a/docs/content/contracts/5.x/api/token/common.mdx b/docs/content/contracts/5.x/api/token/common.mdx new file mode 100644 index 00000000..a8040990 --- /dev/null +++ b/docs/content/contracts/5.x/api/token/common.mdx @@ -0,0 +1,282 @@ +--- +title: "Common" +description: "Smart contract common utilities and implementations" +--- + +Functionality that is common to multiple token standards. + +* [`ERC2981`](#ERC2981): NFT Royalties compatible with both ERC-721 and ERC-1155. + * For ERC-721 consider [`ERC721Royalty`](/contracts/5.x/api/token/ERC721#ERC721Royalty) which clears the royalty information from storage on burn. + +## Contracts + +[`ERC2981`](#ERC2981) + + + +
+ +## `ERC2981` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/token/common/ERC2981.sol"; +``` + +Implementation of the NFT Royalty Standard, a standardized way to retrieve royalty payment information. + +Royalty information can be specified globally for all token ids via [`ERC2981._setDefaultRoyalty`](#ERC2981-_setDefaultRoyalty-address-uint96-), and/or individually for +specific token ids via [`ERC2981._setTokenRoyalty`](#ERC2981-_setTokenRoyalty-uint256-address-uint96-). The latter takes precedence over the first. + +Royalty is specified as a fraction of sale price. [`ERC2981._feeDenominator`](#ERC2981-_feeDenominator--) is overridable but defaults to 10000, meaning the +fee is specified in basis points by default. + + +ERC-2981 only specifies a way to signal royalty information and does not enforce its payment. See +[Rationale](https://eips.ethereum.org/EIPS/eip-2981#optional-royalty-payments) in the ERC. Marketplaces are expected to +voluntarily pay royalties together with sales, but note that this standard is not yet widely supported. + + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC2981-supportsInterface-bytes4-) +- [royaltyInfo(tokenId, salePrice)](#ERC2981-royaltyInfo-uint256-uint256-) +- [_feeDenominator()](#ERC2981-_feeDenominator--) +- [_setDefaultRoyalty(receiver, feeNumerator)](#ERC2981-_setDefaultRoyalty-address-uint96-) +- [_deleteDefaultRoyalty()](#ERC2981-_deleteDefaultRoyalty--) +- [_setTokenRoyalty(tokenId, receiver, feeNumerator)](#ERC2981-_setTokenRoyalty-uint256-address-uint96-) +- [_resetTokenRoyalty(tokenId)](#ERC2981-_resetTokenRoyalty-uint256-) +#### ERC165 [!toc] +#### IERC2981 [!toc] +#### IERC165 [!toc] +
+
+ +
+

Errors

+
+- [ERC2981InvalidDefaultRoyalty(numerator, denominator)](#ERC2981-ERC2981InvalidDefaultRoyalty-uint256-uint256-) +- [ERC2981InvalidDefaultRoyaltyReceiver(receiver)](#ERC2981-ERC2981InvalidDefaultRoyaltyReceiver-address-) +- [ERC2981InvalidTokenRoyalty(tokenId, numerator, denominator)](#ERC2981-ERC2981InvalidTokenRoyalty-uint256-uint256-uint256-) +- [ERC2981InvalidTokenRoyaltyReceiver(tokenId, receiver)](#ERC2981-ERC2981InvalidTokenRoyaltyReceiver-uint256-address-) +#### ERC165 [!toc] +#### IERC2981 [!toc] +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+
+

royaltyInfo(uint256 tokenId, uint256 salePrice) → address receiver, uint256 amount

+
+

public

+# +
+
+
+ +Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of +exchange. The royalty amount is denominated and should be paid in that same unit of exchange. + + +ERC-2981 allows setting the royalty to 100% of the price. In that case all the price would be sent to the +royalty receiver and 0 tokens to the seller. Contracts dealing with royalty should consider empty transfers. + + +
+
+ + + +
+
+

_feeDenominator() → uint96

+
+

internal

+# +
+
+
+ +The denominator with which to interpret the fee set in [`ERC2981._setTokenRoyalty`](#ERC2981-_setTokenRoyalty-uint256-address-uint96-) and [`ERC2981._setDefaultRoyalty`](#ERC2981-_setDefaultRoyalty-address-uint96-) as a +fraction of the sale price. Defaults to 10000 so fees are expressed in basis points, but may be customized by an +override. + +
+
+ + + +
+
+

_setDefaultRoyalty(address receiver, uint96 feeNumerator)

+
+

internal

+# +
+
+
+ +Sets the royalty information that all ids in this contract will default to. + +Requirements: + +- `receiver` cannot be the zero address. +- `feeNumerator` cannot be greater than the fee denominator. + +
+
+ + + +
+
+

_deleteDefaultRoyalty()

+
+

internal

+# +
+
+
+ +Removes default royalty information. + +
+
+ + + +
+
+

_setTokenRoyalty(uint256 tokenId, address receiver, uint96 feeNumerator)

+
+

internal

+# +
+
+
+ +Sets the royalty information for a specific token id, overriding the global default. + +Requirements: + +- `receiver` cannot be the zero address. +- `feeNumerator` cannot be greater than the fee denominator. + +
+
+ + + +
+
+

_resetTokenRoyalty(uint256 tokenId)

+
+

internal

+# +
+
+
+ +Resets royalty information for the token id back to the global default. + +
+
+ + + +
+
+

ERC2981InvalidDefaultRoyalty(uint256 numerator, uint256 denominator)

+
+

error

+# +
+
+
+ +The default royalty set is invalid (eg. (numerator / denominator) >= 1). + +
+
+ + + +
+
+

ERC2981InvalidDefaultRoyaltyReceiver(address receiver)

+
+

error

+# +
+
+
+ +The default royalty receiver is invalid. + +
+
+ + + +
+
+

ERC2981InvalidTokenRoyalty(uint256 tokenId, uint256 numerator, uint256 denominator)

+
+

error

+# +
+
+
+ +The royalty set for a specific `tokenId` is invalid (eg. (numerator / denominator) >= 1). + +
+
+ + + +
+
+

ERC2981InvalidTokenRoyaltyReceiver(uint256 tokenId, address receiver)

+
+

error

+# +
+
+
+ +The royalty receiver for `tokenId` is invalid. + +
+
diff --git a/docs/content/contracts/5.x/api/utils.mdx b/docs/content/contracts/5.x/api/utils.mdx new file mode 100644 index 00000000..e3003515 --- /dev/null +++ b/docs/content/contracts/5.x/api/utils.mdx @@ -0,0 +1,18242 @@ +--- +title: "Utils" +description: "Smart contract utils utilities and implementations" +--- + +Miscellaneous contracts and libraries containing utility functions you can use to improve security, work with new data types, or safely use low-level primitives. + +* [`Math`](#Math), [`SignedMath`](#SignedMath): Implementation of various arithmetic functions. +* [`SafeCast`](#SafeCast): Checked downcasting functions to avoid silent truncation. +* [`Nonces`](#Nonces): Utility for tracking and verifying address nonces that only increment. +* [`NoncesKeyed`](#NoncesKeyed): Alternative to [`Nonces`](#Nonces), that support keyed nonces following [ERC-4337 specifications](https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support). +* [`Pausable`](#Pausable): A common emergency response mechanism that can pause functionality while a remediation is pending. +* [`ReentrancyGuard`](#ReentrancyGuard): A modifier that can prevent reentrancy during certain functions. +* [`ReentrancyGuardTransient`](#ReentrancyGuardTransient): Variant of [`ReentrancyGuard`](#ReentrancyGuard) that uses transient storage ([EIP-1153](https://eips.ethereum.org/EIPS/eip-1153)). +* [`ERC165`](#ERC165), [`ERC165Checker`](#ERC165Checker): Utilities for inspecting interfaces supported by contracts. +* [`Accumulators`](#Accumulators): A library for merging an arbitrary dynamic number of bytes buffers. +* [`BitMaps`](#BitMaps): A simple library to manage boolean value mapped to a numerical index in an efficient way. +* [`Checkpoints`](#Checkpoints): A data structure to store values mapped to a strictly increasing key. Can be used for storing and accessing values over time. +* [`CircularBuffer`](#CircularBuffer): A data structure to store the last N values pushed to it. +* [`DoubleEndedQueue`](#DoubleEndedQueue): An implementation of a [double ended queue](https://en.wikipedia.org/wiki/Double-ended_queue) whose values can be added or removed from both sides. Useful for FIFO and LIFO structures. +* [`EnumerableMap`](#EnumerableMap): A type like Solidity’s [`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types), but with key-value _enumeration_: this will let you know how many entries a mapping has, and iterate over them (which is not possible with `mapping`). +* [`EnumerableSet`](#EnumerableSet): Like [`EnumerableMap`](#EnumerableMap), but for [sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)). Can be used to store privileged accounts, issued IDs, etc. +* [`Heap`](#Heap): A library that implements a [binary heap](https://en.wikipedia.org/wiki/Binary_heap) in storage. +* [`MerkleTree`](#MerkleTree): A library with [Merkle Tree](https://wikipedia.org/wiki/Merkle_Tree) data structures and helper functions. +* [`Address`](#Address): Collection of functions for overloading Solidity’s [`address`](https://docs.soliditylang.org/en/latest/types.html#address) type. +* [`Arrays`](#Arrays): Collection of functions that operate on [`arrays`](https://docs.soliditylang.org/en/latest/types.html#arrays). +* [`Base58`](#Base58): On-chain base58 encoding and decoding. +* [`Base64`](#Base64): On-chain base64 and base64URL encoding according to [RFC-4648](https://datatracker.ietf.org/doc/html/rfc4648). +* [`Blockhash`](#Blockhash): A library for accessing historical block hashes beyond the standard 256 block limit utilizing EIP-2935’s historical blockhash functionality. +* [`Bytes`](#Bytes): Common operations on bytes objects. +* [`CAIP2`](#CAIP2), [`CAIP10`](#CAIP10): Libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. +* [`Calldata`](#Calldata): Helpers for manipulating calldata. +* [`Comparators`](#Comparators): A library that contains comparator functions to use with the [`Heap`](#Heap) library. +* [`Context`](#Context): A utility for abstracting the sender and calldata in the current execution context. +* [`Create2`](#Create2): Wrapper around the [`CREATE2` EVM opcode](https://blog.openzeppelin.com/getting-the-most-out-of-create2/) for safe use without having to deal with low-level assembly. +* [`InteroperableAddress`](#InteroperableAddress): Library for formatting and parsing ERC-7930 interoperable addresses. +* [`LowLevelCall`](#LowLevelCall): Collection of functions to perform calls with low-level assembly. +* [`Memory`](#Memory): A utility library to manipulate memory. +* [`Multicall`](#Multicall): Abstract contract with a utility to allow batching together multiple calls in a single transaction. Useful for allowing EOAs to perform multiple operations at once. +* [`Packing`](#Packing): A library for packing and unpacking multiple values into bytes32. +* [`Panic`](#Panic): A library to revert with [Solidity panic codes](https://docs.soliditylang.org/en/v0.8.20/control-structures.html#panic-via-assert-and-error-via-require). +* [`RelayedCall`](#RelayedCall): A library for performing calls that use minimal and predictable relayers to hide the sender. +* [`RLP`](#RLP): Library for encoding and decoding data in Ethereum’s Recursive Length Prefix format. +* [`ShortStrings`](#ShortStrings): Library to encode (and decode) short strings into (or from) a single bytes32 slot for optimizing costs. Short strings are limited to 31 characters. +* [`SlotDerivation`](#SlotDerivation): Methods for deriving storage slot from ERC-7201 namespaces as well as from constructions such as mapping and arrays. +* [`StorageSlot`](#StorageSlot): Methods for accessing specific storage slots formatted as common primitive types. +* [`Strings`](#Strings): Common operations for strings formatting. +* [`Time`](#Time): A library that provides helpers for manipulating time-related objects, including a `Delay` type. +* [`TransientSlot`](#TransientSlot): Primitives for reading from and writing to transient storage (only value types are currently supported). + + +Because Solidity does not support generic types, [`EnumerableMap`](#EnumerableMap) and [`EnumerableSet`](#EnumerableSet) are specialized to a limited number of key-value types. + + +## Math + +[`Math`](#Math) + +[`SignedMath`](#SignedMath) + +[`SafeCast`](#SafeCast) + +## Security + +[`Nonces`](#Nonces) + +[`NoncesKeyed`](#NoncesKeyed) + +[`Pausable`](#Pausable) + +[`ReentrancyGuard`](#ReentrancyGuard) + +[`ReentrancyGuardTransient`](#ReentrancyGuardTransient) + +## Introspection + +This set of interfaces and contracts deal with [type introspection](https://en.wikipedia.org/wiki/Type_introspection) of contracts, that is, examining which functions can be called on them. This is usually referred to as a contract’s _interface_. + +Ethereum contracts have no native concept of an interface, so applications must usually simply trust they are not making an incorrect call. For trusted setups this is a non-issue, but often unknown and untrusted third-party addresses need to be interacted with. There may even not be any direct calls to them! (e.g. ERC-20 tokens may be sent to a contract that lacks a way to transfer them out of it, locking them forever). In these cases, a contract _declaring_ its interface can be very helpful in preventing errors. + +[`IERC165`](#IERC165) + +[`ERC165`](#ERC165) + +[`ERC165Checker`](#ERC165Checker) + +## Data Structures + +[`Accumulators`](#Accumulators) + +[`BitMaps`](#BitMaps) + +[`Checkpoints`](#Checkpoints) + +[`CircularBuffer`](#CircularBuffer) + +[`DoubleEndedQueue`](#DoubleEndedQueue) + +[`EnumerableMap`](#EnumerableMap) + +[`EnumerableSet`](#EnumerableSet) + +[`Heap`](#Heap) + +[`MerkleTree`](#MerkleTree) + +## Libraries + +[`Address`](#Address) + +[`Arrays`](#Arrays) + +[`Base58`](#Base58) + +[`Base64`](#Base64) + +[`Blockhash`](#Blockhash) + +[`Bytes`](#Bytes) + +[`CAIP10`](#CAIP10) + +[`CAIP2`](#CAIP2) + +[`Calldata`](#Calldata) + +[`Comparators`](#Comparators) + +[`Context`](#Context) + +[`Create2`](#Create2) + +[`InteroperableAddress`](#InteroperableAddress) + +[`LowLevelCall`](#LowLevelCall) + +[`Memory`](#Memory) + +[`Multicall`](#Multicall) + +[`Packing`](#Packing) + +[`Panic`](#Panic) + +[`RelayedCall`](#RelayedCall) + +[`RLP`](#RLP) + +[`ShortStrings`](#ShortStrings) + +[`SlotDerivation`](#SlotDerivation) + +[`StorageSlot`](#StorageSlot) + +[`Strings`](#Strings) + +[`Time`](#Time) + +[`TransientSlot`](#TransientSlot) + + + +
+ +## `Address` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Address.sol"; +``` + +Collection of functions related to the address type + +
+

Functions

+
+- [sendValue(recipient, amount)](#Address-sendValue-address-payable-uint256-) +- [functionCall(target, data)](#Address-functionCall-address-bytes-) +- [functionCallWithValue(target, data, value)](#Address-functionCallWithValue-address-bytes-uint256-) +- [functionStaticCall(target, data)](#Address-functionStaticCall-address-bytes-) +- [functionDelegateCall(target, data)](#Address-functionDelegateCall-address-bytes-) +- [verifyCallResultFromTarget(target, success, returndata)](#Address-verifyCallResultFromTarget-address-bool-bytes-) +- [verifyCallResult(success, returndata)](#Address-verifyCallResult-bool-bytes-) +
+
+ +
+

Errors

+
+- [AddressEmptyCode(target)](#Address-AddressEmptyCode-address-) +
+
+ + + +
+
+

sendValue(address payable recipient, uint256 amount)

+
+

internal

+# +
+
+
+ +Replacement for Solidity's `transfer`: sends `amount` wei to +`recipient`, forwarding all available gas and reverting on errors. + +[EIP1884](https://eips.ethereum.org/EIPS/eip-1884) increases the gas cost +of certain opcodes, possibly making contracts go over the 2300 gas limit +imposed by `transfer`, making them unable to receive funds via +`transfer`. [`Address.sendValue`](#Address-sendValue-address-payable-uint256-) removes this limitation. + +[Learn more](https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/). + + +because control is transferred to `recipient`, care must be +taken to not create reentrancy vulnerabilities. Consider using +[`ReentrancyGuard`](#ReentrancyGuard) or the +[checks-effects-interactions pattern](https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern). + + +
+
+ + + +
+
+

functionCall(address target, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `call`. A +plain `call` is an unsafe replacement for a function call: use this +function instead. + +If `target` reverts with a revert reason or custom error, it is bubbled +up by this function (like regular Solidity function calls). However, if +the call reverted with no returned reason, this function reverts with a +[`Errors.FailedCall`](#Errors-FailedCall--) error. + +Returns the raw returned data. To convert to the expected return value, +use [`abi.decode`](https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions). + +Requirements: + +- `target` must be a contract. +- calling `target` with `data` must not revert. + +
+
+ + + +
+
+

functionCallWithValue(address target, bytes data, uint256 value) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but also transferring `value` wei to `target`. + +Requirements: + +- the calling contract must have an ETH balance of at least `value`. +- the called Solidity function must be `payable`. + +
+
+ + + +
+
+

functionStaticCall(address target, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but performing a static call. + +
+
+ + + +
+
+

functionDelegateCall(address target, bytes data) → bytes

+
+

internal

+# +
+
+
+ +Same as [`functionCall`](#Address-functionCall-address-bytes-), +but performing a delegate call. + +
+
+ + + +
+
+

verifyCallResultFromTarget(address target, bool success, bytes returndata) → bytes

+
+

internal

+# +
+
+
+ +Tool to verify that a low level call to smart-contract was successful, and reverts if the target +was not a contract or bubbling up the revert reason (falling back to [`Errors.FailedCall`](#Errors-FailedCall--)) in case +of an unsuccessful call. + + +This function is DEPRECATED and may be removed in the next major release. + + +
+
+ + + +
+
+

verifyCallResult(bool success, bytes returndata) → bytes

+
+

internal

+# +
+
+
+ +Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the +revert reason or with a default [`Errors.FailedCall`](#Errors-FailedCall--) error. + +
+
+ + + +
+
+

AddressEmptyCode(address target)

+
+

error

+# +
+
+
+ +There's no code at `target` (it is not a contract). + +
+
+ + + +
+ +## `Arrays` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Arrays.sol"; +``` + +Collection of functions related to array types. + +
+

Functions

+
+- [sort(array, comp)](#Arrays-sort-uint256---function--uint256-uint256--pure-returns--bool--) +- [sort(array)](#Arrays-sort-uint256---) +- [sort(array, comp)](#Arrays-sort-address---function--address-address--pure-returns--bool--) +- [sort(array)](#Arrays-sort-address---) +- [sort(array, comp)](#Arrays-sort-bytes32---function--bytes32-bytes32--pure-returns--bool--) +- [sort(array)](#Arrays-sort-bytes32---) +- [findUpperBound(array, element)](#Arrays-findUpperBound-uint256---uint256-) +- [lowerBound(array, element)](#Arrays-lowerBound-uint256---uint256-) +- [upperBound(array, element)](#Arrays-upperBound-uint256---uint256-) +- [lowerBoundMemory(array, element)](#Arrays-lowerBoundMemory-uint256---uint256-) +- [upperBoundMemory(array, element)](#Arrays-upperBoundMemory-uint256---uint256-) +- [slice(array, start)](#Arrays-slice-address---uint256-) +- [slice(array, start, end)](#Arrays-slice-address---uint256-uint256-) +- [slice(array, start)](#Arrays-slice-bytes32---uint256-) +- [slice(array, start, end)](#Arrays-slice-bytes32---uint256-uint256-) +- [slice(array, start)](#Arrays-slice-uint256---uint256-) +- [slice(array, start, end)](#Arrays-slice-uint256---uint256-uint256-) +- [splice(array, start)](#Arrays-splice-address---uint256-) +- [splice(array, start, end)](#Arrays-splice-address---uint256-uint256-) +- [splice(array, start)](#Arrays-splice-bytes32---uint256-) +- [splice(array, start, end)](#Arrays-splice-bytes32---uint256-uint256-) +- [splice(array, start)](#Arrays-splice-uint256---uint256-) +- [splice(array, start, end)](#Arrays-splice-uint256---uint256-uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-address---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-bytes32---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-uint256---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-bytes---uint256-) +- [unsafeAccess(arr, pos)](#Arrays-unsafeAccess-string---uint256-) +- [unsafeMemoryAccess(arr, pos)](#Arrays-unsafeMemoryAccess-address---uint256-) +- [unsafeMemoryAccess(arr, pos)](#Arrays-unsafeMemoryAccess-bytes32---uint256-) +- [unsafeMemoryAccess(arr, pos)](#Arrays-unsafeMemoryAccess-uint256---uint256-) +- [unsafeMemoryAccess(arr, pos)](#Arrays-unsafeMemoryAccess-bytes---uint256-) +- [unsafeMemoryAccess(arr, pos)](#Arrays-unsafeMemoryAccess-string---uint256-) +- [unsafeSetLength(array, len)](#Arrays-unsafeSetLength-address---uint256-) +- [unsafeSetLength(array, len)](#Arrays-unsafeSetLength-bytes32---uint256-) +- [unsafeSetLength(array, len)](#Arrays-unsafeSetLength-uint256---uint256-) +- [unsafeSetLength(array, len)](#Arrays-unsafeSetLength-bytes---uint256-) +- [unsafeSetLength(array, len)](#Arrays-unsafeSetLength-string---uint256-) +
+
+ + + +
+
+

sort(uint256[] array, function (uint256,uint256) pure returns (bool) comp) → uint256[]

+
+

internal

+# +
+
+
+ +Sort an array of uint256 (in memory) following the provided comparator function. + +This function does the sorting "in place", meaning that it overrides the input. The object is returned for +convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. + + +this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the +array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful +when executing this as part of a transaction. If the array being sorted is too large, the sort operation may +consume more gas than is available in a block, leading to potential DoS. + + + +Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. + + +
+
+ + + +
+
+

sort(uint256[] array) → uint256[]

+
+

internal

+# +
+
+
+ +Variant of [`Arrays.sort`](#Arrays-sort-bytes32---) that sorts an array of uint256 in increasing order. + +
+
+ + + +
+
+

sort(address[] array, function (address,address) pure returns (bool) comp) → address[]

+
+

internal

+# +
+
+
+ +Sort an array of address (in memory) following the provided comparator function. + +This function does the sorting "in place", meaning that it overrides the input. The object is returned for +convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. + + +this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the +array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful +when executing this as part of a transaction. If the array being sorted is too large, the sort operation may +consume more gas than is available in a block, leading to potential DoS. + + + +Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. + + +
+
+ + + +
+
+

sort(address[] array) → address[]

+
+

internal

+# +
+
+
+ +Variant of [`Arrays.sort`](#Arrays-sort-bytes32---) that sorts an array of address in increasing order. + +
+
+ + + +
+
+

sort(bytes32[] array, function (bytes32,bytes32) pure returns (bool) comp) → bytes32[]

+
+

internal

+# +
+
+
+ +Sort an array of bytes32 (in memory) following the provided comparator function. + +This function does the sorting "in place", meaning that it overrides the input. The object is returned for +convenience, but that returned value can be discarded safely if the caller has a memory pointer to the array. + + +this function's cost is `O(n · log(n))` in average and `O(n²)` in the worst case, with n the length of the +array. Using it in view functions that are executed through `eth_call` is safe, but one should be very careful +when executing this as part of a transaction. If the array being sorted is too large, the sort operation may +consume more gas than is available in a block, leading to potential DoS. + + + +Consider memory side-effects when using custom comparator functions that access memory in an unsafe way. + + +
+
+ + + +
+
+

sort(bytes32[] array) → bytes32[]

+
+

internal

+# +
+
+
+ +Variant of [`Arrays.sort`](#Arrays-sort-bytes32---) that sorts an array of bytes32 in increasing order. + +
+
+ + + +
+
+

findUpperBound(uint256[] array, uint256 element) → uint256

+
+

internal

+# +
+
+
+ +Searches a sorted `array` and returns the first index that contains +a value greater or equal to `element`. If no such index exists (i.e. all +values in the array are strictly less than `element`), the array length is +returned. Time complexity O(log n). + + +The `array` is expected to be sorted in ascending order, and to +contain no repeated elements. + + + +Deprecated. This implementation behaves as [`Arrays.lowerBound`](#Arrays-lowerBound-uint256---uint256-) but lacks +support for repeated elements in the array. The [`Arrays.lowerBound`](#Arrays-lowerBound-uint256---uint256-) function should +be used instead. + + +
+
+ + + +
+
+

lowerBound(uint256[] array, uint256 element) → uint256

+
+

internal

+# +
+
+
+ +Searches an `array` sorted in ascending order and returns the first +index that contains a value greater or equal than `element`. If no such index +exists (i.e. all values in the array are strictly less than `element`), the array +length is returned. Time complexity O(log n). + +See C++'s [lower_bound](https://en.cppreference.com/w/cpp/algorithm/lower_bound). + +
+
+ + + +
+
+

upperBound(uint256[] array, uint256 element) → uint256

+
+

internal

+# +
+
+
+ +Searches an `array` sorted in ascending order and returns the first +index that contains a value strictly greater than `element`. If no such index +exists (i.e. all values in the array are strictly less than `element`), the array +length is returned. Time complexity O(log n). + +See C++'s [upper_bound](https://en.cppreference.com/w/cpp/algorithm/upper_bound). + +
+
+ + + +
+
+

lowerBoundMemory(uint256[] array, uint256 element) → uint256

+
+

internal

+# +
+
+
+ +Same as [`Arrays.lowerBound`](#Arrays-lowerBound-uint256---uint256-), but with an array in memory. + +
+
+ + + +
+
+

upperBoundMemory(uint256[] array, uint256 element) → uint256

+
+

internal

+# +
+
+
+ +Same as [`Arrays.upperBound`](#Arrays-upperBound-uint256---uint256-), but with an array in memory. + +
+
+ + + +
+
+

slice(address[] array, uint256 start) → address[]

+
+

internal

+# +
+
+
+ +Copies the content of `array`, from `start` (included) to the end of `array` into a new address array in +memory. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

slice(address[] array, uint256 start, uint256 end) → address[]

+
+

internal

+# +
+
+
+ +Copies the content of `array`, from `start` (included) to `end` (excluded) into a new address array in +memory. The `end` argument is truncated to the length of the `array`. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

slice(bytes32[] array, uint256 start) → bytes32[]

+
+

internal

+# +
+
+
+ +Copies the content of `array`, from `start` (included) to the end of `array` into a new bytes32 array in +memory. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

slice(bytes32[] array, uint256 start, uint256 end) → bytes32[]

+
+

internal

+# +
+
+
+ +Copies the content of `array`, from `start` (included) to `end` (excluded) into a new bytes32 array in +memory. The `end` argument is truncated to the length of the `array`. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

slice(uint256[] array, uint256 start) → uint256[]

+
+

internal

+# +
+
+
+ +Copies the content of `array`, from `start` (included) to the end of `array` into a new uint256 array in +memory. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

slice(uint256[] array, uint256 start, uint256 end) → uint256[]

+
+

internal

+# +
+
+
+ +Copies the content of `array`, from `start` (included) to `end` (excluded) into a new uint256 array in +memory. The `end` argument is truncated to the length of the `array`. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

splice(address[] array, uint256 start) → address[]

+
+

internal

+# +
+
+
+ +Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array. + + +This function modifies the provided array in place. If you need to preserve the original array, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead. + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

splice(address[] array, uint256 start, uint256 end) → address[]

+
+

internal

+# +
+
+
+ +Moves the content of `array`, from `start` (included) to `end` (excluded) to the start of that array. The +`end` argument is truncated to the length of the `array`. + + +This function modifies the provided array in place. If you need to preserve the original array, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead. + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

splice(bytes32[] array, uint256 start) → bytes32[]

+
+

internal

+# +
+
+
+ +Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array. + + +This function modifies the provided array in place. If you need to preserve the original array, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead. + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

splice(bytes32[] array, uint256 start, uint256 end) → bytes32[]

+
+

internal

+# +
+
+
+ +Moves the content of `array`, from `start` (included) to `end` (excluded) to the start of that array. The +`end` argument is truncated to the length of the `array`. + + +This function modifies the provided array in place. If you need to preserve the original array, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead. + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

splice(uint256[] array, uint256 start) → uint256[]

+
+

internal

+# +
+
+
+ +Moves the content of `array`, from `start` (included) to the end of `array` to the start of that array. + + +This function modifies the provided array in place. If you need to preserve the original array, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead. + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

splice(uint256[] array, uint256 start, uint256 end) → uint256[]

+
+

internal

+# +
+
+
+ +Moves the content of `array`, from `start` (included) to `end` (excluded) to the start of that array. The +`end` argument is truncated to the length of the `array`. + + +This function modifies the provided array in place. If you need to preserve the original array, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead. + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

unsafeAccess(address[] arr, uint256 pos) → struct StorageSlot.AddressSlot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeAccess(bytes32[] arr, uint256 pos) → struct StorageSlot.Bytes32Slot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeAccess(uint256[] arr, uint256 pos) → struct StorageSlot.Uint256Slot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeAccess(bytes[] arr, uint256 pos) → struct StorageSlot.BytesSlot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeAccess(string[] arr, uint256 pos) → struct StorageSlot.StringSlot

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeMemoryAccess(address[] arr, uint256 pos) → address res

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeMemoryAccess(bytes32[] arr, uint256 pos) → bytes32 res

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeMemoryAccess(uint256[] arr, uint256 pos) → uint256 res

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeMemoryAccess(bytes[] arr, uint256 pos) → bytes res

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeMemoryAccess(string[] arr, uint256 pos) → string res

+
+

internal

+# +
+
+
+ +Access an array in an "unsafe" way. Skips solidity "index-out-of-range" check. + + +Only use if you are certain `pos` is lower than the array length. + + +
+
+ + + +
+
+

unsafeSetLength(address[] array, uint256 len)

+
+

internal

+# +
+
+
+ +Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. + + +this does not clear elements if length is reduced, of initialize elements if length is increased. + + +
+
+ + + +
+
+

unsafeSetLength(bytes32[] array, uint256 len)

+
+

internal

+# +
+
+
+ +Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. + + +this does not clear elements if length is reduced, of initialize elements if length is increased. + + +
+
+ + + +
+
+

unsafeSetLength(uint256[] array, uint256 len)

+
+

internal

+# +
+
+
+ +Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. + + +this does not clear elements if length is reduced, of initialize elements if length is increased. + + +
+
+ + + +
+
+

unsafeSetLength(bytes[] array, uint256 len)

+
+

internal

+# +
+
+
+ +Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. + + +this does not clear elements if length is reduced, of initialize elements if length is increased. + + +
+
+ + + +
+
+

unsafeSetLength(string[] array, uint256 len)

+
+

internal

+# +
+
+
+ +Helper to set the length of a dynamic array. Directly writing to `.length` is forbidden. + + +this does not clear elements if length is reduced, of initialize elements if length is increased. + + +
+
+ + + +
+ +## `Base58` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Base58.sol"; +``` + +Provides a set of functions to operate with Base58 strings. + +Base58 is an encoding scheme that converts binary data into a human-readable text format. +Similar to [`Base64`](#Base64) but specifically designed for better human usability. + +1. Human-friendly alphabet: Excludes visually similar characters to reduce human error: + * No 0 (zero) vs O (capital o) confusion + * No I (capital i) vs l (lowercase L) confusion + * No non-alphanumeric characters like + or = +2. URL-safe: Contains only alphanumeric characters, making it safe for URLs without encoding. + +Initially based on [storyicon's implementation](https://github.com/storyicon/base58-solidity/commit/807428e5174e61867e4c606bdb26cba58a8c5cb1) (MIT). +Based on the updated and improved [Vectorized version](https://github.com/Vectorized/solady/blob/208e4f31cfae26e4983eb95c3488a14fdc497ad7/src/utils/Base58.sol) (MIT). + +
+

Functions

+
+- [encode(input)](#Base58-encode-bytes-) +- [decode(input)](#Base58-decode-string-) +
+
+ +
+

Errors

+
+- [InvalidBase58Char()](#Base58-InvalidBase58Char-bytes1-) +
+
+ + + +
+
+

encode(bytes input) → string

+
+

internal

+# +
+
+
+ +Encode a `bytes` buffer as a Base58 `string`. + +
+
+ + + +
+
+

decode(string input) → bytes

+
+

internal

+# +
+
+
+ +Decode a Base58 `string` into a `bytes` buffer. + +
+
+ + + +
+
+

InvalidBase58Char(bytes1)

+
+

error

+# +
+
+
+ +Unrecognized Base58 character on decoding. + +
+
+ + + +
+ +## `Base64` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Base64.sol"; +``` + +Provides a set of functions to operate with Base64 strings. + +
+

Functions

+
+- [encode(data)](#Base64-encode-bytes-) +- [encodeURL(data)](#Base64-encodeURL-bytes-) +- [decode(data)](#Base64-decode-string-) +
+
+ +
+

Errors

+
+- [InvalidBase64Char()](#Base64-InvalidBase64Char-bytes1-) +
+
+ + + +
+
+

encode(bytes data) → string

+
+

internal

+# +
+
+
+ +Converts a `bytes` to its Base64 `string` representation. + +
+
+ + + +
+
+

encodeURL(bytes data) → string

+
+

internal

+# +
+
+
+ +Converts a `bytes` to its Base64Url `string` representation. +Output is not padded with `=` as specified in [rfc4648](https://www.rfc-editor.org/rfc/rfc4648). + +
+
+ + + +
+
+

decode(string data) → bytes

+
+

internal

+# +
+
+
+ +Converts a Base64 `string` to the `bytes` it represents. + +* Supports padded and unpadded inputs. +* Supports both encoding ([`Base58.encode`](#Base58-encode-bytes-) and [`Base64.encodeURL`](#Base64-encodeURL-bytes-)) seamlessly. +* Does NOT revert if the input is not a valid Base64 string. + +
+
+ + + +
+
+

InvalidBase64Char(bytes1)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `Blockhash` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Blockhash.sol"; +``` + +Library for accessing historical block hashes beyond the standard 256 block limit. +Uses EIP-2935's history storage contract which maintains a ring buffer of the last +8191 block hashes in state. + +For blocks within the last 256 blocks, it uses the native `BLOCKHASH` opcode. +For blocks between 257 and 8191 blocks ago, it queries the EIP-2935 history storage. +For blocks older than 8191 or future blocks, it returns zero, matching the `BLOCKHASH` behavior. + + +After EIP-2935 activation, it takes 8191 blocks to completely fill the history. +Before that, only block hashes since the fork block will be available. + + +
+

Functions

+
+- [blockHash(blockNumber)](#Blockhash-blockHash-uint256-) +
+
+ + + +
+
+

blockHash(uint256 blockNumber) → bytes32

+
+

internal

+# +
+
+
+ +Retrieves the block hash for any historical block within the supported range. + + +The function gracefully handles future blocks and blocks beyond the history window +by returning zero, consistent with the EVM's native `BLOCKHASH` behavior. + + +
+
+ + + +
+ +## `Bytes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Bytes.sol"; +``` + +Bytes operations. + +
+

Functions

+
+- [indexOf(buffer, s)](#Bytes-indexOf-bytes-bytes1-) +- [indexOf(buffer, s, pos)](#Bytes-indexOf-bytes-bytes1-uint256-) +- [lastIndexOf(buffer, s)](#Bytes-lastIndexOf-bytes-bytes1-) +- [lastIndexOf(buffer, s, pos)](#Bytes-lastIndexOf-bytes-bytes1-uint256-) +- [slice(buffer, start)](#Bytes-slice-bytes-uint256-) +- [slice(buffer, start, end)](#Bytes-slice-bytes-uint256-uint256-) +- [splice(buffer, start)](#Bytes-splice-bytes-uint256-) +- [splice(buffer, start, end)](#Bytes-splice-bytes-uint256-uint256-) +- [concat(buffers)](#Bytes-concat-bytes---) +- [equal(a, b)](#Bytes-equal-bytes-bytes-) +- [reverseBytes32(value)](#Bytes-reverseBytes32-bytes32-) +- [reverseBytes16(value)](#Bytes-reverseBytes16-bytes16-) +- [reverseBytes8(value)](#Bytes-reverseBytes8-bytes8-) +- [reverseBytes4(value)](#Bytes-reverseBytes4-bytes4-) +- [reverseBytes2(value)](#Bytes-reverseBytes2-bytes2-) +- [clz(buffer)](#Bytes-clz-bytes-) +
+
+ + + +
+
+

indexOf(bytes buffer, bytes1 s) → uint256

+
+

internal

+# +
+
+
+ +Forward search for `s` in `buffer` +* If `s` is present in the buffer, returns the index of the first instance +* If `s` is not present in the buffer, returns type(uint256).max + + +replicates the behavior of [Javascript's `Array.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) + + +
+
+ + + +
+
+

indexOf(bytes buffer, bytes1 s, uint256 pos) → uint256

+
+

internal

+# +
+
+
+ +Forward search for `s` in `buffer` starting at position `pos` +* If `s` is present in the buffer (at or after `pos`), returns the index of the next instance +* If `s` is not present in the buffer (at or after `pos`), returns type(uint256).max + + +replicates the behavior of [Javascript's `Array.indexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf) + + +
+
+ + + +
+
+

lastIndexOf(bytes buffer, bytes1 s) → uint256

+
+

internal

+# +
+
+
+ +Backward search for `s` in `buffer` +* If `s` is present in the buffer, returns the index of the last instance +* If `s` is not present in the buffer, returns type(uint256).max + + +replicates the behavior of [Javascript's `Array.lastIndexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf) + + +
+
+ + + +
+
+

lastIndexOf(bytes buffer, bytes1 s, uint256 pos) → uint256

+
+

internal

+# +
+
+
+ +Backward search for `s` in `buffer` starting at position `pos` +* If `s` is present in the buffer (at or before `pos`), returns the index of the previous instance +* If `s` is not present in the buffer (at or before `pos`), returns type(uint256).max + + +replicates the behavior of [Javascript's `Array.lastIndexOf`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf) + + +
+
+ + + +
+
+

slice(bytes buffer, uint256 start) → bytes

+
+

internal

+# +
+
+
+ +Copies the content of `buffer`, from `start` (included) to the end of `buffer` into a new bytes object in +memory. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

slice(bytes buffer, uint256 start, uint256 end) → bytes

+
+

internal

+# +
+
+
+ +Copies the content of `buffer`, from `start` (included) to `end` (excluded) into a new bytes object in +memory. The `end` argument is truncated to the length of the `buffer`. + + +replicates the behavior of [Javascript's `Array.slice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) + + +
+
+ + + +
+
+

splice(bytes buffer, uint256 start) → bytes

+
+

internal

+# +
+
+
+ +Moves the content of `buffer`, from `start` (included) to the end of `buffer` to the start of that buffer. + + +This function modifies the provided buffer in place. If you need to preserve the original buffer, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

splice(bytes buffer, uint256 start, uint256 end) → bytes

+
+

internal

+# +
+
+
+ +Moves the content of `buffer`, from `start` (included) to end (excluded) to the start of that buffer. The +`end` argument is truncated to the length of the `buffer`. + + +This function modifies the provided buffer in place. If you need to preserve the original buffer, use [`Arrays.slice`](#Arrays-slice-uint256---uint256-uint256-) instead + + +replicates the behavior of [Javascript's `Array.splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) + + +
+
+ + + +
+
+

concat(bytes[] buffers) → bytes

+
+

internal

+# +
+
+
+ +Concatenate an array of bytes into a single bytes object. + +For fixed bytes types, we recommend using the solidity built-in `bytes.concat` or (equivalent) +`abi.encodePacked`. + + +this could be done in assembly with a single loop that expands starting at the FMP, but that would be +significantly less readable. It might be worth benchmarking the savings of the full-assembly approach. + + +
+
+ + + +
+
+

equal(bytes a, bytes b) → bool

+
+

internal

+# +
+
+
+ +Returns true if the two byte buffers are equal. + +
+
+ + + +
+
+

reverseBytes32(bytes32 value) → bytes32

+
+

internal

+# +
+
+
+ +Reverses the byte order of a bytes32 value, converting between little-endian and big-endian. +Inspired by [Reverse Parallel](https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel) + +
+
+ + + +
+
+

reverseBytes16(bytes16 value) → bytes16

+
+

internal

+# +
+
+
+ +Same as [`Bytes.reverseBytes32`](#Bytes-reverseBytes32-bytes32-) but optimized for 128-bit values. + +
+
+ + + +
+
+

reverseBytes8(bytes8 value) → bytes8

+
+

internal

+# +
+
+
+ +Same as [`Bytes.reverseBytes32`](#Bytes-reverseBytes32-bytes32-) but optimized for 64-bit values. + +
+
+ + + +
+
+

reverseBytes4(bytes4 value) → bytes4

+
+

internal

+# +
+
+
+ +Same as [`Bytes.reverseBytes32`](#Bytes-reverseBytes32-bytes32-) but optimized for 32-bit values. + +
+
+ + + +
+
+

reverseBytes2(bytes2 value) → bytes2

+
+

internal

+# +
+
+
+ +Same as [`Bytes.reverseBytes32`](#Bytes-reverseBytes32-bytes32-) but optimized for 16-bit values. + +
+
+ + + +
+
+

clz(bytes buffer) → uint256

+
+

internal

+# +
+
+
+ +Counts the number of leading zero bits a bytes array. Returns `8 * buffer.length` +if the buffer is all zeros. + +
+
+ + + +
+ +## `CAIP10` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/CAIP10.sol"; +``` + +Helper library to format and parse CAIP-10 identifiers + +[CAIP-10](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md) defines account identifiers as: +account_id: chain_id + ":" + account_address +chain_id: [-a-z0-9]{3,8}:[-_a-zA-Z0-9]{1,32} (See [`CAIP2`](#CAIP2)) +account_address: [-.%a-zA-Z0-9]{1,128} + + +According to [CAIP-10's canonicalization section](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md#canonicalization), +the implementation remains at the developer's discretion. Please note that case variations may introduce ambiguity. +For example, when building hashes to identify accounts or data associated to them, multiple representations of the +same account would derive to different hashes. For EVM chains, we recommend using checksummed addresses for the +"account_address" part. They can be generated onchain using [`Strings.toChecksumHexString`](#Strings-toChecksumHexString-address-). + + +
+

Functions

+
+- [local(account)](#CAIP10-local-address-) +- [format(caip2, account)](#CAIP10-format-string-string-) +- [parse(caip10)](#CAIP10-parse-string-) +
+
+ + + +
+
+

local(address account) → string

+
+

internal

+# +
+
+
+ +Return the CAIP-10 identifier for an account on the current (local) chain. + +
+
+ + + +
+
+

format(string caip2, string account) → string

+
+

internal

+# +
+
+
+ +Return the CAIP-10 identifier for a given caip2 chain and account. + + +This function does not verify that the inputs are properly formatted. + + +
+
+ + + +
+
+

parse(string caip10) → string caip2, string account

+
+

internal

+# +
+
+
+ +Parse a CAIP-10 identifier into its components. + + +This function does not verify that the CAIP-10 input is properly formatted. The `caip2` return can be +parsed using the [`CAIP2`](#CAIP2) library. + + +
+
+ + + +
+ +## `CAIP2` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/CAIP2.sol"; +``` + +Helper library to format and parse CAIP-2 identifiers + +[CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md) defines chain identifiers as: +chain_id: namespace + ":" + reference +namespace: [-a-z0-9]{3,8} +reference: [-_a-zA-Z0-9]{1,32} + + +In some cases, multiple CAIP-2 identifiers may all be valid representation of a single chain. +For EVM chains, it is recommended to use `eip155:xxx` as the canonical representation (where `xxx` is +the EIP-155 chain id). Consider the possible ambiguity when processing CAIP-2 identifiers or when using them +in the context of hashes. + + +
+

Functions

+
+- [local()](#CAIP2-local--) +- [format(namespace, ref)](#CAIP2-format-string-string-) +- [parse(caip2)](#CAIP2-parse-string-) +
+
+ + + +
+
+

local() → string

+
+

internal

+# +
+
+
+ +Return the CAIP-2 identifier for the current (local) chain. + +
+
+ + + +
+
+

format(string namespace, string ref) → string

+
+

internal

+# +
+
+
+ +Return the CAIP-2 identifier for a given namespace and reference. + + +This function does not verify that the inputs are properly formatted. + + +
+
+ + + +
+
+

parse(string caip2) → string namespace, string ref

+
+

internal

+# +
+
+
+ +Parse a CAIP-2 identifier into its components. + + +This function does not verify that the CAIP-2 input is properly formatted. + + +
+
+ + + +
+ +## `Calldata` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Calldata.sol"; +``` + +Helper library for manipulating objects in calldata. + +
+

Functions

+
+- [emptyBytes()](#Calldata-emptyBytes--) +- [emptyString()](#Calldata-emptyString--) +
+
+ + + +
+
+

emptyBytes() → bytes result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

emptyString() → string result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `Comparators` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Comparators.sol"; +``` + +Provides a set of functions to compare values. + +_Available since v5.1._ + +
+

Functions

+
+- [lt(a, b)](#Comparators-lt-uint256-uint256-) +- [gt(a, b)](#Comparators-gt-uint256-uint256-) +
+
+ + + +
+
+

lt(uint256 a, uint256 b) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

gt(uint256 a, uint256 b) → bool

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `Context` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Context.sol"; +``` + +Provides information about the current execution context, including the +sender of the transaction and its data. While these are generally available +via msg.sender and msg.data, they should not be accessed in such a direct +manner, since when dealing with meta-transactions the account sending and +paying for execution may not be the actual sender (as far as an application +is concerned). + +This contract is only required for intermediate, library-like contracts. + +
+

Functions

+
+- [_msgSender()](#Context-_msgSender--) +- [_msgData()](#Context-_msgData--) +- [_contextSuffixLength()](#Context-_contextSuffixLength--) +
+
+ + + +
+
+

_msgSender() → address

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_msgData() → bytes

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_contextSuffixLength() → uint256

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `Create2` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Create2.sol"; +``` + +Helper to make usage of the `CREATE2` EVM opcode easier and safer. +`CREATE2` can be used to compute in advance the address where a smart +contract will be deployed, which allows for interesting new mechanisms known +as 'counterfactual interactions'. + +See the [EIP](https://eips.ethereum.org/EIPS/eip-1014#motivation) for more +information. + +
+

Functions

+
+- [deploy(amount, salt, bytecode)](#Create2-deploy-uint256-bytes32-bytes-) +- [computeAddress(salt, bytecodeHash)](#Create2-computeAddress-bytes32-bytes32-) +- [computeAddress(salt, bytecodeHash, deployer)](#Create2-computeAddress-bytes32-bytes32-address-) +
+
+ +
+

Errors

+
+- [Create2EmptyBytecode()](#Create2-Create2EmptyBytecode--) +
+
+ + + +
+
+

deploy(uint256 amount, bytes32 salt, bytes bytecode) → address addr

+
+

internal

+# +
+
+
+ +Deploys a contract using `CREATE2`. The address where the contract +will be deployed can be known in advance via [`Create2.computeAddress`](#Create2-computeAddress-bytes32-bytes32-address-). + +The bytecode for a contract can be obtained from Solidity with +`type(contractName).creationCode`. + +Requirements: + +- `bytecode` must not be empty. +- `salt` must have not been used for `bytecode` already. +- the factory must have a balance of at least `amount`. +- if `amount` is non-zero, `bytecode` must have a `payable` constructor. + +
+
+ + + +
+
+

computeAddress(bytes32 salt, bytes32 bytecodeHash) → address

+
+

internal

+# +
+
+
+ +Returns the address where a contract will be stored if deployed via [`Create2.deploy`](#Create2-deploy-uint256-bytes32-bytes-). Any change in the +`bytecodeHash` or `salt` will result in a new destination address. + +
+
+ + + +
+
+

computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) → address addr

+
+

internal

+# +
+
+
+ +Returns the address where a contract will be stored if deployed via [`Create2.deploy`](#Create2-deploy-uint256-bytes32-bytes-) from a contract located at +`deployer`. If `deployer` is this contract's address, returns the same value as [`Create2.computeAddress`](#Create2-computeAddress-bytes32-bytes32-address-). + +
+
+ + + +
+
+

Create2EmptyBytecode()

+
+

error

+# +
+
+
+ +There's no code to deploy. + +
+
+ + + +
+ +## `Errors` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Errors.sol"; +``` + +Collection of common custom errors used in multiple contracts + + +Backwards compatibility is not guaranteed in future versions of the library. +It is recommended to avoid relying on the error API for critical functionality. + + +_Available since v5.1._ + +
+

Errors

+
+- [InsufficientBalance(balance, needed)](#Errors-InsufficientBalance-uint256-uint256-) +- [FailedCall()](#Errors-FailedCall--) +- [FailedDeployment()](#Errors-FailedDeployment--) +- [MissingPrecompile()](#Errors-MissingPrecompile-address-) +
+
+ + + +
+
+

InsufficientBalance(uint256 balance, uint256 needed)

+
+

error

+# +
+
+
+ +The ETH balance of the account is not enough to perform the operation. + +
+
+ + + +
+
+

FailedCall()

+
+

error

+# +
+
+
+ +A call to an address target failed. The target may have reverted. + +
+
+ + + +
+
+

FailedDeployment()

+
+

error

+# +
+
+
+ +The deployment failed. + +
+
+ + + +
+
+

MissingPrecompile(address)

+
+

error

+# +
+
+
+ +A necessary precompile is missing. + +
+
+ + + +
+ +## `LowLevelCall` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/LowLevelCall.sol"; +``` + +Library of low level call functions that implement different calling strategies to deal with the return data. + + +Using this library requires an advanced understanding of Solidity and how the EVM works. It is recommended +to use the [`Address`](#Address) library instead. + + +
+

Functions

+
+- [callNoReturn(target, data)](#LowLevelCall-callNoReturn-address-bytes-) +- [callNoReturn(target, value, data)](#LowLevelCall-callNoReturn-address-uint256-bytes-) +- [callReturn64Bytes(target, data)](#LowLevelCall-callReturn64Bytes-address-bytes-) +- [callReturn64Bytes(target, value, data)](#LowLevelCall-callReturn64Bytes-address-uint256-bytes-) +- [staticcallNoReturn(target, data)](#LowLevelCall-staticcallNoReturn-address-bytes-) +- [staticcallReturn64Bytes(target, data)](#LowLevelCall-staticcallReturn64Bytes-address-bytes-) +- [delegatecallNoReturn(target, data)](#LowLevelCall-delegatecallNoReturn-address-bytes-) +- [delegatecallReturn64Bytes(target, data)](#LowLevelCall-delegatecallReturn64Bytes-address-bytes-) +- [returnDataSize()](#LowLevelCall-returnDataSize--) +- [returnData()](#LowLevelCall-returnData--) +- [bubbleRevert()](#LowLevelCall-bubbleRevert--) +- [bubbleRevert(returndata)](#LowLevelCall-bubbleRevert-bytes-) +
+
+ + + +
+
+

callNoReturn(address target, bytes data) → bool success

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `call` and ignoring the return data. + +
+
+ + + +
+
+

callNoReturn(address target, uint256 value, bytes data) → bool success

+
+

internal

+# +
+
+
+ +Same as [`LowLevelCall.callNoReturn`](#LowLevelCall-callNoReturn-address-uint256-bytes-), but allows to specify the value to be sent in the call. + +
+
+ + + +
+
+

callReturn64Bytes(address target, bytes data) → bool success, bytes32 result1, bytes32 result2

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `call` and returns the first 64 bytes of the result +in the scratch space of memory. Useful for functions that return a tuple of single-word values. + + +Do not assume that the results are zero if `success` is false. Memory can be already allocated +and this function doesn't zero it out. + + +
+
+ + + +
+
+

callReturn64Bytes(address target, uint256 value, bytes data) → bool success, bytes32 result1, bytes32 result2

+
+

internal

+# +
+
+
+ +Same as `callReturnBytes32Pair`, but allows to specify the value to be sent in the call. + +
+
+ + + +
+
+

staticcallNoReturn(address target, bytes data) → bool success

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `staticcall` and ignoring the return data. + +
+
+ + + +
+
+

staticcallReturn64Bytes(address target, bytes data) → bool success, bytes32 result1, bytes32 result2

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `staticcall` and returns the first 64 bytes of the result +in the scratch space of memory. Useful for functions that return a tuple of single-word values. + + +Do not assume that the results are zero if `success` is false. Memory can be already allocated +and this function doesn't zero it out. + + +
+
+ + + +
+
+

delegatecallNoReturn(address target, bytes data) → bool success

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `delegatecall` and ignoring the return data. + +
+
+ + + +
+
+

delegatecallReturn64Bytes(address target, bytes data) → bool success, bytes32 result1, bytes32 result2

+
+

internal

+# +
+
+
+ +Performs a Solidity function call using a low level `delegatecall` and returns the first 64 bytes of the result +in the scratch space of memory. Useful for functions that return a tuple of single-word values. + + +Do not assume that the results are zero if `success` is false. Memory can be already allocated +and this function doesn't zero it out. + + +
+
+ + + +
+
+

returnDataSize() → uint256 size

+
+

internal

+# +
+
+
+ +Returns the size of the return data buffer. + +
+
+ + + +
+
+

returnData() → bytes result

+
+

internal

+# +
+
+
+ +Returns a buffer containing the return data from the last call. + +
+
+ + + +
+
+

bubbleRevert()

+
+

internal

+# +
+
+
+ +Revert with the return data from the last call. + +
+
+ + + +
+
+

bubbleRevert(bytes returndata)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+ +## `Memory` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Memory.sol"; +``` + +Utilities to manipulate memory. + +Memory is a contiguous and dynamic byte array in which Solidity stores non-primitive types. +This library provides functions to manipulate pointers to this dynamic array and work with slices of it. + +Slices provide a view into a portion of memory without copying data, enabling efficient substring operations. + + +When manipulating memory pointers or slices, make sure to follow the Solidity documentation +guidelines for [Memory Safety](https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety). + + +
+

Functions

+
+- [getFreeMemoryPointer()](#Memory-getFreeMemoryPointer--) +- [setFreeMemoryPointer(ptr)](#Memory-setFreeMemoryPointer-Memory-Pointer-) +- [asBytes32(ptr)](#Memory-asBytes32-Memory-Pointer-) +- [asPointer(value)](#Memory-asPointer-bytes32-) +- [forward(ptr, offset)](#Memory-forward-Memory-Pointer-uint256-) +- [equal(ptr1, ptr2)](#Memory-equal-Memory-Pointer-Memory-Pointer-) +- [asSlice(self)](#Memory-asSlice-bytes-) +- [length(self)](#Memory-length-Memory-Slice-) +- [slice(self, offset)](#Memory-slice-Memory-Slice-uint256-) +- [slice(self, offset, len)](#Memory-slice-Memory-Slice-uint256-uint256-) +- [load(self, offset)](#Memory-load-Memory-Slice-uint256-) +- [toBytes(self)](#Memory-toBytes-Memory-Slice-) +
+
+ + + +
+
+

getFreeMemoryPointer() → Memory.Pointer ptr

+
+

internal

+# +
+
+
+ +Returns a `Pointer` to the current free `Pointer`. + +
+
+ + + +
+
+

setFreeMemoryPointer(Memory.Pointer ptr)

+
+

internal

+# +
+
+
+ +Sets the free `Pointer` to a specific value. + + +Everything after the pointer may be overwritten. + + +
+
+ + + +
+
+

asBytes32(Memory.Pointer ptr) → bytes32

+
+

internal

+# +
+
+
+ +`Pointer` to `bytes32`. Expects a pointer to a properly ABI-encoded `bytes` object. + +
+
+ + + +
+
+

asPointer(bytes32 value) → Memory.Pointer

+
+

internal

+# +
+
+
+ +`bytes32` to `Pointer`. Expects a pointer to a properly ABI-encoded `bytes` object. + +
+
+ + + +
+
+

forward(Memory.Pointer ptr, uint256 offset) → Memory.Pointer

+
+

internal

+# +
+
+
+ +Move a pointer forward by a given offset. + +
+
+ + + +
+
+

equal(Memory.Pointer ptr1, Memory.Pointer ptr2) → bool

+
+

internal

+# +
+
+
+ +Equality comparator for memory pointers. + +
+
+ + + +
+
+

asSlice(bytes self) → Memory.Slice result

+
+

internal

+# +
+
+
+ +Get a slice representation of a bytes object in memory + +
+
+ + + +
+
+

length(Memory.Slice self) → uint256 result

+
+

internal

+# +
+
+
+ +Returns the length of a given slice (equiv to self.length for calldata slices) + +
+
+ + + +
+
+

slice(Memory.Slice self, uint256 offset) → Memory.Slice

+
+

internal

+# +
+
+
+ +Offset a memory slice (equivalent to self[start:] for calldata slices) + +
+
+ + + +
+
+

slice(Memory.Slice self, uint256 offset, uint256 len) → Memory.Slice

+
+

internal

+# +
+
+
+ +Offset and cut a Slice (equivalent to self[start:start+length] for calldata slices) + +
+
+ + + +
+
+

load(Memory.Slice self, uint256 offset) → bytes32 value

+
+

internal

+# +
+
+
+ +Read a bytes32 buffer from a given Slice at a specific offset + + +If offset > length(slice) - 0x20, part of the return value will be out of bound of the slice. These bytes are zeroed. + + +
+
+ + + +
+
+

toBytes(Memory.Slice self) → bytes result

+
+

internal

+# +
+
+
+ +Extract the data corresponding to a Slice (allocate new memory) + +
+
+ + + +
+ +## `Multicall` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Multicall.sol"; +``` + +Provides a function to batch together multiple calls in a single external call. + +Consider any assumption about calldata validation performed by the sender may be violated if it's not especially +careful about sending transactions invoking [`Multicall.multicall`](#Multicall-multicall-bytes---). For example, a relay address that filters function +selectors won't filter calls nested within a [`Multicall.multicall`](#Multicall-multicall-bytes---) operation. + + +Since 5.0.1 and 4.9.4, this contract identifies non-canonical contexts (i.e. `msg.sender` is not [`Context._msgSender`](#Context-_msgSender--)). +If a non-canonical context is identified, the following self `delegatecall` appends the last bytes of `msg.data` +to the subcall. This makes it safe to use with [`ERC2771Context`](/contracts/5.x/api/metatx#ERC2771Context). Contexts that don't affect the resolution of +[`Context._msgSender`](#Context-_msgSender--) are not propagated to subcalls. + + +
+

Functions

+
+- [multicall(data)](#Multicall-multicall-bytes---) +
+
+ + + +
+
+

multicall(bytes[] data) → bytes[] results

+
+

public

+# +
+
+
+ +Receives and executes a batch of function calls on this contract. + +
+
+ + + +
+ +## `Nonces` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Nonces.sol"; +``` + +Provides tracking nonces for addresses. Nonces will only increment. + +
+

Functions

+
+- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +- [_useCheckedNonce(owner, nonce)](#Nonces-_useCheckedNonce-address-uint256-) +
+
+ +
+

Errors

+
+- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +
+
+ + + +
+
+

nonces(address owner) → uint256

+
+

public

+# +
+
+
+ +Returns the next unused nonce for an address. + +
+
+ + + +
+
+

_useNonce(address owner) → uint256

+
+

internal

+# +
+
+
+ +Consumes a nonce. + +Returns the current value and increments nonce. + +
+
+ + + +
+
+

_useCheckedNonce(address owner, uint256 nonce)

+
+

internal

+# +
+
+
+ +Same as [`Nonces._useNonce`](#Nonces-_useNonce-address-) but checking that `nonce` is the next valid for `owner`. + +
+
+ + + +
+
+

InvalidAccountNonce(address account, uint256 currentNonce)

+
+

error

+# +
+
+
+ +The nonce used for an `account` is not the expected current nonce. + +
+
+ + + +
+ +## `NoncesKeyed` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/NoncesKeyed.sol"; +``` + +Alternative to [`Nonces`](#Nonces), that supports key-ed nonces. + +Follows the [ERC-4337's semi-abstracted nonce system](https://eips.ethereum.org/EIPS/eip-4337#semi-abstracted-nonce-support). + + +This contract inherits from [`Nonces`](#Nonces) and reuses its storage for the first nonce key (i.e. `0`). This +makes upgrading from [`Nonces`](#Nonces) to [`NoncesKeyed`](#NoncesKeyed) safe when using their upgradeable versions (e.g. `NoncesKeyedUpgradeable`). +Doing so will NOT reset the current state of nonces, avoiding replay attacks where a nonce is reused after the upgrade. + + +
+

Functions

+
+- [nonces(owner, key)](#NoncesKeyed-nonces-address-uint192-) +- [_useNonce(owner, key)](#NoncesKeyed-_useNonce-address-uint192-) +- [_useCheckedNonce(owner, keyNonce)](#NoncesKeyed-_useCheckedNonce-address-uint256-) +- [_useCheckedNonce(owner, key, nonce)](#NoncesKeyed-_useCheckedNonce-address-uint192-uint64-) +#### Nonces [!toc] +- [nonces(owner)](#Nonces-nonces-address-) +- [_useNonce(owner)](#Nonces-_useNonce-address-) +
+
+ +
+

Errors

+
+#### Nonces [!toc] +- [InvalidAccountNonce(account, currentNonce)](#Nonces-InvalidAccountNonce-address-uint256-) +
+
+ + + +
+
+

nonces(address owner, uint192 key) → uint256

+
+

public

+# +
+
+
+ +Returns the next unused nonce for an address and key. Result contains the key prefix. + +
+
+ + + +
+
+

_useNonce(address owner, uint192 key) → uint256

+
+

internal

+# +
+
+
+ +Consumes the next unused nonce for an address and key. + +Returns the current value without the key prefix. Consumed nonce is increased, so calling this function twice +with the same arguments will return different (sequential) results. + +
+
+ + + +
+
+

_useCheckedNonce(address owner, uint256 keyNonce)

+
+

internal

+# +
+
+
+ +Same as [`Nonces._useNonce`](#Nonces-_useNonce-address-) but checking that `nonce` is the next valid for `owner`. + +This version takes the key and the nonce in a single uint256 parameter: +- use the first 24 bytes for the key +- use the last 8 bytes for the nonce + +
+
+ + + +
+
+

_useCheckedNonce(address owner, uint192 key, uint64 nonce)

+
+

internal

+# +
+
+
+ +Same as [`Nonces._useNonce`](#Nonces-_useNonce-address-) but checking that `nonce` is the next valid for `owner`. + +This version takes the key and the nonce as two different parameters. + +
+
+ + + +
+ +## `Packing` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Packing.sol"; +``` + +Helper library packing and unpacking multiple values into bytesXX. + +Example usage: + +```solidity +library MyPacker { + type MyType is bytes32; + + function _pack(address account, bytes4 selector, uint64 period) external pure returns (MyType) { + bytes12 subpack = Packing.pack_4_8(selector, bytes8(period)); + bytes32 pack = Packing.pack_20_12(bytes20(account), subpack); + return MyType.wrap(pack); + } + + function _unpack(MyType self) external pure returns (address, bytes4, uint64) { + bytes32 pack = MyType.unwrap(self); + return ( + address(Packing.extract_32_20(pack, 0)), + Packing.extract_32_4(pack, 20), + uint64(Packing.extract_32_8(pack, 24)) + ); + } +} +``` + +_Available since v5.1._ + +
+

Functions

+
+- [pack_1_1(left, right)](#Packing-pack_1_1-bytes1-bytes1-) +- [pack_2_2(left, right)](#Packing-pack_2_2-bytes2-bytes2-) +- [pack_2_4(left, right)](#Packing-pack_2_4-bytes2-bytes4-) +- [pack_2_6(left, right)](#Packing-pack_2_6-bytes2-bytes6-) +- [pack_2_8(left, right)](#Packing-pack_2_8-bytes2-bytes8-) +- [pack_2_10(left, right)](#Packing-pack_2_10-bytes2-bytes10-) +- [pack_2_20(left, right)](#Packing-pack_2_20-bytes2-bytes20-) +- [pack_2_22(left, right)](#Packing-pack_2_22-bytes2-bytes22-) +- [pack_4_2(left, right)](#Packing-pack_4_2-bytes4-bytes2-) +- [pack_4_4(left, right)](#Packing-pack_4_4-bytes4-bytes4-) +- [pack_4_6(left, right)](#Packing-pack_4_6-bytes4-bytes6-) +- [pack_4_8(left, right)](#Packing-pack_4_8-bytes4-bytes8-) +- [pack_4_12(left, right)](#Packing-pack_4_12-bytes4-bytes12-) +- [pack_4_16(left, right)](#Packing-pack_4_16-bytes4-bytes16-) +- [pack_4_20(left, right)](#Packing-pack_4_20-bytes4-bytes20-) +- [pack_4_24(left, right)](#Packing-pack_4_24-bytes4-bytes24-) +- [pack_4_28(left, right)](#Packing-pack_4_28-bytes4-bytes28-) +- [pack_6_2(left, right)](#Packing-pack_6_2-bytes6-bytes2-) +- [pack_6_4(left, right)](#Packing-pack_6_4-bytes6-bytes4-) +- [pack_6_6(left, right)](#Packing-pack_6_6-bytes6-bytes6-) +- [pack_6_10(left, right)](#Packing-pack_6_10-bytes6-bytes10-) +- [pack_6_16(left, right)](#Packing-pack_6_16-bytes6-bytes16-) +- [pack_6_22(left, right)](#Packing-pack_6_22-bytes6-bytes22-) +- [pack_8_2(left, right)](#Packing-pack_8_2-bytes8-bytes2-) +- [pack_8_4(left, right)](#Packing-pack_8_4-bytes8-bytes4-) +- [pack_8_8(left, right)](#Packing-pack_8_8-bytes8-bytes8-) +- [pack_8_12(left, right)](#Packing-pack_8_12-bytes8-bytes12-) +- [pack_8_16(left, right)](#Packing-pack_8_16-bytes8-bytes16-) +- [pack_8_20(left, right)](#Packing-pack_8_20-bytes8-bytes20-) +- [pack_8_24(left, right)](#Packing-pack_8_24-bytes8-bytes24-) +- [pack_10_2(left, right)](#Packing-pack_10_2-bytes10-bytes2-) +- [pack_10_6(left, right)](#Packing-pack_10_6-bytes10-bytes6-) +- [pack_10_10(left, right)](#Packing-pack_10_10-bytes10-bytes10-) +- [pack_10_12(left, right)](#Packing-pack_10_12-bytes10-bytes12-) +- [pack_10_22(left, right)](#Packing-pack_10_22-bytes10-bytes22-) +- [pack_12_4(left, right)](#Packing-pack_12_4-bytes12-bytes4-) +- [pack_12_8(left, right)](#Packing-pack_12_8-bytes12-bytes8-) +- [pack_12_10(left, right)](#Packing-pack_12_10-bytes12-bytes10-) +- [pack_12_12(left, right)](#Packing-pack_12_12-bytes12-bytes12-) +- [pack_12_16(left, right)](#Packing-pack_12_16-bytes12-bytes16-) +- [pack_12_20(left, right)](#Packing-pack_12_20-bytes12-bytes20-) +- [pack_16_4(left, right)](#Packing-pack_16_4-bytes16-bytes4-) +- [pack_16_6(left, right)](#Packing-pack_16_6-bytes16-bytes6-) +- [pack_16_8(left, right)](#Packing-pack_16_8-bytes16-bytes8-) +- [pack_16_12(left, right)](#Packing-pack_16_12-bytes16-bytes12-) +- [pack_16_16(left, right)](#Packing-pack_16_16-bytes16-bytes16-) +- [pack_20_2(left, right)](#Packing-pack_20_2-bytes20-bytes2-) +- [pack_20_4(left, right)](#Packing-pack_20_4-bytes20-bytes4-) +- [pack_20_8(left, right)](#Packing-pack_20_8-bytes20-bytes8-) +- [pack_20_12(left, right)](#Packing-pack_20_12-bytes20-bytes12-) +- [pack_22_2(left, right)](#Packing-pack_22_2-bytes22-bytes2-) +- [pack_22_6(left, right)](#Packing-pack_22_6-bytes22-bytes6-) +- [pack_22_10(left, right)](#Packing-pack_22_10-bytes22-bytes10-) +- [pack_24_4(left, right)](#Packing-pack_24_4-bytes24-bytes4-) +- [pack_24_8(left, right)](#Packing-pack_24_8-bytes24-bytes8-) +- [pack_28_4(left, right)](#Packing-pack_28_4-bytes28-bytes4-) +- [extract_2_1(self, offset)](#Packing-extract_2_1-bytes2-uint8-) +- [replace_2_1(self, value, offset)](#Packing-replace_2_1-bytes2-bytes1-uint8-) +- [extract_4_1(self, offset)](#Packing-extract_4_1-bytes4-uint8-) +- [replace_4_1(self, value, offset)](#Packing-replace_4_1-bytes4-bytes1-uint8-) +- [extract_4_2(self, offset)](#Packing-extract_4_2-bytes4-uint8-) +- [replace_4_2(self, value, offset)](#Packing-replace_4_2-bytes4-bytes2-uint8-) +- [extract_6_1(self, offset)](#Packing-extract_6_1-bytes6-uint8-) +- [replace_6_1(self, value, offset)](#Packing-replace_6_1-bytes6-bytes1-uint8-) +- [extract_6_2(self, offset)](#Packing-extract_6_2-bytes6-uint8-) +- [replace_6_2(self, value, offset)](#Packing-replace_6_2-bytes6-bytes2-uint8-) +- [extract_6_4(self, offset)](#Packing-extract_6_4-bytes6-uint8-) +- [replace_6_4(self, value, offset)](#Packing-replace_6_4-bytes6-bytes4-uint8-) +- [extract_8_1(self, offset)](#Packing-extract_8_1-bytes8-uint8-) +- [replace_8_1(self, value, offset)](#Packing-replace_8_1-bytes8-bytes1-uint8-) +- [extract_8_2(self, offset)](#Packing-extract_8_2-bytes8-uint8-) +- [replace_8_2(self, value, offset)](#Packing-replace_8_2-bytes8-bytes2-uint8-) +- [extract_8_4(self, offset)](#Packing-extract_8_4-bytes8-uint8-) +- [replace_8_4(self, value, offset)](#Packing-replace_8_4-bytes8-bytes4-uint8-) +- [extract_8_6(self, offset)](#Packing-extract_8_6-bytes8-uint8-) +- [replace_8_6(self, value, offset)](#Packing-replace_8_6-bytes8-bytes6-uint8-) +- [extract_10_1(self, offset)](#Packing-extract_10_1-bytes10-uint8-) +- [replace_10_1(self, value, offset)](#Packing-replace_10_1-bytes10-bytes1-uint8-) +- [extract_10_2(self, offset)](#Packing-extract_10_2-bytes10-uint8-) +- [replace_10_2(self, value, offset)](#Packing-replace_10_2-bytes10-bytes2-uint8-) +- [extract_10_4(self, offset)](#Packing-extract_10_4-bytes10-uint8-) +- [replace_10_4(self, value, offset)](#Packing-replace_10_4-bytes10-bytes4-uint8-) +- [extract_10_6(self, offset)](#Packing-extract_10_6-bytes10-uint8-) +- [replace_10_6(self, value, offset)](#Packing-replace_10_6-bytes10-bytes6-uint8-) +- [extract_10_8(self, offset)](#Packing-extract_10_8-bytes10-uint8-) +- [replace_10_8(self, value, offset)](#Packing-replace_10_8-bytes10-bytes8-uint8-) +- [extract_12_1(self, offset)](#Packing-extract_12_1-bytes12-uint8-) +- [replace_12_1(self, value, offset)](#Packing-replace_12_1-bytes12-bytes1-uint8-) +- [extract_12_2(self, offset)](#Packing-extract_12_2-bytes12-uint8-) +- [replace_12_2(self, value, offset)](#Packing-replace_12_2-bytes12-bytes2-uint8-) +- [extract_12_4(self, offset)](#Packing-extract_12_4-bytes12-uint8-) +- [replace_12_4(self, value, offset)](#Packing-replace_12_4-bytes12-bytes4-uint8-) +- [extract_12_6(self, offset)](#Packing-extract_12_6-bytes12-uint8-) +- [replace_12_6(self, value, offset)](#Packing-replace_12_6-bytes12-bytes6-uint8-) +- [extract_12_8(self, offset)](#Packing-extract_12_8-bytes12-uint8-) +- [replace_12_8(self, value, offset)](#Packing-replace_12_8-bytes12-bytes8-uint8-) +- [extract_12_10(self, offset)](#Packing-extract_12_10-bytes12-uint8-) +- [replace_12_10(self, value, offset)](#Packing-replace_12_10-bytes12-bytes10-uint8-) +- [extract_16_1(self, offset)](#Packing-extract_16_1-bytes16-uint8-) +- [replace_16_1(self, value, offset)](#Packing-replace_16_1-bytes16-bytes1-uint8-) +- [extract_16_2(self, offset)](#Packing-extract_16_2-bytes16-uint8-) +- [replace_16_2(self, value, offset)](#Packing-replace_16_2-bytes16-bytes2-uint8-) +- [extract_16_4(self, offset)](#Packing-extract_16_4-bytes16-uint8-) +- [replace_16_4(self, value, offset)](#Packing-replace_16_4-bytes16-bytes4-uint8-) +- [extract_16_6(self, offset)](#Packing-extract_16_6-bytes16-uint8-) +- [replace_16_6(self, value, offset)](#Packing-replace_16_6-bytes16-bytes6-uint8-) +- [extract_16_8(self, offset)](#Packing-extract_16_8-bytes16-uint8-) +- [replace_16_8(self, value, offset)](#Packing-replace_16_8-bytes16-bytes8-uint8-) +- [extract_16_10(self, offset)](#Packing-extract_16_10-bytes16-uint8-) +- [replace_16_10(self, value, offset)](#Packing-replace_16_10-bytes16-bytes10-uint8-) +- [extract_16_12(self, offset)](#Packing-extract_16_12-bytes16-uint8-) +- [replace_16_12(self, value, offset)](#Packing-replace_16_12-bytes16-bytes12-uint8-) +- [extract_20_1(self, offset)](#Packing-extract_20_1-bytes20-uint8-) +- [replace_20_1(self, value, offset)](#Packing-replace_20_1-bytes20-bytes1-uint8-) +- [extract_20_2(self, offset)](#Packing-extract_20_2-bytes20-uint8-) +- [replace_20_2(self, value, offset)](#Packing-replace_20_2-bytes20-bytes2-uint8-) +- [extract_20_4(self, offset)](#Packing-extract_20_4-bytes20-uint8-) +- [replace_20_4(self, value, offset)](#Packing-replace_20_4-bytes20-bytes4-uint8-) +- [extract_20_6(self, offset)](#Packing-extract_20_6-bytes20-uint8-) +- [replace_20_6(self, value, offset)](#Packing-replace_20_6-bytes20-bytes6-uint8-) +- [extract_20_8(self, offset)](#Packing-extract_20_8-bytes20-uint8-) +- [replace_20_8(self, value, offset)](#Packing-replace_20_8-bytes20-bytes8-uint8-) +- [extract_20_10(self, offset)](#Packing-extract_20_10-bytes20-uint8-) +- [replace_20_10(self, value, offset)](#Packing-replace_20_10-bytes20-bytes10-uint8-) +- [extract_20_12(self, offset)](#Packing-extract_20_12-bytes20-uint8-) +- [replace_20_12(self, value, offset)](#Packing-replace_20_12-bytes20-bytes12-uint8-) +- [extract_20_16(self, offset)](#Packing-extract_20_16-bytes20-uint8-) +- [replace_20_16(self, value, offset)](#Packing-replace_20_16-bytes20-bytes16-uint8-) +- [extract_22_1(self, offset)](#Packing-extract_22_1-bytes22-uint8-) +- [replace_22_1(self, value, offset)](#Packing-replace_22_1-bytes22-bytes1-uint8-) +- [extract_22_2(self, offset)](#Packing-extract_22_2-bytes22-uint8-) +- [replace_22_2(self, value, offset)](#Packing-replace_22_2-bytes22-bytes2-uint8-) +- [extract_22_4(self, offset)](#Packing-extract_22_4-bytes22-uint8-) +- [replace_22_4(self, value, offset)](#Packing-replace_22_4-bytes22-bytes4-uint8-) +- [extract_22_6(self, offset)](#Packing-extract_22_6-bytes22-uint8-) +- [replace_22_6(self, value, offset)](#Packing-replace_22_6-bytes22-bytes6-uint8-) +- [extract_22_8(self, offset)](#Packing-extract_22_8-bytes22-uint8-) +- [replace_22_8(self, value, offset)](#Packing-replace_22_8-bytes22-bytes8-uint8-) +- [extract_22_10(self, offset)](#Packing-extract_22_10-bytes22-uint8-) +- [replace_22_10(self, value, offset)](#Packing-replace_22_10-bytes22-bytes10-uint8-) +- [extract_22_12(self, offset)](#Packing-extract_22_12-bytes22-uint8-) +- [replace_22_12(self, value, offset)](#Packing-replace_22_12-bytes22-bytes12-uint8-) +- [extract_22_16(self, offset)](#Packing-extract_22_16-bytes22-uint8-) +- [replace_22_16(self, value, offset)](#Packing-replace_22_16-bytes22-bytes16-uint8-) +- [extract_22_20(self, offset)](#Packing-extract_22_20-bytes22-uint8-) +- [replace_22_20(self, value, offset)](#Packing-replace_22_20-bytes22-bytes20-uint8-) +- [extract_24_1(self, offset)](#Packing-extract_24_1-bytes24-uint8-) +- [replace_24_1(self, value, offset)](#Packing-replace_24_1-bytes24-bytes1-uint8-) +- [extract_24_2(self, offset)](#Packing-extract_24_2-bytes24-uint8-) +- [replace_24_2(self, value, offset)](#Packing-replace_24_2-bytes24-bytes2-uint8-) +- [extract_24_4(self, offset)](#Packing-extract_24_4-bytes24-uint8-) +- [replace_24_4(self, value, offset)](#Packing-replace_24_4-bytes24-bytes4-uint8-) +- [extract_24_6(self, offset)](#Packing-extract_24_6-bytes24-uint8-) +- [replace_24_6(self, value, offset)](#Packing-replace_24_6-bytes24-bytes6-uint8-) +- [extract_24_8(self, offset)](#Packing-extract_24_8-bytes24-uint8-) +- [replace_24_8(self, value, offset)](#Packing-replace_24_8-bytes24-bytes8-uint8-) +- [extract_24_10(self, offset)](#Packing-extract_24_10-bytes24-uint8-) +- [replace_24_10(self, value, offset)](#Packing-replace_24_10-bytes24-bytes10-uint8-) +- [extract_24_12(self, offset)](#Packing-extract_24_12-bytes24-uint8-) +- [replace_24_12(self, value, offset)](#Packing-replace_24_12-bytes24-bytes12-uint8-) +- [extract_24_16(self, offset)](#Packing-extract_24_16-bytes24-uint8-) +- [replace_24_16(self, value, offset)](#Packing-replace_24_16-bytes24-bytes16-uint8-) +- [extract_24_20(self, offset)](#Packing-extract_24_20-bytes24-uint8-) +- [replace_24_20(self, value, offset)](#Packing-replace_24_20-bytes24-bytes20-uint8-) +- [extract_24_22(self, offset)](#Packing-extract_24_22-bytes24-uint8-) +- [replace_24_22(self, value, offset)](#Packing-replace_24_22-bytes24-bytes22-uint8-) +- [extract_28_1(self, offset)](#Packing-extract_28_1-bytes28-uint8-) +- [replace_28_1(self, value, offset)](#Packing-replace_28_1-bytes28-bytes1-uint8-) +- [extract_28_2(self, offset)](#Packing-extract_28_2-bytes28-uint8-) +- [replace_28_2(self, value, offset)](#Packing-replace_28_2-bytes28-bytes2-uint8-) +- [extract_28_4(self, offset)](#Packing-extract_28_4-bytes28-uint8-) +- [replace_28_4(self, value, offset)](#Packing-replace_28_4-bytes28-bytes4-uint8-) +- [extract_28_6(self, offset)](#Packing-extract_28_6-bytes28-uint8-) +- [replace_28_6(self, value, offset)](#Packing-replace_28_6-bytes28-bytes6-uint8-) +- [extract_28_8(self, offset)](#Packing-extract_28_8-bytes28-uint8-) +- [replace_28_8(self, value, offset)](#Packing-replace_28_8-bytes28-bytes8-uint8-) +- [extract_28_10(self, offset)](#Packing-extract_28_10-bytes28-uint8-) +- [replace_28_10(self, value, offset)](#Packing-replace_28_10-bytes28-bytes10-uint8-) +- [extract_28_12(self, offset)](#Packing-extract_28_12-bytes28-uint8-) +- [replace_28_12(self, value, offset)](#Packing-replace_28_12-bytes28-bytes12-uint8-) +- [extract_28_16(self, offset)](#Packing-extract_28_16-bytes28-uint8-) +- [replace_28_16(self, value, offset)](#Packing-replace_28_16-bytes28-bytes16-uint8-) +- [extract_28_20(self, offset)](#Packing-extract_28_20-bytes28-uint8-) +- [replace_28_20(self, value, offset)](#Packing-replace_28_20-bytes28-bytes20-uint8-) +- [extract_28_22(self, offset)](#Packing-extract_28_22-bytes28-uint8-) +- [replace_28_22(self, value, offset)](#Packing-replace_28_22-bytes28-bytes22-uint8-) +- [extract_28_24(self, offset)](#Packing-extract_28_24-bytes28-uint8-) +- [replace_28_24(self, value, offset)](#Packing-replace_28_24-bytes28-bytes24-uint8-) +- [extract_32_1(self, offset)](#Packing-extract_32_1-bytes32-uint8-) +- [replace_32_1(self, value, offset)](#Packing-replace_32_1-bytes32-bytes1-uint8-) +- [extract_32_2(self, offset)](#Packing-extract_32_2-bytes32-uint8-) +- [replace_32_2(self, value, offset)](#Packing-replace_32_2-bytes32-bytes2-uint8-) +- [extract_32_4(self, offset)](#Packing-extract_32_4-bytes32-uint8-) +- [replace_32_4(self, value, offset)](#Packing-replace_32_4-bytes32-bytes4-uint8-) +- [extract_32_6(self, offset)](#Packing-extract_32_6-bytes32-uint8-) +- [replace_32_6(self, value, offset)](#Packing-replace_32_6-bytes32-bytes6-uint8-) +- [extract_32_8(self, offset)](#Packing-extract_32_8-bytes32-uint8-) +- [replace_32_8(self, value, offset)](#Packing-replace_32_8-bytes32-bytes8-uint8-) +- [extract_32_10(self, offset)](#Packing-extract_32_10-bytes32-uint8-) +- [replace_32_10(self, value, offset)](#Packing-replace_32_10-bytes32-bytes10-uint8-) +- [extract_32_12(self, offset)](#Packing-extract_32_12-bytes32-uint8-) +- [replace_32_12(self, value, offset)](#Packing-replace_32_12-bytes32-bytes12-uint8-) +- [extract_32_16(self, offset)](#Packing-extract_32_16-bytes32-uint8-) +- [replace_32_16(self, value, offset)](#Packing-replace_32_16-bytes32-bytes16-uint8-) +- [extract_32_20(self, offset)](#Packing-extract_32_20-bytes32-uint8-) +- [replace_32_20(self, value, offset)](#Packing-replace_32_20-bytes32-bytes20-uint8-) +- [extract_32_22(self, offset)](#Packing-extract_32_22-bytes32-uint8-) +- [replace_32_22(self, value, offset)](#Packing-replace_32_22-bytes32-bytes22-uint8-) +- [extract_32_24(self, offset)](#Packing-extract_32_24-bytes32-uint8-) +- [replace_32_24(self, value, offset)](#Packing-replace_32_24-bytes32-bytes24-uint8-) +- [extract_32_28(self, offset)](#Packing-extract_32_28-bytes32-uint8-) +- [replace_32_28(self, value, offset)](#Packing-replace_32_28-bytes32-bytes28-uint8-) +
+
+ +
+

Errors

+
+- [OutOfRangeAccess()](#Packing-OutOfRangeAccess--) +
+
+ + + +
+
+

pack_1_1(bytes1 left, bytes1 right) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_2(bytes2 left, bytes2 right) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_4(bytes2 left, bytes4 right) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_6(bytes2 left, bytes6 right) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_8(bytes2 left, bytes8 right) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_10(bytes2 left, bytes10 right) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_20(bytes2 left, bytes20 right) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_2_22(bytes2 left, bytes22 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_2(bytes4 left, bytes2 right) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_4(bytes4 left, bytes4 right) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_6(bytes4 left, bytes6 right) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_8(bytes4 left, bytes8 right) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_12(bytes4 left, bytes12 right) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_16(bytes4 left, bytes16 right) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_20(bytes4 left, bytes20 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_24(bytes4 left, bytes24 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_4_28(bytes4 left, bytes28 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_6_2(bytes6 left, bytes2 right) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_6_4(bytes6 left, bytes4 right) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_6_6(bytes6 left, bytes6 right) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_6_10(bytes6 left, bytes10 right) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_6_16(bytes6 left, bytes16 right) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_6_22(bytes6 left, bytes22 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_2(bytes8 left, bytes2 right) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_4(bytes8 left, bytes4 right) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_8(bytes8 left, bytes8 right) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_12(bytes8 left, bytes12 right) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_16(bytes8 left, bytes16 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_20(bytes8 left, bytes20 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_8_24(bytes8 left, bytes24 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_10_2(bytes10 left, bytes2 right) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_10_6(bytes10 left, bytes6 right) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_10_10(bytes10 left, bytes10 right) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_10_12(bytes10 left, bytes12 right) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_10_22(bytes10 left, bytes22 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_12_4(bytes12 left, bytes4 right) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_12_8(bytes12 left, bytes8 right) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_12_10(bytes12 left, bytes10 right) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_12_12(bytes12 left, bytes12 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_12_16(bytes12 left, bytes16 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_12_20(bytes12 left, bytes20 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_16_4(bytes16 left, bytes4 right) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_16_6(bytes16 left, bytes6 right) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_16_8(bytes16 left, bytes8 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_16_12(bytes16 left, bytes12 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_16_16(bytes16 left, bytes16 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_20_2(bytes20 left, bytes2 right) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_20_4(bytes20 left, bytes4 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_20_8(bytes20 left, bytes8 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_20_12(bytes20 left, bytes12 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_22_2(bytes22 left, bytes2 right) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_22_6(bytes22 left, bytes6 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_22_10(bytes22 left, bytes10 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_24_4(bytes24 left, bytes4 right) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_24_8(bytes24 left, bytes8 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

pack_28_4(bytes28 left, bytes4 right) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_2_1(bytes2 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_2_1(bytes2 self, bytes1 value, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_4_1(bytes4 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_4_1(bytes4 self, bytes1 value, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_4_2(bytes4 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_4_2(bytes4 self, bytes2 value, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_6_1(bytes6 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_6_1(bytes6 self, bytes1 value, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_6_2(bytes6 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_6_2(bytes6 self, bytes2 value, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_6_4(bytes6 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_6_4(bytes6 self, bytes4 value, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_8_1(bytes8 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_8_1(bytes8 self, bytes1 value, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_8_2(bytes8 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_8_2(bytes8 self, bytes2 value, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_8_4(bytes8 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_8_4(bytes8 self, bytes4 value, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_8_6(bytes8 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_8_6(bytes8 self, bytes6 value, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_10_1(bytes10 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_10_1(bytes10 self, bytes1 value, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_10_2(bytes10 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_10_2(bytes10 self, bytes2 value, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_10_4(bytes10 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_10_4(bytes10 self, bytes4 value, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_10_6(bytes10 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_10_6(bytes10 self, bytes6 value, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_10_8(bytes10 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_10_8(bytes10 self, bytes8 value, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_12_1(bytes12 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_12_1(bytes12 self, bytes1 value, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_12_2(bytes12 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_12_2(bytes12 self, bytes2 value, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_12_4(bytes12 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_12_4(bytes12 self, bytes4 value, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_12_6(bytes12 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_12_6(bytes12 self, bytes6 value, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_12_8(bytes12 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_12_8(bytes12 self, bytes8 value, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_12_10(bytes12 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_12_10(bytes12 self, bytes10 value, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_1(bytes16 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_1(bytes16 self, bytes1 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_2(bytes16 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_2(bytes16 self, bytes2 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_4(bytes16 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_4(bytes16 self, bytes4 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_6(bytes16 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_6(bytes16 self, bytes6 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_8(bytes16 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_8(bytes16 self, bytes8 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_10(bytes16 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_10(bytes16 self, bytes10 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_16_12(bytes16 self, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_16_12(bytes16 self, bytes12 value, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_1(bytes20 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_1(bytes20 self, bytes1 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_2(bytes20 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_2(bytes20 self, bytes2 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_4(bytes20 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_4(bytes20 self, bytes4 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_6(bytes20 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_6(bytes20 self, bytes6 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_8(bytes20 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_8(bytes20 self, bytes8 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_10(bytes20 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_10(bytes20 self, bytes10 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_12(bytes20 self, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_12(bytes20 self, bytes12 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_20_16(bytes20 self, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_20_16(bytes20 self, bytes16 value, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_1(bytes22 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_1(bytes22 self, bytes1 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_2(bytes22 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_2(bytes22 self, bytes2 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_4(bytes22 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_4(bytes22 self, bytes4 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_6(bytes22 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_6(bytes22 self, bytes6 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_8(bytes22 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_8(bytes22 self, bytes8 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_10(bytes22 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_10(bytes22 self, bytes10 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_12(bytes22 self, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_12(bytes22 self, bytes12 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_16(bytes22 self, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_16(bytes22 self, bytes16 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_22_20(bytes22 self, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_22_20(bytes22 self, bytes20 value, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_1(bytes24 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_1(bytes24 self, bytes1 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_2(bytes24 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_2(bytes24 self, bytes2 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_4(bytes24 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_4(bytes24 self, bytes4 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_6(bytes24 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_6(bytes24 self, bytes6 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_8(bytes24 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_8(bytes24 self, bytes8 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_10(bytes24 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_10(bytes24 self, bytes10 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_12(bytes24 self, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_12(bytes24 self, bytes12 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_16(bytes24 self, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_16(bytes24 self, bytes16 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_20(bytes24 self, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_20(bytes24 self, bytes20 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_24_22(bytes24 self, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_24_22(bytes24 self, bytes22 value, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_1(bytes28 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_1(bytes28 self, bytes1 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_2(bytes28 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_2(bytes28 self, bytes2 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_4(bytes28 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_4(bytes28 self, bytes4 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_6(bytes28 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_6(bytes28 self, bytes6 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_8(bytes28 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_8(bytes28 self, bytes8 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_10(bytes28 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_10(bytes28 self, bytes10 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_12(bytes28 self, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_12(bytes28 self, bytes12 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_16(bytes28 self, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_16(bytes28 self, bytes16 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_20(bytes28 self, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_20(bytes28 self, bytes20 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_22(bytes28 self, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_22(bytes28 self, bytes22 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_28_24(bytes28 self, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_28_24(bytes28 self, bytes24 value, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_1(bytes32 self, uint8 offset) → bytes1 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_1(bytes32 self, bytes1 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_2(bytes32 self, uint8 offset) → bytes2 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_2(bytes32 self, bytes2 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_4(bytes32 self, uint8 offset) → bytes4 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_4(bytes32 self, bytes4 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_6(bytes32 self, uint8 offset) → bytes6 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_6(bytes32 self, bytes6 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_8(bytes32 self, uint8 offset) → bytes8 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_8(bytes32 self, bytes8 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_10(bytes32 self, uint8 offset) → bytes10 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_10(bytes32 self, bytes10 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_12(bytes32 self, uint8 offset) → bytes12 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_12(bytes32 self, bytes12 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_16(bytes32 self, uint8 offset) → bytes16 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_16(bytes32 self, bytes16 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_20(bytes32 self, uint8 offset) → bytes20 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_20(bytes32 self, bytes20 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_22(bytes32 self, uint8 offset) → bytes22 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_22(bytes32 self, bytes22 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_24(bytes32 self, uint8 offset) → bytes24 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_24(bytes32 self, bytes24 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

extract_32_28(bytes32 self, uint8 offset) → bytes28 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

replace_32_28(bytes32 self, bytes28 value, uint8 offset) → bytes32 result

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

OutOfRangeAccess()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `Panic` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Panic.sol"; +``` + +Helper library for emitting standardized panic codes. + +```solidity +contract Example { + using Panic for uint256; + + // Use any of the declared internal constants + function foo() { Panic.GENERIC.panic(); } + + // Alternatively + function foo() { Panic.panic(Panic.GENERIC); } +} +``` + +Follows the list from [libsolutil](https://github.com/ethereum/solidity/blob/v0.8.24/libsolutil/ErrorCodes.h). + +_Available since v5.1._ + +
+

Functions

+
+- [panic(code)](#Panic-panic-uint256-) +
+
+ + + +
+
+

panic(uint256 code)

+
+

internal

+# +
+
+
+ +Reverts with a panic code. Recommended to use with +the internal constants with predefined codes. + +
+
+ + + +
+ +## `Pausable` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Pausable.sol"; +``` + +Contract module which allows children to implement an emergency stop +mechanism that can be triggered by an authorized account. + +This module is used through inheritance. It will make available the +modifiers `whenNotPaused` and `whenPaused`, which can be applied to +the functions of your contract. Note that they will not be pausable by +simply including this module, only once the modifiers are put in place. + +
+

Modifiers

+
+- [whenNotPaused()](#Pausable-whenNotPaused--) +- [whenPaused()](#Pausable-whenPaused--) +
+
+ +
+

Functions

+
+- [paused()](#Pausable-paused--) +- [_requireNotPaused()](#Pausable-_requireNotPaused--) +- [_requirePaused()](#Pausable-_requirePaused--) +- [_pause()](#Pausable-_pause--) +- [_unpause()](#Pausable-_unpause--) +
+
+ +
+

Events

+
+- [Paused(account)](#Pausable-Paused-address-) +- [Unpaused(account)](#Pausable-Unpaused-address-) +
+
+ +
+

Errors

+
+- [EnforcedPause()](#Pausable-EnforcedPause--) +- [ExpectedPause()](#Pausable-ExpectedPause--) +
+
+ + + +
+
+

whenNotPaused()

+
+

internal

+# +
+
+ +
+ +Modifier to make a function callable only when the contract is not paused. + +Requirements: + +- The contract must not be paused. + +
+
+ + + +
+
+

whenPaused()

+
+

internal

+# +
+
+ +
+ +Modifier to make a function callable only when the contract is paused. + +Requirements: + +- The contract must be paused. + +
+
+ + + +
+
+

paused() → bool

+
+

public

+# +
+
+
+ +Returns true if the contract is paused, and false otherwise. + +
+
+ + + +
+
+

_requireNotPaused()

+
+

internal

+# +
+
+
+ +Throws if the contract is paused. + +
+
+ + + +
+
+

_requirePaused()

+
+

internal

+# +
+
+
+ +Throws if the contract is not paused. + +
+
+ + + +
+
+

_pause()

+
+

internal

+# +
+
+
+ +Triggers stopped state. + +Requirements: + +- The contract must not be paused. + +
+
+ + + +
+
+

_unpause()

+
+

internal

+# +
+
+
+ +Returns to normal state. + +Requirements: + +- The contract must be paused. + +
+
+ + + +
+
+

Paused(address account)

+
+

event

+# +
+
+ +
+ +Emitted when the pause is triggered by `account`. + +
+
+ + +
+
+

Unpaused(address account)

+
+

event

+# +
+
+ +
+ +Emitted when the pause is lifted by `account`. + +
+
+ + + +
+
+

EnforcedPause()

+
+

error

+# +
+
+
+ +The operation failed because the contract is paused. + +
+
+ + + +
+
+

ExpectedPause()

+
+

error

+# +
+
+
+ +The operation failed because the contract is not paused. + +
+
+ + + +
+ +## `RLP` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/RLP.sol"; +``` + +Library for encoding and decoding data in RLP format. +Recursive Length Prefix (RLP) is the main encoding method used to serialize objects in Ethereum. +It's used for encoding everything from transactions to blocks to Patricia-Merkle tries. + +Inspired by + +* https://github.com/succinctlabs/optimism-bedrock-contracts/blob/main/rlp/RLPWriter.sol +* https://github.com/succinctlabs/optimism-bedrock-contracts/blob/main/rlp/RLPReader.sol + +
+

Functions

+
+- [encoder()](#RLP-encoder--) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-bool-) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-address-) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-uint256-) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-bytes32-) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-bytes-) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-string-) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-bytes---) +- [push(self, input)](#RLP-push-struct-RLP-Encoder-struct-RLP-Encoder-) +- [encode(input)](#RLP-encode-bool-) +- [encode(input)](#RLP-encode-address-) +- [encode(input)](#RLP-encode-uint256-) +- [encode(input)](#RLP-encode-bytes32-) +- [encode(input)](#RLP-encode-bytes-) +- [encode(input)](#RLP-encode-string-) +- [encode(input)](#RLP-encode-bytes---) +- [encode(self)](#RLP-encode-struct-RLP-Encoder-) +- [readBool(item)](#RLP-readBool-Memory-Slice-) +- [readAddress(item)](#RLP-readAddress-Memory-Slice-) +- [readUint256(item)](#RLP-readUint256-Memory-Slice-) +- [readBytes32(item)](#RLP-readBytes32-Memory-Slice-) +- [readBytes(item)](#RLP-readBytes-Memory-Slice-) +- [readString(item)](#RLP-readString-Memory-Slice-) +- [readList(item)](#RLP-readList-Memory-Slice-) +- [decodeBool(item)](#RLP-decodeBool-bytes-) +- [decodeAddress(item)](#RLP-decodeAddress-bytes-) +- [decodeUint256(item)](#RLP-decodeUint256-bytes-) +- [decodeBytes32(item)](#RLP-decodeBytes32-bytes-) +- [decodeBytes(item)](#RLP-decodeBytes-bytes-) +- [decodeString(item)](#RLP-decodeString-bytes-) +- [decodeList(value)](#RLP-decodeList-bytes-) +
+
+ +
+

Errors

+
+- [RLPInvalidEncoding()](#RLP-RLPInvalidEncoding--) +
+
+ + + +
+
+

encoder() → struct RLP.Encoder enc

+
+

internal

+# +
+
+
+ +Create an empty RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, bool input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add a boolean to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, address input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add an address to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, uint256 input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add a uint256 to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, bytes32 input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add a bytes32 to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, bytes input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add a bytes buffer to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, string input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add a string to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, bytes[] input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add an array of bytes to a given RLP Encoder. + +
+
+ + + +
+
+

push(struct RLP.Encoder self, struct RLP.Encoder input) → struct RLP.Encoder

+
+

internal

+# +
+
+
+ +Add an (input) Encoder to a (target) Encoder. The input is RLP encoded as a list of bytes, and added to the target Encoder. + +
+
+ + + +
+
+

encode(bool input) → bytes result

+
+

internal

+# +
+
+
+ +Encode a boolean as RLP. + +Boolean `true` is encoded as 0x01, `false` as 0x80 (equivalent to encoding integers 1 and 0). +This follows the de facto ecosystem standard where booleans are treated as 0/1 integers. + +
+
+ + + +
+
+

encode(address input) → bytes result

+
+

internal

+# +
+
+
+ +Encode an address as RLP. + +
+
+ + + +
+
+

encode(uint256 input) → bytes result

+
+

internal

+# +
+
+
+ +Encode a uint256 as RLP. + +
+
+ + + +
+
+

encode(bytes32 input) → bytes

+
+

internal

+# +
+
+
+ +Encode a bytes32 as RLP. Type alias for #RLP-encode-uint256-. + +
+
+ + + +
+
+

encode(bytes input) → bytes

+
+

internal

+# +
+
+
+ +Encode a bytes buffer as RLP. + +
+
+ + + +
+
+

encode(string input) → bytes

+
+

internal

+# +
+
+
+ +Encode a string as RLP. Type alias for #Base58-encode-bytes-. + +
+
+ + + +
+
+

encode(bytes[] input) → bytes

+
+

internal

+# +
+
+
+ +Encode an array of bytes as RLP. + +
+
+ + + +
+
+

encode(struct RLP.Encoder self) → bytes result

+
+

internal

+# +
+
+
+ +Encode an encoder (list of bytes) as RLP + +
+
+ + + +
+
+

readBool(Memory.Slice item) → bool

+
+

internal

+# +
+
+
+ +Decode an RLP encoded bool. See #RLP-encode-bool- + +
+
+ + + +
+
+

readAddress(Memory.Slice item) → address

+
+

internal

+# +
+
+
+ +Decode an RLP encoded address. See #RLP-encode-address- + +
+
+ + + +
+
+

readUint256(Memory.Slice item) → uint256

+
+

internal

+# +
+
+
+ +Decode an RLP encoded uint256. See #RLP-encode-uint256- + +
+
+ + + +
+
+

readBytes32(Memory.Slice item) → bytes32

+
+

internal

+# +
+
+
+ +Decode an RLP encoded bytes32. See #RLP-encode-bytes32- + +
+
+ + + +
+
+

readBytes(Memory.Slice item) → bytes

+
+

internal

+# +
+
+
+ +Decodes an RLP encoded bytes. See #Base58-encode-bytes- + +
+
+ + + +
+
+

readString(Memory.Slice item) → string

+
+

internal

+# +
+
+
+ +Decodes an RLP encoded string. See #RLP-encode-string- + +
+
+ + + +
+
+

readList(Memory.Slice item) → Memory.Slice[] list

+
+

internal

+# +
+
+
+ +Decodes an RLP encoded list into an array of RLP Items. + +
+
+ + + +
+
+

decodeBool(bytes item) → bool

+
+

internal

+# +
+
+
+ +Decode an RLP encoded bool from bytes. See [`RLP.readBool`](#RLP-readBool-Memory-Slice-) + +
+
+ + + +
+
+

decodeAddress(bytes item) → address

+
+

internal

+# +
+
+
+ +Decode an RLP encoded address from bytes. See [`RLP.readAddress`](#RLP-readAddress-Memory-Slice-) + +
+
+ + + +
+
+

decodeUint256(bytes item) → uint256

+
+

internal

+# +
+
+
+ +Decode an RLP encoded uint256 from bytes. See [`RLP.readUint256`](#RLP-readUint256-Memory-Slice-) + +
+
+ + + +
+
+

decodeBytes32(bytes item) → bytes32

+
+

internal

+# +
+
+
+ +Decode an RLP encoded bytes32 from bytes. See [`RLP.readBytes32`](#RLP-readBytes32-Memory-Slice-) + +
+
+ + + +
+
+

decodeBytes(bytes item) → bytes

+
+

internal

+# +
+
+
+ +Decode an RLP encoded bytes from bytes. See [`RLP.readBytes`](#RLP-readBytes-Memory-Slice-) + +
+
+ + + +
+
+

decodeString(bytes item) → string

+
+

internal

+# +
+
+
+ +Decode an RLP encoded string from bytes. See [`RLP.readString`](#RLP-readString-Memory-Slice-) + +
+
+ + + +
+
+

decodeList(bytes value) → Memory.Slice[]

+
+

internal

+# +
+
+
+ +Decode an RLP encoded list from bytes. See [`RLP.readList`](#RLP-readList-Memory-Slice-) + +
+
+ + + +
+
+

RLPInvalidEncoding()

+
+

error

+# +
+
+
+ +The item is not properly formatted and cannot de decoded. + +
+
+ + + +
+ +## `ReentrancyGuard` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; +``` + +Contract module that helps prevent reentrant calls to a function. + +Inheriting from `ReentrancyGuard` will make the [`ReentrancyGuard.nonReentrant`](#ReentrancyGuard-nonReentrant--) modifier +available, which can be applied to functions to make sure there are no nested +(reentrant) calls to them. + +Note that because there is a single `nonReentrant` guard, functions marked as +`nonReentrant` may not call one another. This can be worked around by making +those functions `private`, and then adding `external` `nonReentrant` entry +points to them. + + +If EIP-1153 (transient storage) is available on the chain you're deploying at, +consider using [`ReentrancyGuardTransient`](#ReentrancyGuardTransient) instead. + + + +If you would like to learn more about reentrancy and alternative ways +to protect against it, check out our blog post +[Reentrancy After Istanbul](https://blog.openzeppelin.com/reentrancy-after-istanbul/). + + + +Deprecated. This storage-based reentrancy guard will be removed and replaced +by the [`ReentrancyGuardTransient`](#ReentrancyGuardTransient) variant in v6.0. + + +@custom:stateless + +
+

Modifiers

+
+- [nonReentrant()](#ReentrancyGuard-nonReentrant--) +- [nonReentrantView()](#ReentrancyGuard-nonReentrantView--) +
+
+ +
+

Functions

+
+- [constructor()](#ReentrancyGuard-constructor--) +- [_reentrancyGuardEntered()](#ReentrancyGuard-_reentrancyGuardEntered--) +- [_reentrancyGuardStorageSlot()](#ReentrancyGuard-_reentrancyGuardStorageSlot--) +
+
+ +
+

Errors

+
+- [ReentrancyGuardReentrantCall()](#ReentrancyGuard-ReentrancyGuardReentrantCall--) +
+
+ + + +
+
+

nonReentrant()

+
+

internal

+# +
+
+ +
+ +Prevents a contract from calling itself, directly or indirectly. +Calling a `nonReentrant` function from another `nonReentrant` +function is not supported. It is possible to prevent this from happening +by making the `nonReentrant` function external, and making it call a +`private` function that does the actual work. + +
+
+ + + +
+
+

nonReentrantView()

+
+

internal

+# +
+
+ +
+ +A `view` only version of [`ReentrancyGuard.nonReentrant`](#ReentrancyGuard-nonReentrant--). Use to block view functions +from being called, preventing reading from inconsistent contract state. + +CAUTION: This is a "view" modifier and does not change the reentrancy +status. Use it only on view functions. For payable or non-payable functions, +use the standard [`ReentrancyGuard.nonReentrant`](#ReentrancyGuard-nonReentrant--) modifier instead. + +
+
+ + + +
+
+

constructor()

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_reentrancyGuardEntered() → bool

+
+

internal

+# +
+
+
+ +Returns true if the reentrancy guard is currently set to "entered", which indicates there is a +`nonReentrant` function in the call stack. + +
+
+ + + +
+
+

_reentrancyGuardStorageSlot() → bytes32

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

ReentrancyGuardReentrantCall()

+
+

error

+# +
+
+
+ +Unauthorized reentrant call. + +
+
+ + + +
+ +## `ReentrancyGuardTransient` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol"; +``` + +Variant of [`ReentrancyGuard`](#ReentrancyGuard) that uses transient storage. + + +This variant only works on networks where EIP-1153 is available. + + +_Available since v5.1._ + +@custom:stateless + +
+

Modifiers

+
+- [nonReentrant()](#ReentrancyGuardTransient-nonReentrant--) +- [nonReentrantView()](#ReentrancyGuardTransient-nonReentrantView--) +
+
+ +
+

Functions

+
+- [_reentrancyGuardEntered()](#ReentrancyGuardTransient-_reentrancyGuardEntered--) +- [_reentrancyGuardStorageSlot()](#ReentrancyGuardTransient-_reentrancyGuardStorageSlot--) +
+
+ +
+

Errors

+
+- [ReentrancyGuardReentrantCall()](#ReentrancyGuardTransient-ReentrancyGuardReentrantCall--) +
+
+ + + +
+
+

nonReentrant()

+
+

internal

+# +
+
+ +
+ +Prevents a contract from calling itself, directly or indirectly. +Calling a `nonReentrant` function from another `nonReentrant` +function is not supported. It is possible to prevent this from happening +by making the `nonReentrant` function external, and making it call a +`private` function that does the actual work. + +
+
+ + + +
+
+

nonReentrantView()

+
+

internal

+# +
+
+ +
+ +A `view` only version of [`ReentrancyGuard.nonReentrant`](#ReentrancyGuard-nonReentrant--). Use to block view functions +from being called, preventing reading from inconsistent contract state. + +CAUTION: This is a "view" modifier and does not change the reentrancy +status. Use it only on view functions. For payable or non-payable functions, +use the standard [`ReentrancyGuard.nonReentrant`](#ReentrancyGuard-nonReentrant--) modifier instead. + +
+
+ + + +
+
+

_reentrancyGuardEntered() → bool

+
+

internal

+# +
+
+
+ +Returns true if the reentrancy guard is currently set to "entered", which indicates there is a +`nonReentrant` function in the call stack. + +
+
+ + + +
+
+

_reentrancyGuardStorageSlot() → bytes32

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

ReentrancyGuardReentrantCall()

+
+

error

+# +
+
+
+ +Unauthorized reentrant call. + +
+
+ + + +
+ +## `RelayedCall` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/RelayedCall.sol"; +``` + +Library for performing external calls through dynamically deployed relay contracts that hide the original +caller's address from the target contract. This pattern is used in ERC-4337's EntryPoint for account factory +calls and ERC-6942 for safe factory interactions. + +When privileged contracts need to make arbitrary external calls based on user input, calling the target directly +can be risky because the target sees the privileged contract as `msg.sender` and could exploit this trust +relationship. This library solves this by deploying minimal relay contracts that act as intermediaries, ensuring +the target only sees the unprivileged relay address as `msg.sender`. + +For example, instead of `target.call(data)` where the target sees this contract as `msg.sender`, use +[`RelayedCall.relayCall`](#RelayedCall-relayCall-address-uint256-bytes-bytes32-) where the target sees a relay address as `msg.sender`. + +
+

Functions

+
+- [relayCall(target, data)](#RelayedCall-relayCall-address-bytes-) +- [relayCall(target, value, data)](#RelayedCall-relayCall-address-uint256-bytes-) +- [relayCall(target, data, salt)](#RelayedCall-relayCall-address-bytes-bytes32-) +- [relayCall(target, value, data, salt)](#RelayedCall-relayCall-address-uint256-bytes-bytes32-) +- [getRelayer()](#RelayedCall-getRelayer--) +- [getRelayer(salt)](#RelayedCall-getRelayer-bytes32-) +
+
+ + + +
+
+

relayCall(address target, bytes data) → bool, bytes

+
+

internal

+# +
+
+
+ +Relays a call to the target contract through a dynamically deployed relay contract. + +
+
+ + + +
+
+

relayCall(address target, uint256 value, bytes data) → bool, bytes

+
+

internal

+# +
+
+
+ +Same as [`RelayedCall.relayCall`](#RelayedCall-relayCall-address-uint256-bytes-bytes32-) but with a value. + +
+
+ + + +
+
+

relayCall(address target, bytes data, bytes32 salt) → bool, bytes

+
+

internal

+# +
+
+
+ +Same as [`RelayedCall.relayCall`](#RelayedCall-relayCall-address-uint256-bytes-bytes32-) but with a salt. + +
+
+ + + +
+
+

relayCall(address target, uint256 value, bytes data, bytes32 salt) → bool, bytes

+
+

internal

+# +
+
+
+ +Same as [`RelayedCall.relayCall`](#RelayedCall-relayCall-address-uint256-bytes-bytes32-) but with a salt and a value. + +
+
+ + + +
+
+

getRelayer() → address

+
+

internal

+# +
+
+
+ +Same as [`RelayedCall.getRelayer`](#RelayedCall-getRelayer-bytes32-) but with a `bytes32(0)` default salt. + +
+
+ + + +
+
+

getRelayer(bytes32 salt) → address relayer

+
+

internal

+# +
+
+
+ +Returns the relayer address for a given salt. + +
+
+ + + +
+ +## `ShortString` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/ShortStrings.sol"; +``` + + + +
+ +## `ShortStrings` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/ShortStrings.sol"; +``` + +This library provides functions to convert short memory strings +into a `ShortString` type that can be used as an immutable variable. + +Strings of arbitrary length can be optimized using this library if +they are short enough (up to 31 bytes) by packing them with their +length (1 byte) in a single EVM word (32 bytes). Additionally, a +fallback mechanism can be used for every other case. + +Usage example: + +```solidity +contract Named { + using ShortStrings for *; + + ShortString private immutable _name; + string private _nameFallback; + + constructor(string memory contractName) { + _name = contractName.toShortStringWithFallback(_nameFallback); + } + + function name() external view returns (string memory) { + return _name.toStringWithFallback(_nameFallback); + } +} +``` + +
+

Functions

+
+- [toShortString(str)](#ShortStrings-toShortString-string-) +- [toString(sstr)](#ShortStrings-toString-ShortString-) +- [byteLength(sstr)](#ShortStrings-byteLength-ShortString-) +- [toShortStringWithFallback(value, store)](#ShortStrings-toShortStringWithFallback-string-string-) +- [toStringWithFallback(value, store)](#ShortStrings-toStringWithFallback-ShortString-string-) +- [byteLengthWithFallback(value, store)](#ShortStrings-byteLengthWithFallback-ShortString-string-) +
+
+ +
+

Errors

+
+- [StringTooLong(str)](#ShortStrings-StringTooLong-string-) +- [InvalidShortString()](#ShortStrings-InvalidShortString--) +
+
+ + + +
+
+

toShortString(string str) → ShortString

+
+

internal

+# +
+
+
+ +Encode a string of at most 31 chars into a `ShortString`. + +This will trigger a `StringTooLong` error is the input string is too long. + +
+
+ + + +
+
+

toString(ShortString sstr) → string

+
+

internal

+# +
+
+
+ +Decode a `ShortString` back to a "normal" string. + +
+
+ + + +
+
+

byteLength(ShortString sstr) → uint256

+
+

internal

+# +
+
+
+ +Return the length of a `ShortString`. + +
+
+ + + +
+
+

toShortStringWithFallback(string value, string store) → ShortString

+
+

internal

+# +
+
+
+ +Encode a string into a `ShortString`, or write it to storage if it is too long. + +
+
+ + + +
+
+

toStringWithFallback(ShortString value, string store) → string

+
+

internal

+# +
+
+
+ +Decode a string that was encoded to `ShortString` or written to storage using [`ShortStrings.toShortStringWithFallback`](#ShortStrings-toShortStringWithFallback-string-string-). + +
+
+ + + +
+
+

byteLengthWithFallback(ShortString value, string store) → uint256

+
+

internal

+# +
+
+
+ +Return the length of a string that was encoded to `ShortString` or written to storage using +[`ShortStrings.toShortStringWithFallback`](#ShortStrings-toShortStringWithFallback-string-string-). + + +This will return the "byte length" of the string. This may not reflect the actual length in terms of +actual characters as the UTF-8 encoding of a single character can span over multiple bytes. + + +
+
+ + + +
+
+

StringTooLong(string str)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InvalidShortString()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `SlotDerivation` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/SlotDerivation.sol"; +``` + +Library for computing storage (and transient storage) locations from namespaces and deriving slots +corresponding to standard patterns. The derivation method for array and mapping matches the storage layout used by +the solidity language / compiler. + +See [Solidity docs for mappings and dynamic arrays.](https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_storage.html#mappings-and-dynamic-arrays). + +Example usage: +```solidity +contract Example { + // Add the library methods + using StorageSlot for bytes32; + using SlotDerivation for *; + + // Declare a namespace + string private constant _NAMESPACE = ""; // eg. OpenZeppelin.Slot + + function setValueInNamespace(uint256 key, address newValue) internal { + _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value = newValue; + } + + function getValueInNamespace(uint256 key) internal view returns (address) { + return _NAMESPACE.erc7201Slot().deriveMapping(key).getAddressSlot().value; + } +} +``` + + +Consider using this library along with [`StorageSlot`](#StorageSlot). + + + +This library provides a way to manipulate storage locations in a non-standard way. Tooling for checking +upgrade safety will ignore the slots accessed through this library. + + +_Available since v5.1._ + +
+

Functions

+
+- [erc7201Slot(namespace)](#SlotDerivation-erc7201Slot-string-) +- [offset(slot, pos)](#SlotDerivation-offset-bytes32-uint256-) +- [deriveArray(slot)](#SlotDerivation-deriveArray-bytes32-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-address-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-bool-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-bytes32-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-uint256-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-int256-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-string-) +- [deriveMapping(slot, key)](#SlotDerivation-deriveMapping-bytes32-bytes-) +
+
+ + + +
+
+

erc7201Slot(string namespace) → bytes32 slot

+
+

internal

+# +
+
+
+ +Derive an ERC-7201 slot from a string (namespace). + +
+
+ + + +
+
+

offset(bytes32 slot, uint256 pos) → bytes32 result

+
+

internal

+# +
+
+
+ +Add an offset to a slot to get the n-th element of a structure or an array. + +
+
+ + + +
+
+

deriveArray(bytes32 slot) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of the first element in an array from the slot where the length is stored. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, address key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, bool key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, bytes32 key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, uint256 key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, int256 key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, string key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+
+

deriveMapping(bytes32 slot, bytes key) → bytes32 result

+
+

internal

+# +
+
+
+ +Derive the location of a mapping element from the key. + +
+
+ + + +
+ +## `StorageSlot` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/StorageSlot.sol"; +``` + +Library for reading and writing primitive types to specific storage slots. + +Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. +This library helps with reading and writing to such slots without the need for inline assembly. + +The functions in this library return Slot structs that contain a `value` member that can be used to read or write. + +Example usage to set ERC-1967 implementation slot: +```solidity +contract ERC1967 { + // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + 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; + } +} +``` + + +Consider using this library along with [`SlotDerivation`](#SlotDerivation). + + +
+

Functions

+
+- [getAddressSlot(slot)](#StorageSlot-getAddressSlot-bytes32-) +- [getBooleanSlot(slot)](#StorageSlot-getBooleanSlot-bytes32-) +- [getBytes32Slot(slot)](#StorageSlot-getBytes32Slot-bytes32-) +- [getUint256Slot(slot)](#StorageSlot-getUint256Slot-bytes32-) +- [getInt256Slot(slot)](#StorageSlot-getInt256Slot-bytes32-) +- [getStringSlot(slot)](#StorageSlot-getStringSlot-bytes32-) +- [getStringSlot(store)](#StorageSlot-getStringSlot-string-) +- [getBytesSlot(slot)](#StorageSlot-getBytesSlot-bytes32-) +- [getBytesSlot(store)](#StorageSlot-getBytesSlot-bytes-) +
+
+ + + +
+
+

getAddressSlot(bytes32 slot) → struct StorageSlot.AddressSlot r

+
+

internal

+# +
+
+
+ +Returns an `AddressSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getBooleanSlot(bytes32 slot) → struct StorageSlot.BooleanSlot r

+
+

internal

+# +
+
+
+ +Returns a `BooleanSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getBytes32Slot(bytes32 slot) → struct StorageSlot.Bytes32Slot r

+
+

internal

+# +
+
+
+ +Returns a `Bytes32Slot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getUint256Slot(bytes32 slot) → struct StorageSlot.Uint256Slot r

+
+

internal

+# +
+
+
+ +Returns a `Uint256Slot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getInt256Slot(bytes32 slot) → struct StorageSlot.Int256Slot r

+
+

internal

+# +
+
+
+ +Returns a `Int256Slot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getStringSlot(bytes32 slot) → struct StorageSlot.StringSlot r

+
+

internal

+# +
+
+
+ +Returns a `StringSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getStringSlot(string store) → struct StorageSlot.StringSlot r

+
+

internal

+# +
+
+
+ +Returns an `StringSlot` representation of the string storage pointer `store`. + +
+
+ + + +
+
+

getBytesSlot(bytes32 slot) → struct StorageSlot.BytesSlot r

+
+

internal

+# +
+
+
+ +Returns a `BytesSlot` with member `value` located at `slot`. + +
+
+ + + +
+
+

getBytesSlot(bytes store) → struct StorageSlot.BytesSlot r

+
+

internal

+# +
+
+
+ +Returns an `BytesSlot` representation of the bytes storage pointer `store`. + +
+
+ + + +
+ +## `Strings` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/Strings.sol"; +``` + +String operations. + +
+

Functions

+
+- [toString(value)](#Strings-toString-uint256-) +- [toStringSigned(value)](#Strings-toStringSigned-int256-) +- [toHexString(value)](#Strings-toHexString-uint256-) +- [toHexString(value, length)](#Strings-toHexString-uint256-uint256-) +- [toHexString(addr)](#Strings-toHexString-address-) +- [toChecksumHexString(addr)](#Strings-toChecksumHexString-address-) +- [toHexString(input)](#Strings-toHexString-bytes-) +- [equal(a, b)](#Strings-equal-string-string-) +- [parseUint(input)](#Strings-parseUint-string-) +- [parseUint(input, begin, end)](#Strings-parseUint-string-uint256-uint256-) +- [tryParseUint(input)](#Strings-tryParseUint-string-) +- [tryParseUint(input, begin, end)](#Strings-tryParseUint-string-uint256-uint256-) +- [parseInt(input)](#Strings-parseInt-string-) +- [parseInt(input, begin, end)](#Strings-parseInt-string-uint256-uint256-) +- [tryParseInt(input)](#Strings-tryParseInt-string-) +- [tryParseInt(input, begin, end)](#Strings-tryParseInt-string-uint256-uint256-) +- [parseHexUint(input)](#Strings-parseHexUint-string-) +- [parseHexUint(input, begin, end)](#Strings-parseHexUint-string-uint256-uint256-) +- [tryParseHexUint(input)](#Strings-tryParseHexUint-string-) +- [tryParseHexUint(input, begin, end)](#Strings-tryParseHexUint-string-uint256-uint256-) +- [parseAddress(input)](#Strings-parseAddress-string-) +- [parseAddress(input, begin, end)](#Strings-parseAddress-string-uint256-uint256-) +- [tryParseAddress(input)](#Strings-tryParseAddress-string-) +- [tryParseAddress(input, begin, end)](#Strings-tryParseAddress-string-uint256-uint256-) +- [escapeJSON(input)](#Strings-escapeJSON-string-) +
+
+ +
+

Errors

+
+- [StringsInsufficientHexLength(value, length)](#Strings-StringsInsufficientHexLength-uint256-uint256-) +- [StringsInvalidChar()](#Strings-StringsInvalidChar--) +- [StringsInvalidAddressFormat()](#Strings-StringsInvalidAddressFormat--) +
+
+ + + +
+
+

toString(uint256 value) → string

+
+

internal

+# +
+
+
+ +Converts a `uint256` to its ASCII `string` decimal representation. + +
+
+ + + +
+
+

toStringSigned(int256 value) → string

+
+

internal

+# +
+
+
+ +Converts a `int256` to its ASCII `string` decimal representation. + +
+
+ + + +
+
+

toHexString(uint256 value) → string

+
+

internal

+# +
+
+
+ +Converts a `uint256` to its ASCII `string` hexadecimal representation. + +
+
+ + + +
+
+

toHexString(uint256 value, uint256 length) → string

+
+

internal

+# +
+
+
+ +Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + +
+
+ + + +
+
+

toHexString(address addr) → string

+
+

internal

+# +
+
+
+ +Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal +representation. + +
+
+ + + +
+
+

toChecksumHexString(address addr) → string

+
+

internal

+# +
+
+
+ +Converts an `address` with fixed length of 20 bytes to its checksummed ASCII `string` hexadecimal +representation, according to EIP-55. + +
+
+ + + +
+
+

toHexString(bytes input) → string

+
+

internal

+# +
+
+
+ +Converts a `bytes` buffer to its ASCII `string` hexadecimal representation. + +
+
+ + + +
+
+

equal(string a, string b) → bool

+
+

internal

+# +
+
+
+ +Returns true if the two strings are equal. + +
+
+ + + +
+
+

parseUint(string input) → uint256

+
+

internal

+# +
+
+
+ +Parse a decimal string and returns the value as a `uint256`. + +Requirements: +- The string must be formatted as `[0-9]*` +- The result must fit into an `uint256` type + +
+
+ + + +
+
+

parseUint(string input, uint256 begin, uint256 end) → uint256

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseUint-string- that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `[0-9]*` +- The result must fit into an `uint256` type + +
+
+ + + +
+
+

tryParseUint(string input) → bool success, uint256 value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseUint-string- that returns false if the parsing fails because of an invalid character. + + +This function will revert if the result does not fit in a `uint256`. + + +
+
+ + + +
+
+

tryParseUint(string input, uint256 begin, uint256 end) → bool success, uint256 value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseUint-string-uint256-uint256- that returns false if the parsing fails because of an invalid +character. + + +This function will revert if the result does not fit in a `uint256`. + + +
+
+ + + +
+
+

parseInt(string input) → int256

+
+

internal

+# +
+
+
+ +Parse a decimal string and returns the value as a `int256`. + +Requirements: +- The string must be formatted as `[-+]?[0-9]*` +- The result must fit in an `int256` type. + +
+
+ + + +
+
+

parseInt(string input, uint256 begin, uint256 end) → int256

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseInt-string- that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `[-+]?[0-9]*` +- The result must fit in an `int256` type. + +
+
+ + + +
+
+

tryParseInt(string input) → bool success, int256 value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseInt-string- that returns false if the parsing fails because of an invalid character or if +the result does not fit in a `int256`. + + +This function will revert if the absolute value of the result does not fit in a `uint256`. + + +
+
+ + + +
+
+

tryParseInt(string input, uint256 begin, uint256 end) → bool success, int256 value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseInt-string-uint256-uint256- that returns false if the parsing fails because of an invalid +character or if the result does not fit in a `int256`. + + +This function will revert if the absolute value of the result does not fit in a `uint256`. + + +
+
+ + + +
+
+

parseHexUint(string input) → uint256

+
+

internal

+# +
+
+
+ +Parse a hexadecimal string (with or without "0x" prefix), and returns the value as a `uint256`. + +Requirements: +- The string must be formatted as `(0x)?[0-9a-fA-F]*` +- The result must fit in an `uint256` type. + +
+
+ + + +
+
+

parseHexUint(string input, uint256 begin, uint256 end) → uint256

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseHexUint-string- that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `(0x)?[0-9a-fA-F]*` +- The result must fit in an `uint256` type. + +
+
+ + + +
+
+

tryParseHexUint(string input) → bool success, uint256 value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseHexUint-string- that returns false if the parsing fails because of an invalid character. + + +This function will revert if the result does not fit in a `uint256`. + + +
+
+ + + +
+
+

tryParseHexUint(string input, uint256 begin, uint256 end) → bool success, uint256 value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseHexUint-string-uint256-uint256- that returns false if the parsing fails because of an +invalid character. + + +This function will revert if the result does not fit in a `uint256`. + + +
+
+ + + +
+
+

parseAddress(string input) → address

+
+

internal

+# +
+
+
+ +Parse a hexadecimal string (with or without "0x" prefix), and returns the value as an `address`. + +Requirements: +- The string must be formatted as `(0x)?[0-9a-fA-F][`SafeCast.toUint240`](#SafeCast-toUint240-uint256-)` + +
+
+ + + +
+
+

parseAddress(string input, uint256 begin, uint256 end) → address

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseAddress-string- that parses a substring of `input` located between position `begin` (included) and +`end` (excluded). + +Requirements: +- The substring must be formatted as `(0x)?[0-9a-fA-F][`SafeCast.toUint240`](#SafeCast-toUint240-uint256-)` + +
+
+ + + +
+
+

tryParseAddress(string input) → bool success, address value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseAddress-string- that returns false if the parsing fails because the input is not a properly +formatted address. See #Strings-parseAddress-string- requirements. + +
+
+ + + +
+
+

tryParseAddress(string input, uint256 begin, uint256 end) → bool success, address value

+
+

internal

+# +
+
+
+ +Variant of #Strings-parseAddress-string-uint256-uint256- that returns false if the parsing fails because input is not a properly +formatted address. See #Strings-parseAddress-string-uint256-uint256- requirements. + +
+
+ + + +
+
+

escapeJSON(string input) → string

+
+

internal

+# +
+
+
+ +Escape special characters in JSON strings. This can be useful to prevent JSON injection in NFT metadata. + + +This function should only be used in double quoted JSON strings. Single quotes are not escaped. + + + +This function escapes all unicode characters, and not just the ones in ranges defined in section 2.5 of +RFC-4627 (U+0000 to U+001F, U+0022 and U+005C). ECMAScript's `JSON.parse` does recover escaped unicode +characters that are not in this range, but other tooling may provide different results. + + +
+
+ + + +
+
+

StringsInsufficientHexLength(uint256 value, uint256 length)

+
+

error

+# +
+
+
+ +The `value` string doesn't fit in the specified `length`. + +
+
+ + + +
+
+

StringsInvalidChar()

+
+

error

+# +
+
+
+ +The string being parsed contains characters that are not in scope of the given base. + +
+
+ + + +
+
+

StringsInvalidAddressFormat()

+
+

error

+# +
+
+
+ +The string being parsed is not a properly formatted address. + +
+
+ + + +
+ +## `TransientSlot` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/TransientSlot.sol"; +``` + +Library for reading and writing value-types to specific transient storage slots. + +Transient slots are often used to store temporary values that are removed after the current transaction. +This library helps with reading and writing to such slots without the need for inline assembly. + + * Example reading and writing values using transient storage: +```solidity +contract Lock { + using TransientSlot for *; + + // Define the slot. Alternatively, use the SlotDerivation library to derive the slot. + bytes32 internal constant _LOCK_SLOT = 0xf4678858b2b588224636b8522b729e7722d32fc491da849ed75b3fdf3c84f542; + + modifier locked() { + require(!_LOCK_SLOT.asBoolean().tload()); + + _LOCK_SLOT.asBoolean().tstore(true); + _; + _LOCK_SLOT.asBoolean().tstore(false); + } +} +``` + + +Consider using this library along with [`SlotDerivation`](#SlotDerivation). + + +
+

Functions

+
+- [asAddress(slot)](#TransientSlot-asAddress-bytes32-) +- [asBoolean(slot)](#TransientSlot-asBoolean-bytes32-) +- [asBytes32(slot)](#TransientSlot-asBytes32-bytes32-) +- [asUint256(slot)](#TransientSlot-asUint256-bytes32-) +- [asInt256(slot)](#TransientSlot-asInt256-bytes32-) +- [tload(slot)](#TransientSlot-tload-TransientSlot-AddressSlot-) +- [tstore(slot, value)](#TransientSlot-tstore-TransientSlot-AddressSlot-address-) +- [tload(slot)](#TransientSlot-tload-TransientSlot-BooleanSlot-) +- [tstore(slot, value)](#TransientSlot-tstore-TransientSlot-BooleanSlot-bool-) +- [tload(slot)](#TransientSlot-tload-TransientSlot-Bytes32Slot-) +- [tstore(slot, value)](#TransientSlot-tstore-TransientSlot-Bytes32Slot-bytes32-) +- [tload(slot)](#TransientSlot-tload-TransientSlot-Uint256Slot-) +- [tstore(slot, value)](#TransientSlot-tstore-TransientSlot-Uint256Slot-uint256-) +- [tload(slot)](#TransientSlot-tload-TransientSlot-Int256Slot-) +- [tstore(slot, value)](#TransientSlot-tstore-TransientSlot-Int256Slot-int256-) +
+
+ + + +
+
+

asAddress(bytes32 slot) → TransientSlot.AddressSlot

+
+

internal

+# +
+
+
+ +Cast an arbitrary slot to a AddressSlot. + +
+
+ + + +
+
+

asBoolean(bytes32 slot) → TransientSlot.BooleanSlot

+
+

internal

+# +
+
+
+ +Cast an arbitrary slot to a BooleanSlot. + +
+
+ + + +
+
+

asBytes32(bytes32 slot) → TransientSlot.Bytes32Slot

+
+

internal

+# +
+
+
+ +Cast an arbitrary slot to a Bytes32Slot. + +
+
+ + + +
+
+

asUint256(bytes32 slot) → TransientSlot.Uint256Slot

+
+

internal

+# +
+
+
+ +Cast an arbitrary slot to a Uint256Slot. + +
+
+ + + +
+
+

asInt256(bytes32 slot) → TransientSlot.Int256Slot

+
+

internal

+# +
+
+
+ +Cast an arbitrary slot to a Int256Slot. + +
+
+ + + +
+
+

tload(TransientSlot.AddressSlot slot) → address value

+
+

internal

+# +
+
+
+ +Load the value held at location `slot` in transient storage. + +
+
+ + + +
+
+

tstore(TransientSlot.AddressSlot slot, address value)

+
+

internal

+# +
+
+
+ +Store `value` at location `slot` in transient storage. + +
+
+ + + +
+
+

tload(TransientSlot.BooleanSlot slot) → bool value

+
+

internal

+# +
+
+
+ +Load the value held at location `slot` in transient storage. + +
+
+ + + +
+
+

tstore(TransientSlot.BooleanSlot slot, bool value)

+
+

internal

+# +
+
+
+ +Store `value` at location `slot` in transient storage. + +
+
+ + + +
+
+

tload(TransientSlot.Bytes32Slot slot) → bytes32 value

+
+

internal

+# +
+
+
+ +Load the value held at location `slot` in transient storage. + +
+
+ + + +
+
+

tstore(TransientSlot.Bytes32Slot slot, bytes32 value)

+
+

internal

+# +
+
+
+ +Store `value` at location `slot` in transient storage. + +
+
+ + + +
+
+

tload(TransientSlot.Uint256Slot slot) → uint256 value

+
+

internal

+# +
+
+
+ +Load the value held at location `slot` in transient storage. + +
+
+ + + +
+
+

tstore(TransientSlot.Uint256Slot slot, uint256 value)

+
+

internal

+# +
+
+
+ +Store `value` at location `slot` in transient storage. + +
+
+ + + +
+
+

tload(TransientSlot.Int256Slot slot) → int256 value

+
+

internal

+# +
+
+
+ +Load the value held at location `slot` in transient storage. + +
+
+ + + +
+
+

tstore(TransientSlot.Int256Slot slot, int256 value)

+
+

internal

+# +
+
+
+ +Store `value` at location `slot` in transient storage. + +
+
+ + + +
+ +## `InteroperableAddress` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol"; +``` + +Helper library to format and parse [ERC-7930](https://ethereum-magicians.org/t/erc-7930-interoperable-addresses/23365) interoperable +addresses. + +
+

Functions

+
+- [formatV1(chainType, chainReference, addr)](#InteroperableAddress-formatV1-bytes2-bytes-bytes-) +- [formatEvmV1(chainid, addr)](#InteroperableAddress-formatEvmV1-uint256-address-) +- [formatEvmV1(chainid)](#InteroperableAddress-formatEvmV1-uint256-) +- [formatEvmV1(addr)](#InteroperableAddress-formatEvmV1-address-) +- [parseV1(self)](#InteroperableAddress-parseV1-bytes-) +- [parseV1Calldata(self)](#InteroperableAddress-parseV1Calldata-bytes-) +- [tryParseV1(self)](#InteroperableAddress-tryParseV1-bytes-) +- [tryParseV1Calldata(self)](#InteroperableAddress-tryParseV1Calldata-bytes-) +- [parseEvmV1(self)](#InteroperableAddress-parseEvmV1-bytes-) +- [parseEvmV1Calldata(self)](#InteroperableAddress-parseEvmV1Calldata-bytes-) +- [tryParseEvmV1(self)](#InteroperableAddress-tryParseEvmV1-bytes-) +- [tryParseEvmV1Calldata(self)](#InteroperableAddress-tryParseEvmV1Calldata-bytes-) +
+
+ +
+

Errors

+
+- [InteroperableAddressParsingError()](#InteroperableAddress-InteroperableAddressParsingError-bytes-) +- [InteroperableAddressEmptyReferenceAndAddress()](#InteroperableAddress-InteroperableAddressEmptyReferenceAndAddress--) +
+
+ + + +
+
+

formatV1(bytes2 chainType, bytes chainReference, bytes addr) → bytes

+
+

internal

+# +
+
+
+ +Format an ERC-7930 interoperable address (version 1) from its components `chainType`, `chainReference` +and `addr`. This is a generic function that supports any chain type, chain reference and address supported by +ERC-7930, including interoperable addresses with empty chain reference or empty address. + +
+
+ + + +
+
+

formatEvmV1(uint256 chainid, address addr) → bytes

+
+

internal

+# +
+
+
+ +Variant of #InteroperableAddress-formatV1-bytes2-bytes-bytes- specific to EVM chains. Returns the ERC-7930 interoperable +address (version 1) for a given chainid and ethereum address. + +
+
+ + + +
+
+

formatEvmV1(uint256 chainid) → bytes

+
+

internal

+# +
+
+
+ +Variant of #InteroperableAddress-formatV1-bytes2-bytes-bytes- that specifies an EVM chain without an address. + +
+
+ + + +
+
+

formatEvmV1(address addr) → bytes

+
+

internal

+# +
+
+
+ +Variant of #InteroperableAddress-formatV1-bytes2-bytes-bytes- that specifies an EVM address without a chain reference. + +
+
+ + + +
+
+

parseV1(bytes self) → bytes2 chainType, bytes chainReference, bytes addr

+
+

internal

+# +
+
+
+ +Parse a ERC-7930 interoperable address (version 1) into its different components. Reverts if the input is +not following a version 1 of ERC-7930 + +
+
+ + + +
+
+

parseV1Calldata(bytes self) → bytes2 chainType, bytes chainReference, bytes addr

+
+

internal

+# +
+
+
+ +Variant of [`InteroperableAddress.parseV1`](#InteroperableAddress-parseV1-bytes-) that handles calldata slices to reduce memory copy costs. + +
+
+ + + +
+
+

tryParseV1(bytes self) → bool success, bytes2 chainType, bytes chainReference, bytes addr

+
+

internal

+# +
+
+
+ +Variant of [`InteroperableAddress.parseV1`](#InteroperableAddress-parseV1-bytes-) that does not revert on invalid input. Instead, it returns `false` as the first +return value to indicate parsing failure when the input does not follow version 1 of ERC-7930. + +
+
+ + + +
+
+

tryParseV1Calldata(bytes self) → bool success, bytes2 chainType, bytes chainReference, bytes addr

+
+

internal

+# +
+
+
+ +Variant of [`InteroperableAddress.tryParseV1`](#InteroperableAddress-tryParseV1-bytes-) that handles calldata slices to reduce memory copy costs. + +
+
+ + + +
+
+

parseEvmV1(bytes self) → uint256 chainId, address addr

+
+

internal

+# +
+
+
+ +Parse a ERC-7930 interoperable address (version 1) corresponding to an EIP-155 chain. The `chainId` and +`addr` return values will be zero if the input doesn't include a chainReference or an address, respectively. + +Requirements: + +* The input must be a valid ERC-7930 interoperable address (version 1) +* The underlying chainType must be "eip-155" + +
+
+ + + +
+
+

parseEvmV1Calldata(bytes self) → uint256 chainId, address addr

+
+

internal

+# +
+
+
+ +Variant of [`InteroperableAddress.parseEvmV1`](#InteroperableAddress-parseEvmV1-bytes-) that handles calldata slices to reduce memory copy costs. + +
+
+ + + +
+
+

tryParseEvmV1(bytes self) → bool success, uint256 chainId, address addr

+
+

internal

+# +
+
+
+ +Variant of [`InteroperableAddress.parseEvmV1`](#InteroperableAddress-parseEvmV1-bytes-) that does not revert on invalid input. Instead, it returns `false` as the first +return value to indicate parsing failure when the input does not follow version 1 of ERC-7930. + +
+
+ + + +
+
+

tryParseEvmV1Calldata(bytes self) → bool success, uint256 chainId, address addr

+
+

internal

+# +
+
+
+ +Variant of [`InteroperableAddress.tryParseEvmV1`](#InteroperableAddress-tryParseEvmV1-bytes-) that handles calldata slices to reduce memory copy costs. + +
+
+ + + +
+
+

InteroperableAddressParsingError(bytes)

+
+

error

+# +
+
+
+ +
+
+ + + +
+
+

InteroperableAddressEmptyReferenceAndAddress()

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `ERC165` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +``` + +Implementation of the [`IERC165`](#IERC165) interface. + +Contracts that want to implement ERC-165 should inherit from this contract and override [`AccessControl.supportsInterface`](/contracts/5.x/api/access#AccessControl-supportsInterface-bytes4-) to check +for the additional interface id that will be supported. For example: + +```solidity +function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); +} +``` + +
+

Functions

+
+- [supportsInterface(interfaceId)](#ERC165-supportsInterface-bytes4-) +#### IERC165 [!toc] +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

public

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+ +## `ERC165Checker` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol"; +``` + +Library used to query support of an interface declared via [`IERC165`](#IERC165). + +Note that these functions return the actual result of the query: they do not +`revert` if an interface is not supported. It is up to the caller to decide +what to do in these cases. + +
+

Functions

+
+- [supportsERC165(account)](#ERC165Checker-supportsERC165-address-) +- [supportsInterface(account, interfaceId)](#ERC165Checker-supportsInterface-address-bytes4-) +- [getSupportedInterfaces(account, interfaceIds)](#ERC165Checker-getSupportedInterfaces-address-bytes4---) +- [supportsAllInterfaces(account, interfaceIds)](#ERC165Checker-supportsAllInterfaces-address-bytes4---) +- [supportsERC165InterfaceUnchecked(account, interfaceId)](#ERC165Checker-supportsERC165InterfaceUnchecked-address-bytes4-) +
+
+ + + +
+
+

supportsERC165(address account) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` supports the [`IERC165`](#IERC165) interface. + +
+
+ + + +
+
+

supportsInterface(address account, bytes4 interfaceId) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` supports the interface defined by +`interfaceId`. Support for [`IERC165`](#IERC165) itself is queried automatically. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

getSupportedInterfaces(address account, bytes4[] interfaceIds) → bool[]

+
+

internal

+# +
+
+
+ +Returns a boolean array where each value corresponds to the +interfaces passed in and whether they're supported or not. This allows +you to batch check interfaces for a contract where your expectation +is that some interfaces may not be supported. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

supportsAllInterfaces(address account, bytes4[] interfaceIds) → bool

+
+

internal

+# +
+
+
+ +Returns true if `account` supports all the interfaces defined in +`interfaceIds`. Support for [`IERC165`](#IERC165) itself is queried automatically. + +Batch-querying can lead to gas savings by skipping repeated checks for +[`IERC165`](#IERC165) support. + +See [`IERC165.supportsInterface`](#IERC165-supportsInterface-bytes4-). + +
+
+ + + +
+
+

supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) → bool

+
+

internal

+# +
+
+
+ +Assumes that account contains a contract that supports ERC-165, otherwise +the behavior of this method is undefined. This precondition can be checked +with [`ERC165Checker.supportsERC165`](#ERC165Checker-supportsERC165-address-). + +Some precompiled contracts will falsely indicate support for a given interface, so caution +should be exercised when using this function. + +Interface identification is specified in ERC-165. + +
+
+ + + +
+ +## `IERC165` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +``` + +Interface of the ERC-165 standard, as defined in the +[ERC](https://eips.ethereum.org/EIPS/eip-165). + +Implementers can declare support of contract interfaces, which can then be +queried by others ([`ERC165Checker`](#ERC165Checker)). + +For an implementation, see [`ERC165`](#ERC165). + +
+

Functions

+
+- [supportsInterface(interfaceId)](#IERC165-supportsInterface-bytes4-) +
+
+ + + +
+
+

supportsInterface(bytes4 interfaceId) → bool

+
+

external

+# +
+
+
+ +Returns true if this contract implements the interface defined by +`interfaceId`. See the corresponding +[ERC section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified) +to learn more about how these ids are created. + +This function call must use less than 30 000 gas. + +
+
+ + + +
+ +## `Math` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/Math.sol"; +``` + +Standard math utilities missing in the Solidity language. + +
+

Functions

+
+- [add512(a, b)](#Math-add512-uint256-uint256-) +- [mul512(a, b)](#Math-mul512-uint256-uint256-) +- [tryAdd(a, b)](#Math-tryAdd-uint256-uint256-) +- [trySub(a, b)](#Math-trySub-uint256-uint256-) +- [tryMul(a, b)](#Math-tryMul-uint256-uint256-) +- [tryDiv(a, b)](#Math-tryDiv-uint256-uint256-) +- [tryMod(a, b)](#Math-tryMod-uint256-uint256-) +- [saturatingAdd(a, b)](#Math-saturatingAdd-uint256-uint256-) +- [saturatingSub(a, b)](#Math-saturatingSub-uint256-uint256-) +- [saturatingMul(a, b)](#Math-saturatingMul-uint256-uint256-) +- [ternary(condition, a, b)](#Math-ternary-bool-uint256-uint256-) +- [max(a, b)](#Math-max-uint256-uint256-) +- [min(a, b)](#Math-min-uint256-uint256-) +- [average(a, b)](#Math-average-uint256-uint256-) +- [ceilDiv(a, b)](#Math-ceilDiv-uint256-uint256-) +- [mulDiv(x, y, denominator)](#Math-mulDiv-uint256-uint256-uint256-) +- [mulDiv(x, y, denominator, rounding)](#Math-mulDiv-uint256-uint256-uint256-enum-Math-Rounding-) +- [mulShr(x, y, n)](#Math-mulShr-uint256-uint256-uint8-) +- [mulShr(x, y, n, rounding)](#Math-mulShr-uint256-uint256-uint8-enum-Math-Rounding-) +- [invMod(a, n)](#Math-invMod-uint256-uint256-) +- [invModPrime(a, p)](#Math-invModPrime-uint256-uint256-) +- [modExp(b, e, m)](#Math-modExp-uint256-uint256-uint256-) +- [tryModExp(b, e, m)](#Math-tryModExp-uint256-uint256-uint256-) +- [modExp(b, e, m)](#Math-modExp-bytes-bytes-bytes-) +- [tryModExp(b, e, m)](#Math-tryModExp-bytes-bytes-bytes-) +- [sqrt(a)](#Math-sqrt-uint256-) +- [sqrt(a, rounding)](#Math-sqrt-uint256-enum-Math-Rounding-) +- [log2(x)](#Math-log2-uint256-) +- [log2(value, rounding)](#Math-log2-uint256-enum-Math-Rounding-) +- [log10(value)](#Math-log10-uint256-) +- [log10(value, rounding)](#Math-log10-uint256-enum-Math-Rounding-) +- [log256(x)](#Math-log256-uint256-) +- [log256(value, rounding)](#Math-log256-uint256-enum-Math-Rounding-) +- [unsignedRoundsUp(rounding)](#Math-unsignedRoundsUp-enum-Math-Rounding-) +- [clz(x)](#Math-clz-uint256-) +
+
+ + + +
+
+

add512(uint256 a, uint256 b) → uint256 high, uint256 low

+
+

internal

+# +
+
+
+ +Return the 512-bit addition of two uint256. + +The result is stored in two 256 variables such that sum = high * 2²⁵⁶ + low. + +
+
+ + + +
+
+

mul512(uint256 a, uint256 b) → uint256 high, uint256 low

+
+

internal

+# +
+
+
+ +Return the 512-bit multiplication of two uint256. + +The result is stored in two 256 variables such that product = high * 2²⁵⁶ + low. + +
+
+ + + +
+
+

tryAdd(uint256 a, uint256 b) → bool success, uint256 result

+
+

internal

+# +
+
+
+ +Returns the addition of two unsigned integers, with a success flag (no overflow). + +
+
+ + + +
+
+

trySub(uint256 a, uint256 b) → bool success, uint256 result

+
+

internal

+# +
+
+
+ +Returns the subtraction of two unsigned integers, with a success flag (no overflow). + +
+
+ + + +
+
+

tryMul(uint256 a, uint256 b) → bool success, uint256 result

+
+

internal

+# +
+
+
+ +Returns the multiplication of two unsigned integers, with a success flag (no overflow). + +
+
+ + + +
+
+

tryDiv(uint256 a, uint256 b) → bool success, uint256 result

+
+

internal

+# +
+
+
+ +Returns the division of two unsigned integers, with a success flag (no division by zero). + +
+
+ + + +
+
+

tryMod(uint256 a, uint256 b) → bool success, uint256 result

+
+

internal

+# +
+
+
+ +Returns the remainder of dividing two unsigned integers, with a success flag (no division by zero). + +
+
+ + + +
+
+

saturatingAdd(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Unsigned saturating addition, bounds to `2²⁵⁶ - 1` instead of overflowing. + +
+
+ + + +
+
+

saturatingSub(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Unsigned saturating subtraction, bounds to zero instead of overflowing. + +
+
+ + + +
+
+

saturatingMul(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Unsigned saturating multiplication, bounds to `2²⁵⁶ - 1` instead of overflowing. + +
+
+ + + +
+
+

ternary(bool condition, uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Branchless ternary evaluation for `condition ? a : b`. Gas costs are constant. + + +This function may reduce bytecode size and consume less gas when used standalone. +However, the compiler may optimize Solidity ternary operations (i.e. `condition ? a : b`) to only compute +one branch when needed, making this function more expensive. + + +
+
+ + + +
+
+

max(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the largest of two numbers. + +
+
+ + + +
+
+

min(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the smallest of two numbers. + +
+
+ + + +
+
+

average(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the average of two numbers. The result is rounded towards +zero. + +
+
+ + + +
+
+

ceilDiv(uint256 a, uint256 b) → uint256

+
+

internal

+# +
+
+
+ +Returns the ceiling of the division of two numbers. + +This differs from standard division with `/` in that it rounds towards infinity instead +of rounding towards zero. + +
+
+ + + +
+
+

mulDiv(uint256 x, uint256 y, uint256 denominator) → uint256 result

+
+

internal

+# +
+
+
+ +Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or +denominator == 0. + +Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by +Uniswap Labs also under MIT license. + +
+
+ + + +
+
+

mulDiv(uint256 x, uint256 y, uint256 denominator, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Calculates x * y / denominator with full precision, following the selected rounding direction. + +
+
+ + + +
+
+

mulShr(uint256 x, uint256 y, uint8 n) → uint256 result

+
+

internal

+# +
+
+
+ +Calculates floor(x * y >> n) with full precision. Throws if result overflows a uint256. + +
+
+ + + +
+
+

mulShr(uint256 x, uint256 y, uint8 n, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Calculates x * y >> n with full precision, following the selected rounding direction. + +
+
+ + + +
+
+

invMod(uint256 a, uint256 n) → uint256

+
+

internal

+# +
+
+
+ +Calculate the modular multiplicative inverse of a number in Z/nZ. + +If n is a prime, then Z/nZ is a field. In that case all elements are inversible, except 0. +If n is not a prime, then Z/nZ is not a field, and some elements might not be inversible. + +If the input value is not inversible, 0 is returned. + + +If you know for sure that n is (big) a prime, it may be cheaper to use Fermat's little theorem and get the +inverse using `Math.modExp(a, n - 2, n)`. See [`Math.invModPrime`](#Math-invModPrime-uint256-uint256-). + + +
+
+ + + +
+
+

invModPrime(uint256 a, uint256 p) → uint256

+
+

internal

+# +
+
+
+ +Variant of [`Math.invMod`](#Math-invMod-uint256-uint256-). More efficient, but only works if `p` is known to be a prime greater than `2`. + +From [Fermat's little theorem](https://en.wikipedia.org/wiki/Fermat%27s_little_theorem), we know that if p is +prime, then `a**(p-1) ≡ 1 mod p`. As a consequence, we have `a * a**(p-2) ≡ 1 mod p`, which means that +`a**(p-2)` is the modular multiplicative inverse of a in Fp. + + +this function does NOT check that `p` is a prime greater than `2`. + + +
+
+ + + +
+
+

modExp(uint256 b, uint256 e, uint256 m) → uint256

+
+

internal

+# +
+
+
+ +Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m) + +Requirements: +- modulus can't be zero +- underlying staticcall to precompile must succeed + + +The result is only valid if the underlying call succeeds. When using this function, make +sure the chain you're using it on supports the precompiled contract for modular exponentiation +at address 0x05 as specified in [EIP-198](https://eips.ethereum.org/EIPS/eip-198). Otherwise, +the underlying function will succeed given the lack of a revert, but the result may be incorrectly +interpreted as 0. + + +
+
+ + + +
+
+

tryModExp(uint256 b, uint256 e, uint256 m) → bool success, uint256 result

+
+

internal

+# +
+
+
+ +Returns the modular exponentiation of the specified base, exponent and modulus (b ** e % m). +It includes a success flag indicating if the operation succeeded. Operation will be marked as failed if trying +to operate modulo 0 or if the underlying precompile reverted. + + +The result is only valid if the success flag is true. When using this function, make sure the chain +you're using it on supports the precompiled contract for modular exponentiation at address 0x05 as specified in +[EIP-198](https://eips.ethereum.org/EIPS/eip-198). Otherwise, the underlying function will succeed given the lack +of a revert, but the result may be incorrectly interpreted as 0. + + +
+
+ + + +
+
+

modExp(bytes b, bytes e, bytes m) → bytes

+
+

internal

+# +
+
+
+ +Variant of [`Math.modExp`](#Math-modExp-bytes-bytes-bytes-) that supports inputs of arbitrary length. + +
+
+ + + +
+
+

tryModExp(bytes b, bytes e, bytes m) → bool success, bytes result

+
+

internal

+# +
+
+
+ +Variant of [`Math.tryModExp`](#Math-tryModExp-bytes-bytes-bytes-) that supports inputs of arbitrary length. + +
+
+ + + +
+
+

sqrt(uint256 a) → uint256

+
+

internal

+# +
+
+
+ +Returns the square root of a number. If the number is not a perfect square, the value is rounded +towards zero. + +This method is based on Newton's method for computing square roots; the algorithm is restricted to only +using integer operations. + +
+
+ + + +
+
+

sqrt(uint256 a, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Calculates sqrt(a), following the selected rounding direction. + +
+
+ + + +
+
+

log2(uint256 x) → uint256 r

+
+

internal

+# +
+
+
+ +Return the log in base 2 of a positive value rounded towards zero. +Returns 0 if given 0. + +
+
+ + + +
+
+

log2(uint256 value, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 2, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

log10(uint256 value) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 10 of a positive value rounded towards zero. +Returns 0 if given 0. + +
+
+ + + +
+
+

log10(uint256 value, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 10, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

log256(uint256 x) → uint256 r

+
+

internal

+# +
+
+
+ +Return the log in base 256 of a positive value rounded towards zero. +Returns 0 if given 0. + +Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string. + +
+
+ + + +
+
+

log256(uint256 value, enum Math.Rounding rounding) → uint256

+
+

internal

+# +
+
+
+ +Return the log in base 256, following the selected rounding direction, of a positive value. +Returns 0 if given 0. + +
+
+ + + +
+
+

unsignedRoundsUp(enum Math.Rounding rounding) → bool

+
+

internal

+# +
+
+
+ +Returns whether a provided rounding mode is considered rounding up for unsigned integers. + +
+
+ + + +
+
+

clz(uint256 x) → uint256

+
+

internal

+# +
+
+
+ +Counts the number of leading zero bits in a uint256. + +
+
+ + + +
+ +## `SafeCast` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +``` + +Wrappers over Solidity's uintXX/intXX/bool casting operators with added overflow +checks. + +Downcasting from uint256/int256 in Solidity does not revert on overflow. This can +easily result in undesired exploitation or bugs, since developers usually +assume that overflows raise errors. `SafeCast` restores this intuition by +reverting the transaction when such an operation overflows. + +Using this library instead of the unchecked operations eliminates an entire +class of bugs, so it's recommended to use it always. + +
+

Functions

+
+- [toUint248(value)](#SafeCast-toUint248-uint256-) +- [toUint240(value)](#SafeCast-toUint240-uint256-) +- [toUint232(value)](#SafeCast-toUint232-uint256-) +- [toUint224(value)](#SafeCast-toUint224-uint256-) +- [toUint216(value)](#SafeCast-toUint216-uint256-) +- [toUint208(value)](#SafeCast-toUint208-uint256-) +- [toUint200(value)](#SafeCast-toUint200-uint256-) +- [toUint192(value)](#SafeCast-toUint192-uint256-) +- [toUint184(value)](#SafeCast-toUint184-uint256-) +- [toUint176(value)](#SafeCast-toUint176-uint256-) +- [toUint168(value)](#SafeCast-toUint168-uint256-) +- [toUint160(value)](#SafeCast-toUint160-uint256-) +- [toUint152(value)](#SafeCast-toUint152-uint256-) +- [toUint144(value)](#SafeCast-toUint144-uint256-) +- [toUint136(value)](#SafeCast-toUint136-uint256-) +- [toUint128(value)](#SafeCast-toUint128-uint256-) +- [toUint120(value)](#SafeCast-toUint120-uint256-) +- [toUint112(value)](#SafeCast-toUint112-uint256-) +- [toUint104(value)](#SafeCast-toUint104-uint256-) +- [toUint96(value)](#SafeCast-toUint96-uint256-) +- [toUint88(value)](#SafeCast-toUint88-uint256-) +- [toUint80(value)](#SafeCast-toUint80-uint256-) +- [toUint72(value)](#SafeCast-toUint72-uint256-) +- [toUint64(value)](#SafeCast-toUint64-uint256-) +- [toUint56(value)](#SafeCast-toUint56-uint256-) +- [toUint48(value)](#SafeCast-toUint48-uint256-) +- [toUint40(value)](#SafeCast-toUint40-uint256-) +- [toUint32(value)](#SafeCast-toUint32-uint256-) +- [toUint24(value)](#SafeCast-toUint24-uint256-) +- [toUint16(value)](#SafeCast-toUint16-uint256-) +- [toUint8(value)](#SafeCast-toUint8-uint256-) +- [toUint256(value)](#SafeCast-toUint256-int256-) +- [toInt248(value)](#SafeCast-toInt248-int256-) +- [toInt240(value)](#SafeCast-toInt240-int256-) +- [toInt232(value)](#SafeCast-toInt232-int256-) +- [toInt224(value)](#SafeCast-toInt224-int256-) +- [toInt216(value)](#SafeCast-toInt216-int256-) +- [toInt208(value)](#SafeCast-toInt208-int256-) +- [toInt200(value)](#SafeCast-toInt200-int256-) +- [toInt192(value)](#SafeCast-toInt192-int256-) +- [toInt184(value)](#SafeCast-toInt184-int256-) +- [toInt176(value)](#SafeCast-toInt176-int256-) +- [toInt168(value)](#SafeCast-toInt168-int256-) +- [toInt160(value)](#SafeCast-toInt160-int256-) +- [toInt152(value)](#SafeCast-toInt152-int256-) +- [toInt144(value)](#SafeCast-toInt144-int256-) +- [toInt136(value)](#SafeCast-toInt136-int256-) +- [toInt128(value)](#SafeCast-toInt128-int256-) +- [toInt120(value)](#SafeCast-toInt120-int256-) +- [toInt112(value)](#SafeCast-toInt112-int256-) +- [toInt104(value)](#SafeCast-toInt104-int256-) +- [toInt96(value)](#SafeCast-toInt96-int256-) +- [toInt88(value)](#SafeCast-toInt88-int256-) +- [toInt80(value)](#SafeCast-toInt80-int256-) +- [toInt72(value)](#SafeCast-toInt72-int256-) +- [toInt64(value)](#SafeCast-toInt64-int256-) +- [toInt56(value)](#SafeCast-toInt56-int256-) +- [toInt48(value)](#SafeCast-toInt48-int256-) +- [toInt40(value)](#SafeCast-toInt40-int256-) +- [toInt32(value)](#SafeCast-toInt32-int256-) +- [toInt24(value)](#SafeCast-toInt24-int256-) +- [toInt16(value)](#SafeCast-toInt16-int256-) +- [toInt8(value)](#SafeCast-toInt8-int256-) +- [toInt256(value)](#SafeCast-toInt256-uint256-) +- [toUint(b)](#SafeCast-toUint-bool-) +
+
+ +
+

Errors

+
+- [SafeCastOverflowedUintDowncast(bits, value)](#SafeCast-SafeCastOverflowedUintDowncast-uint8-uint256-) +- [SafeCastOverflowedIntToUint(value)](#SafeCast-SafeCastOverflowedIntToUint-int256-) +- [SafeCastOverflowedIntDowncast(bits, value)](#SafeCast-SafeCastOverflowedIntDowncast-uint8-int256-) +- [SafeCastOverflowedUintToInt(value)](#SafeCast-SafeCastOverflowedUintToInt-uint256-) +
+
+ + + +
+
+

toUint248(uint256 value) → uint248

+
+

internal

+# +
+
+
+ +Returns the downcasted uint248 from uint256, reverting on +overflow (when the input is greater than largest uint248). + +Counterpart to Solidity's `uint248` operator. + +Requirements: + +- input must fit into 248 bits + +
+
+ + + +
+
+

toUint240(uint256 value) → uint240

+
+

internal

+# +
+
+
+ +Returns the downcasted uint240 from uint256, reverting on +overflow (when the input is greater than largest uint240). + +Counterpart to Solidity's `uint240` operator. + +Requirements: + +- input must fit into 240 bits + +
+
+ + + +
+
+

toUint232(uint256 value) → uint232

+
+

internal

+# +
+
+
+ +Returns the downcasted uint232 from uint256, reverting on +overflow (when the input is greater than largest uint232). + +Counterpart to Solidity's `uint232` operator. + +Requirements: + +- input must fit into 232 bits + +
+
+ + + +
+
+

toUint224(uint256 value) → uint224

+
+

internal

+# +
+
+
+ +Returns the downcasted uint224 from uint256, reverting on +overflow (when the input is greater than largest uint224). + +Counterpart to Solidity's `uint224` operator. + +Requirements: + +- input must fit into 224 bits + +
+
+ + + +
+
+

toUint216(uint256 value) → uint216

+
+

internal

+# +
+
+
+ +Returns the downcasted uint216 from uint256, reverting on +overflow (when the input is greater than largest uint216). + +Counterpart to Solidity's `uint216` operator. + +Requirements: + +- input must fit into 216 bits + +
+
+ + + +
+
+

toUint208(uint256 value) → uint208

+
+

internal

+# +
+
+
+ +Returns the downcasted uint208 from uint256, reverting on +overflow (when the input is greater than largest uint208). + +Counterpart to Solidity's `uint208` operator. + +Requirements: + +- input must fit into 208 bits + +
+
+ + + +
+
+

toUint200(uint256 value) → uint200

+
+

internal

+# +
+
+
+ +Returns the downcasted uint200 from uint256, reverting on +overflow (when the input is greater than largest uint200). + +Counterpart to Solidity's `uint200` operator. + +Requirements: + +- input must fit into 200 bits + +
+
+ + + +
+
+

toUint192(uint256 value) → uint192

+
+

internal

+# +
+
+
+ +Returns the downcasted uint192 from uint256, reverting on +overflow (when the input is greater than largest uint192). + +Counterpart to Solidity's `uint192` operator. + +Requirements: + +- input must fit into 192 bits + +
+
+ + + +
+
+

toUint184(uint256 value) → uint184

+
+

internal

+# +
+
+
+ +Returns the downcasted uint184 from uint256, reverting on +overflow (when the input is greater than largest uint184). + +Counterpart to Solidity's `uint184` operator. + +Requirements: + +- input must fit into 184 bits + +
+
+ + + +
+
+

toUint176(uint256 value) → uint176

+
+

internal

+# +
+
+
+ +Returns the downcasted uint176 from uint256, reverting on +overflow (when the input is greater than largest uint176). + +Counterpart to Solidity's `uint176` operator. + +Requirements: + +- input must fit into 176 bits + +
+
+ + + +
+
+

toUint168(uint256 value) → uint168

+
+

internal

+# +
+
+
+ +Returns the downcasted uint168 from uint256, reverting on +overflow (when the input is greater than largest uint168). + +Counterpart to Solidity's `uint168` operator. + +Requirements: + +- input must fit into 168 bits + +
+
+ + + +
+
+

toUint160(uint256 value) → uint160

+
+

internal

+# +
+
+
+ +Returns the downcasted uint160 from uint256, reverting on +overflow (when the input is greater than largest uint160). + +Counterpart to Solidity's `uint160` operator. + +Requirements: + +- input must fit into 160 bits + +
+
+ + + +
+
+

toUint152(uint256 value) → uint152

+
+

internal

+# +
+
+
+ +Returns the downcasted uint152 from uint256, reverting on +overflow (when the input is greater than largest uint152). + +Counterpart to Solidity's `uint152` operator. + +Requirements: + +- input must fit into 152 bits + +
+
+ + + +
+
+

toUint144(uint256 value) → uint144

+
+

internal

+# +
+
+
+ +Returns the downcasted uint144 from uint256, reverting on +overflow (when the input is greater than largest uint144). + +Counterpart to Solidity's `uint144` operator. + +Requirements: + +- input must fit into 144 bits + +
+
+ + + +
+
+

toUint136(uint256 value) → uint136

+
+

internal

+# +
+
+
+ +Returns the downcasted uint136 from uint256, reverting on +overflow (when the input is greater than largest uint136). + +Counterpart to Solidity's `uint136` operator. + +Requirements: + +- input must fit into 136 bits + +
+
+ + + +
+
+

toUint128(uint256 value) → uint128

+
+

internal

+# +
+
+
+ +Returns the downcasted uint128 from uint256, reverting on +overflow (when the input is greater than largest uint128). + +Counterpart to Solidity's `uint128` operator. + +Requirements: + +- input must fit into 128 bits + +
+
+ + + +
+
+

toUint120(uint256 value) → uint120

+
+

internal

+# +
+
+
+ +Returns the downcasted uint120 from uint256, reverting on +overflow (when the input is greater than largest uint120). + +Counterpart to Solidity's `uint120` operator. + +Requirements: + +- input must fit into 120 bits + +
+
+ + + +
+
+

toUint112(uint256 value) → uint112

+
+

internal

+# +
+
+
+ +Returns the downcasted uint112 from uint256, reverting on +overflow (when the input is greater than largest uint112). + +Counterpart to Solidity's `uint112` operator. + +Requirements: + +- input must fit into 112 bits + +
+
+ + + +
+
+

toUint104(uint256 value) → uint104

+
+

internal

+# +
+
+
+ +Returns the downcasted uint104 from uint256, reverting on +overflow (when the input is greater than largest uint104). + +Counterpart to Solidity's `uint104` operator. + +Requirements: + +- input must fit into 104 bits + +
+
+ + + +
+
+

toUint96(uint256 value) → uint96

+
+

internal

+# +
+
+
+ +Returns the downcasted uint96 from uint256, reverting on +overflow (when the input is greater than largest uint96). + +Counterpart to Solidity's `uint96` operator. + +Requirements: + +- input must fit into 96 bits + +
+
+ + + +
+
+

toUint88(uint256 value) → uint88

+
+

internal

+# +
+
+
+ +Returns the downcasted uint88 from uint256, reverting on +overflow (when the input is greater than largest uint88). + +Counterpart to Solidity's `uint88` operator. + +Requirements: + +- input must fit into 88 bits + +
+
+ + + +
+
+

toUint80(uint256 value) → uint80

+
+

internal

+# +
+
+
+ +Returns the downcasted uint80 from uint256, reverting on +overflow (when the input is greater than largest uint80). + +Counterpart to Solidity's `uint80` operator. + +Requirements: + +- input must fit into 80 bits + +
+
+ + + +
+
+

toUint72(uint256 value) → uint72

+
+

internal

+# +
+
+
+ +Returns the downcasted uint72 from uint256, reverting on +overflow (when the input is greater than largest uint72). + +Counterpart to Solidity's `uint72` operator. + +Requirements: + +- input must fit into 72 bits + +
+
+ + + +
+
+

toUint64(uint256 value) → uint64

+
+

internal

+# +
+
+
+ +Returns the downcasted uint64 from uint256, reverting on +overflow (when the input is greater than largest uint64). + +Counterpart to Solidity's `uint64` operator. + +Requirements: + +- input must fit into 64 bits + +
+
+ + + +
+
+

toUint56(uint256 value) → uint56

+
+

internal

+# +
+
+
+ +Returns the downcasted uint56 from uint256, reverting on +overflow (when the input is greater than largest uint56). + +Counterpart to Solidity's `uint56` operator. + +Requirements: + +- input must fit into 56 bits + +
+
+ + + +
+
+

toUint48(uint256 value) → uint48

+
+

internal

+# +
+
+
+ +Returns the downcasted uint48 from uint256, reverting on +overflow (when the input is greater than largest uint48). + +Counterpart to Solidity's `uint48` operator. + +Requirements: + +- input must fit into 48 bits + +
+
+ + + +
+
+

toUint40(uint256 value) → uint40

+
+

internal

+# +
+
+
+ +Returns the downcasted uint40 from uint256, reverting on +overflow (when the input is greater than largest uint40). + +Counterpart to Solidity's `uint40` operator. + +Requirements: + +- input must fit into 40 bits + +
+
+ + + +
+
+

toUint32(uint256 value) → uint32

+
+

internal

+# +
+
+
+ +Returns the downcasted uint32 from uint256, reverting on +overflow (when the input is greater than largest uint32). + +Counterpart to Solidity's `uint32` operator. + +Requirements: + +- input must fit into 32 bits + +
+
+ + + +
+
+

toUint24(uint256 value) → uint24

+
+

internal

+# +
+
+
+ +Returns the downcasted uint24 from uint256, reverting on +overflow (when the input is greater than largest uint24). + +Counterpart to Solidity's `uint24` operator. + +Requirements: + +- input must fit into 24 bits + +
+
+ + + +
+
+

toUint16(uint256 value) → uint16

+
+

internal

+# +
+
+
+ +Returns the downcasted uint16 from uint256, reverting on +overflow (when the input is greater than largest uint16). + +Counterpart to Solidity's `uint16` operator. + +Requirements: + +- input must fit into 16 bits + +
+
+ + + +
+
+

toUint8(uint256 value) → uint8

+
+

internal

+# +
+
+
+ +Returns the downcasted uint8 from uint256, reverting on +overflow (when the input is greater than largest uint8). + +Counterpart to Solidity's `uint8` operator. + +Requirements: + +- input must fit into 8 bits + +
+
+ + + +
+
+

toUint256(int256 value) → uint256

+
+

internal

+# +
+
+
+ +Converts a signed int256 into an unsigned uint256. + +Requirements: + +- input must be greater than or equal to 0. + +
+
+ + + +
+
+

toInt248(int256 value) → int248 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int248 from int256, reverting on +overflow (when the input is less than smallest int248 or +greater than largest int248). + +Counterpart to Solidity's `int248` operator. + +Requirements: + +- input must fit into 248 bits + +
+
+ + + +
+
+

toInt240(int256 value) → int240 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int240 from int256, reverting on +overflow (when the input is less than smallest int240 or +greater than largest int240). + +Counterpart to Solidity's `int240` operator. + +Requirements: + +- input must fit into 240 bits + +
+
+ + + +
+
+

toInt232(int256 value) → int232 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int232 from int256, reverting on +overflow (when the input is less than smallest int232 or +greater than largest int232). + +Counterpart to Solidity's `int232` operator. + +Requirements: + +- input must fit into 232 bits + +
+
+ + + +
+
+

toInt224(int256 value) → int224 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int224 from int256, reverting on +overflow (when the input is less than smallest int224 or +greater than largest int224). + +Counterpart to Solidity's `int224` operator. + +Requirements: + +- input must fit into 224 bits + +
+
+ + + +
+
+

toInt216(int256 value) → int216 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int216 from int256, reverting on +overflow (when the input is less than smallest int216 or +greater than largest int216). + +Counterpart to Solidity's `int216` operator. + +Requirements: + +- input must fit into 216 bits + +
+
+ + + +
+
+

toInt208(int256 value) → int208 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int208 from int256, reverting on +overflow (when the input is less than smallest int208 or +greater than largest int208). + +Counterpart to Solidity's `int208` operator. + +Requirements: + +- input must fit into 208 bits + +
+
+ + + +
+
+

toInt200(int256 value) → int200 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int200 from int256, reverting on +overflow (when the input is less than smallest int200 or +greater than largest int200). + +Counterpart to Solidity's `int200` operator. + +Requirements: + +- input must fit into 200 bits + +
+
+ + + +
+
+

toInt192(int256 value) → int192 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int192 from int256, reverting on +overflow (when the input is less than smallest int192 or +greater than largest int192). + +Counterpart to Solidity's `int192` operator. + +Requirements: + +- input must fit into 192 bits + +
+
+ + + +
+
+

toInt184(int256 value) → int184 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int184 from int256, reverting on +overflow (when the input is less than smallest int184 or +greater than largest int184). + +Counterpart to Solidity's `int184` operator. + +Requirements: + +- input must fit into 184 bits + +
+
+ + + +
+
+

toInt176(int256 value) → int176 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int176 from int256, reverting on +overflow (when the input is less than smallest int176 or +greater than largest int176). + +Counterpart to Solidity's `int176` operator. + +Requirements: + +- input must fit into 176 bits + +
+
+ + + +
+
+

toInt168(int256 value) → int168 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int168 from int256, reverting on +overflow (when the input is less than smallest int168 or +greater than largest int168). + +Counterpart to Solidity's `int168` operator. + +Requirements: + +- input must fit into 168 bits + +
+
+ + + +
+
+

toInt160(int256 value) → int160 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int160 from int256, reverting on +overflow (when the input is less than smallest int160 or +greater than largest int160). + +Counterpart to Solidity's `int160` operator. + +Requirements: + +- input must fit into 160 bits + +
+
+ + + +
+
+

toInt152(int256 value) → int152 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int152 from int256, reverting on +overflow (when the input is less than smallest int152 or +greater than largest int152). + +Counterpart to Solidity's `int152` operator. + +Requirements: + +- input must fit into 152 bits + +
+
+ + + +
+
+

toInt144(int256 value) → int144 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int144 from int256, reverting on +overflow (when the input is less than smallest int144 or +greater than largest int144). + +Counterpart to Solidity's `int144` operator. + +Requirements: + +- input must fit into 144 bits + +
+
+ + + +
+
+

toInt136(int256 value) → int136 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int136 from int256, reverting on +overflow (when the input is less than smallest int136 or +greater than largest int136). + +Counterpart to Solidity's `int136` operator. + +Requirements: + +- input must fit into 136 bits + +
+
+ + + +
+
+

toInt128(int256 value) → int128 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int128 from int256, reverting on +overflow (when the input is less than smallest int128 or +greater than largest int128). + +Counterpart to Solidity's `int128` operator. + +Requirements: + +- input must fit into 128 bits + +
+
+ + + +
+
+

toInt120(int256 value) → int120 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int120 from int256, reverting on +overflow (when the input is less than smallest int120 or +greater than largest int120). + +Counterpart to Solidity's `int120` operator. + +Requirements: + +- input must fit into 120 bits + +
+
+ + + +
+
+

toInt112(int256 value) → int112 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int112 from int256, reverting on +overflow (when the input is less than smallest int112 or +greater than largest int112). + +Counterpart to Solidity's `int112` operator. + +Requirements: + +- input must fit into 112 bits + +
+
+ + + +
+
+

toInt104(int256 value) → int104 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int104 from int256, reverting on +overflow (when the input is less than smallest int104 or +greater than largest int104). + +Counterpart to Solidity's `int104` operator. + +Requirements: + +- input must fit into 104 bits + +
+
+ + + +
+
+

toInt96(int256 value) → int96 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int96 from int256, reverting on +overflow (when the input is less than smallest int96 or +greater than largest int96). + +Counterpart to Solidity's `int96` operator. + +Requirements: + +- input must fit into 96 bits + +
+
+ + + +
+
+

toInt88(int256 value) → int88 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int88 from int256, reverting on +overflow (when the input is less than smallest int88 or +greater than largest int88). + +Counterpart to Solidity's `int88` operator. + +Requirements: + +- input must fit into 88 bits + +
+
+ + + +
+
+

toInt80(int256 value) → int80 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int80 from int256, reverting on +overflow (when the input is less than smallest int80 or +greater than largest int80). + +Counterpart to Solidity's `int80` operator. + +Requirements: + +- input must fit into 80 bits + +
+
+ + + +
+
+

toInt72(int256 value) → int72 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int72 from int256, reverting on +overflow (when the input is less than smallest int72 or +greater than largest int72). + +Counterpart to Solidity's `int72` operator. + +Requirements: + +- input must fit into 72 bits + +
+
+ + + +
+
+

toInt64(int256 value) → int64 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int64 from int256, reverting on +overflow (when the input is less than smallest int64 or +greater than largest int64). + +Counterpart to Solidity's `int64` operator. + +Requirements: + +- input must fit into 64 bits + +
+
+ + + +
+
+

toInt56(int256 value) → int56 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int56 from int256, reverting on +overflow (when the input is less than smallest int56 or +greater than largest int56). + +Counterpart to Solidity's `int56` operator. + +Requirements: + +- input must fit into 56 bits + +
+
+ + + +
+
+

toInt48(int256 value) → int48 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int48 from int256, reverting on +overflow (when the input is less than smallest int48 or +greater than largest int48). + +Counterpart to Solidity's `int48` operator. + +Requirements: + +- input must fit into 48 bits + +
+
+ + + +
+
+

toInt40(int256 value) → int40 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int40 from int256, reverting on +overflow (when the input is less than smallest int40 or +greater than largest int40). + +Counterpart to Solidity's `int40` operator. + +Requirements: + +- input must fit into 40 bits + +
+
+ + + +
+
+

toInt32(int256 value) → int32 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int32 from int256, reverting on +overflow (when the input is less than smallest int32 or +greater than largest int32). + +Counterpart to Solidity's `int32` operator. + +Requirements: + +- input must fit into 32 bits + +
+
+ + + +
+
+

toInt24(int256 value) → int24 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int24 from int256, reverting on +overflow (when the input is less than smallest int24 or +greater than largest int24). + +Counterpart to Solidity's `int24` operator. + +Requirements: + +- input must fit into 24 bits + +
+
+ + + +
+
+

toInt16(int256 value) → int16 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int16 from int256, reverting on +overflow (when the input is less than smallest int16 or +greater than largest int16). + +Counterpart to Solidity's `int16` operator. + +Requirements: + +- input must fit into 16 bits + +
+
+ + + +
+
+

toInt8(int256 value) → int8 downcasted

+
+

internal

+# +
+
+
+ +Returns the downcasted int8 from int256, reverting on +overflow (when the input is less than smallest int8 or +greater than largest int8). + +Counterpart to Solidity's `int8` operator. + +Requirements: + +- input must fit into 8 bits + +
+
+ + + +
+
+

toInt256(uint256 value) → int256

+
+

internal

+# +
+
+
+ +Converts an unsigned uint256 into a signed int256. + +Requirements: + +- input must be less than or equal to maxInt256. + +
+
+ + + +
+
+

toUint(bool b) → uint256 u

+
+

internal

+# +
+
+
+ +Cast a boolean (false or true) to a uint256 (0 or 1) with no jump. + +
+
+ + + +
+
+

SafeCastOverflowedUintDowncast(uint8 bits, uint256 value)

+
+

error

+# +
+
+
+ +Value doesn't fit in an uint of `bits` size. + +
+
+ + + +
+
+

SafeCastOverflowedIntToUint(int256 value)

+
+

error

+# +
+
+
+ +An int value doesn't fit in an uint of `bits` size. + +
+
+ + + +
+
+

SafeCastOverflowedIntDowncast(uint8 bits, int256 value)

+
+

error

+# +
+
+
+ +Value doesn't fit in an int of `bits` size. + +
+
+ + + +
+
+

SafeCastOverflowedUintToInt(uint256 value)

+
+

error

+# +
+
+
+ +An uint value doesn't fit in an int of `bits` size. + +
+
+ + + +
+ +## `SignedMath` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/math/SignedMath.sol"; +``` + +Standard signed math utilities missing in the Solidity language. + +
+

Functions

+
+- [ternary(condition, a, b)](#SignedMath-ternary-bool-int256-int256-) +- [max(a, b)](#SignedMath-max-int256-int256-) +- [min(a, b)](#SignedMath-min-int256-int256-) +- [average(a, b)](#SignedMath-average-int256-int256-) +- [abs(n)](#SignedMath-abs-int256-) +
+
+ + + +
+
+

ternary(bool condition, int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Branchless ternary evaluation for `a ? b : c`. Gas costs are constant. + + +This function may reduce bytecode size and consume less gas when used standalone. +However, the compiler may optimize Solidity ternary operations (i.e. `a ? b : c`) to only compute +one branch when needed, making this function more expensive. + + +
+
+ + + +
+
+

max(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the largest of two signed numbers. + +
+
+ + + +
+
+

min(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the smallest of two signed numbers. + +
+
+ + + +
+
+

average(int256 a, int256 b) → int256

+
+

internal

+# +
+
+
+ +Returns the average of two signed numbers without overflow. +The result is rounded towards zero. + +
+
+ + + +
+
+

abs(int256 n) → uint256

+
+

internal

+# +
+
+
+ +Returns the absolute unsigned value of a signed value. + +
+
+ + + +
+ +## `Accumulators` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/Accumulators.sol"; +``` + +Structure concatenating an arbitrary number of bytes buffers with limited memory allocation. + +The Accumulators library provides a memory-efficient alternative to repeated concatenation of bytes. +Instead of copying data on each concatenation (O(n**2) complexity), it builds a linked list of references +to existing data and performs a single memory allocation during flattening (O(n) complexity). + +Uses 0x00 as sentinel value for empty state (i.e. null pointers) + +==== How it works + +1. Create an empty accumulator with null head/tail pointers +2. Add data using [`RLP.push`](#RLP-push-struct-RLP-Encoder-struct-RLP-Encoder-) (append) or [`Accumulators.shift`](#Accumulators-shift-struct-Accumulators-Accumulator-Memory-Slice-) (prepend). It creates linked list nodes +3. Each node stores a reference to existing data (no copying) +4. Call [`Accumulators.flatten`](#Accumulators-flatten-struct-Accumulators-Accumulator-) to materialize the final concatenated result in a single operation + +==== Performance + +* Addition: O(1) per operation (just pointer manipulation) +* Flattening: O(n) single pass with one memory allocation +* Memory: Minimal overhead until flattening (only stores references) + +
+

Functions

+
+- [accumulator()](#Accumulators-accumulator--) +- [push(self, data)](#Accumulators-push-struct-Accumulators-Accumulator-bytes-) +- [push(self, data)](#Accumulators-push-struct-Accumulators-Accumulator-Memory-Slice-) +- [shift(self, data)](#Accumulators-shift-struct-Accumulators-Accumulator-bytes-) +- [shift(self, data)](#Accumulators-shift-struct-Accumulators-Accumulator-Memory-Slice-) +- [flatten(self)](#Accumulators-flatten-struct-Accumulators-Accumulator-) +
+
+ + + +
+
+

accumulator() → struct Accumulators.Accumulator self

+
+

internal

+# +
+
+
+ +Create a new (empty) accumulator + +
+
+ + + +
+
+

push(struct Accumulators.Accumulator self, bytes data) → struct Accumulators.Accumulator

+
+

internal

+# +
+
+
+ +Add a bytes buffer to (the end of) an Accumulator + +
+
+ + + +
+
+

push(struct Accumulators.Accumulator self, Memory.Slice data) → struct Accumulators.Accumulator

+
+

internal

+# +
+
+
+ +Add a memory slice to (the end of) an Accumulator + +
+
+ + + +
+
+

shift(struct Accumulators.Accumulator self, bytes data) → struct Accumulators.Accumulator

+
+

internal

+# +
+
+
+ +Add a bytes buffer to (the beginning of) an Accumulator + +
+
+ + + +
+
+

shift(struct Accumulators.Accumulator self, Memory.Slice data) → struct Accumulators.Accumulator

+
+

internal

+# +
+
+
+ +Add a memory slice to (the beginning of) an Accumulator + +
+
+ + + +
+
+

flatten(struct Accumulators.Accumulator self) → bytes result

+
+

internal

+# +
+
+
+ +Flatten all the bytes entries in an Accumulator into a single buffer + +
+
+ + + +
+ +## `BitMaps` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/BitMaps.sol"; +``` + +Library for managing uint256 to bool mapping in a compact and efficient way, provided the keys are sequential. +Largely inspired by Uniswap's [merkle-distributor](https://github.com/Uniswap/merkle-distributor/blob/master/contracts/MerkleDistributor.sol). + +BitMaps pack 256 booleans across each bit of a single 256-bit slot of `uint256` type. +Hence booleans corresponding to 256 _sequential_ indices would only consume a single slot, +unlike the regular `bool` which would consume an entire slot for a single value. + +This results in gas savings in two ways: + +- Setting a zero value to non-zero only once every 256 times +- Accessing the same warm slot for every 256 _sequential_ indices + +
+

Functions

+
+- [get(bitmap, index)](#BitMaps-get-struct-BitMaps-BitMap-uint256-) +- [setTo(bitmap, index, value)](#BitMaps-setTo-struct-BitMaps-BitMap-uint256-bool-) +- [set(bitmap, index)](#BitMaps-set-struct-BitMaps-BitMap-uint256-) +- [unset(bitmap, index)](#BitMaps-unset-struct-BitMaps-BitMap-uint256-) +
+
+ + + +
+
+

get(struct BitMaps.BitMap bitmap, uint256 index) → bool

+
+

internal

+# +
+
+
+ +Returns whether the bit at `index` is set. + +
+
+ + + +
+
+

setTo(struct BitMaps.BitMap bitmap, uint256 index, bool value)

+
+

internal

+# +
+
+
+ +Sets the bit at `index` to the boolean `value`. + +
+
+ + + +
+
+

set(struct BitMaps.BitMap bitmap, uint256 index)

+
+

internal

+# +
+
+
+ +Sets the bit at `index`. + +
+
+ + + +
+
+

unset(struct BitMaps.BitMap bitmap, uint256 index)

+
+

internal

+# +
+
+
+ +Unsets the bit at `index`. + +
+
+ + + +
+ +## `Checkpoints` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; +``` + +This library defines the `Trace*` struct, for checkpointing values as they change at different points in +time, and later looking up past values by block number. See [`Votes`](/contracts/5.x/api/governance#Votes) as an example. + +To create a history of checkpoints define a variable type `Checkpoints.Trace*` in your contract, and store a new +checkpoint for the current transaction block using the [`RLP.push`](#RLP-push-struct-RLP-Encoder-struct-RLP-Encoder-) function. + +
+

Functions

+
+- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace256-uint256-uint256-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace256-uint256-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace256-uint256-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace256-uint256-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace256-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace256-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace256-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace256-uint32-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace224-uint32-uint224-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace224-uint32-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace224-uint32-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace224-uint32-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace224-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace224-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace224-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace224-uint32-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace208-uint48-uint208-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace208-uint48-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace208-uint48-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace208-uint48-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace208-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace208-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace208-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace208-uint32-) +- [push(self, key, value)](#Checkpoints-push-struct-Checkpoints-Trace160-uint96-uint160-) +- [lowerLookup(self, key)](#Checkpoints-lowerLookup-struct-Checkpoints-Trace160-uint96-) +- [upperLookup(self, key)](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) +- [upperLookupRecent(self, key)](#Checkpoints-upperLookupRecent-struct-Checkpoints-Trace160-uint96-) +- [latest(self)](#Checkpoints-latest-struct-Checkpoints-Trace160-) +- [latestCheckpoint(self)](#Checkpoints-latestCheckpoint-struct-Checkpoints-Trace160-) +- [length(self)](#Checkpoints-length-struct-Checkpoints-Trace160-) +- [at(self, pos)](#Checkpoints-at-struct-Checkpoints-Trace160-uint32-) +
+
+ +
+

Errors

+
+- [CheckpointUnorderedInsertion()](#Checkpoints-CheckpointUnorderedInsertion--) +
+
+ + + +
+
+

push(struct Checkpoints.Trace256 self, uint256 key, uint256 value) → uint256 oldValue, uint256 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace256 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint256).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace256 self, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace256 self, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace256 self, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace256 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace256 self) → bool exists, uint256 _key, uint256 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace256 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace256 self, uint32 pos) → struct Checkpoints.Checkpoint256

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace224 self, uint32 key, uint224 value) → uint224 oldValue, uint224 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace224 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint32).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace224 self, uint32 key) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace224 self) → uint224

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace224 self) → bool exists, uint32 _key, uint224 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace224 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace224 self, uint32 pos) → struct Checkpoints.Checkpoint224

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace208 self, uint48 key, uint208 value) → uint208 oldValue, uint208 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace208 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint48).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace208 self, uint48 key) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace208 self, uint48 key) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace208 self, uint48 key) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace208 self) → uint208

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace208 self) → bool exists, uint48 _key, uint208 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace208 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace208 self, uint32 pos) → struct Checkpoints.Checkpoint208

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

push(struct Checkpoints.Trace160 self, uint96 key, uint160 value) → uint160 oldValue, uint160 newValue

+
+

internal

+# +
+
+
+ +Pushes a (`key`, `value`) pair into a Trace160 so that it is stored as the checkpoint. + +Returns previous value and new value. + + +Never accept `key` as a user input, since an arbitrary `type(uint96).max` key set will disable the +library. + + +
+
+ + + +
+
+

lowerLookup(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the first (oldest) checkpoint with key greater or equal than the search key, or zero if +there is none. + +
+
+ + + +
+
+

upperLookup(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + +
+
+ + + +
+
+

upperLookupRecent(struct Checkpoints.Trace160 self, uint96 key) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the last (most recent) checkpoint with key lower or equal than the search key, or zero +if there is none. + + +This is a variant of [`Checkpoints.upperLookup`](#Checkpoints-upperLookup-struct-Checkpoints-Trace160-uint96-) that is optimized to find "recent" checkpoint (checkpoints with high +keys). + + +
+
+ + + +
+
+

latest(struct Checkpoints.Trace160 self) → uint160

+
+

internal

+# +
+
+
+ +Returns the value in the most recent checkpoint, or zero if there are no checkpoints. + +
+
+ + + +
+
+

latestCheckpoint(struct Checkpoints.Trace160 self) → bool exists, uint96 _key, uint160 _value

+
+

internal

+# +
+
+
+ +Returns whether there is a checkpoint in the structure (i.e. it is not empty), and if so the key and value +in the most recent checkpoint. + +
+
+ + + +
+
+

length(struct Checkpoints.Trace160 self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of checkpoints. + +
+
+ + + +
+
+

at(struct Checkpoints.Trace160 self, uint32 pos) → struct Checkpoints.Checkpoint160

+
+

internal

+# +
+
+
+ +Returns checkpoint at given position. + +
+
+ + + +
+
+

CheckpointUnorderedInsertion()

+
+

error

+# +
+
+
+ +A value was attempted to be inserted on a past checkpoint. + +
+
+ + + +
+ +## `CircularBuffer` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/CircularBuffer.sol"; +``` + +A fixed-size buffer for keeping `bytes32` items in storage. + +This data structure allows for pushing elements to it, and when its length exceeds the specified fixed size, +new items take the place of the oldest element in the buffer, keeping at most `N` elements in the +structure. + +Elements can't be removed but the data structure can be cleared. See [`CircularBuffer.clear`](#CircularBuffer-clear-struct-CircularBuffer-Bytes32CircularBuffer-). + +Complexity: +- insertion ([`RLP.push`](#RLP-push-struct-RLP-Encoder-struct-RLP-Encoder-)): O(1) +- lookup ([`CircularBuffer.last`](#CircularBuffer-last-struct-CircularBuffer-Bytes32CircularBuffer-uint256-)): O(1) +- inclusion ([`CircularBuffer.includes`](#CircularBuffer-includes-struct-CircularBuffer-Bytes32CircularBuffer-bytes32-)): O(N) (worst case) +- reset ([`CircularBuffer.clear`](#CircularBuffer-clear-struct-CircularBuffer-Bytes32CircularBuffer-)): O(1) + +The struct is called `Bytes32CircularBuffer`. Other types can be cast to and from `bytes32`. This data structure +can only be used in storage, and not in memory. + +Example usage: + +```solidity +contract Example { + // Add the library methods + using CircularBuffer for CircularBuffer.Bytes32CircularBuffer; + + // Declare a buffer storage variable + CircularBuffer.Bytes32CircularBuffer private myBuffer; +} +``` + +_Available since v5.1._ + +
+

Functions

+
+- [setup(self, size)](#CircularBuffer-setup-struct-CircularBuffer-Bytes32CircularBuffer-uint256-) +- [clear(self)](#CircularBuffer-clear-struct-CircularBuffer-Bytes32CircularBuffer-) +- [push(self, value)](#CircularBuffer-push-struct-CircularBuffer-Bytes32CircularBuffer-bytes32-) +- [count(self)](#CircularBuffer-count-struct-CircularBuffer-Bytes32CircularBuffer-) +- [length(self)](#CircularBuffer-length-struct-CircularBuffer-Bytes32CircularBuffer-) +- [last(self, i)](#CircularBuffer-last-struct-CircularBuffer-Bytes32CircularBuffer-uint256-) +- [includes(self, value)](#CircularBuffer-includes-struct-CircularBuffer-Bytes32CircularBuffer-bytes32-) +
+
+ +
+

Errors

+
+- [InvalidBufferSize()](#CircularBuffer-InvalidBufferSize--) +
+
+ + + +
+
+

setup(struct CircularBuffer.Bytes32CircularBuffer self, uint256 size)

+
+

internal

+# +
+
+
+ +Initialize a new CircularBuffer of a given size. + +If the CircularBuffer was already setup and used, calling that function again will reset it to a blank state. + + +The size of the buffer will affect the execution of [`CircularBuffer.includes`](#CircularBuffer-includes-struct-CircularBuffer-Bytes32CircularBuffer-bytes32-) function, as it has a complexity of O(N). +Consider a large buffer size may render the function unusable. + + +
+
+ + + +
+
+

clear(struct CircularBuffer.Bytes32CircularBuffer self)

+
+

internal

+# +
+
+
+ +Clear all data in the buffer without resetting memory, keeping the existing size. + +
+
+ + + +
+
+

push(struct CircularBuffer.Bytes32CircularBuffer self, bytes32 value)

+
+

internal

+# +
+
+
+ +Push a new value to the buffer. If the buffer is already full, the new value replaces the oldest value in +the buffer. + +
+
+ + + +
+
+

count(struct CircularBuffer.Bytes32CircularBuffer self) → uint256

+
+

internal

+# +
+
+
+ +Number of values currently in the buffer. This value is 0 for an empty buffer, and cannot exceed the size of +the buffer. + +
+
+ + + +
+
+

length(struct CircularBuffer.Bytes32CircularBuffer self) → uint256

+
+

internal

+# +
+
+
+ +Length of the buffer. This is the maximum number of elements kept in the buffer. + +
+
+ + + +
+
+

last(struct CircularBuffer.Bytes32CircularBuffer self, uint256 i) → bytes32

+
+

internal

+# +
+
+
+ +Getter for the i-th value in the buffer, from the end. + +Reverts with [`Panic.ARRAY_OUT_OF_BOUNDS`](#Panic-ARRAY_OUT_OF_BOUNDS-uint256) if trying to access an element that was not pushed, or that was +dropped to make room for newer elements. + +
+
+ + + +
+
+

includes(struct CircularBuffer.Bytes32CircularBuffer self, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Check if a given value is in the buffer. + +
+
+ + + +
+
+

InvalidBufferSize()

+
+

error

+# +
+
+
+ +Error emitted when trying to setup a buffer with a size of 0. + +
+
+ + + +
+ +## `DoubleEndedQueue` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +``` + +A sequence of items with the ability to efficiently push and pop items (i.e. insert and remove) on both ends of +the sequence (called front and back). Among other access patterns, it can be used to implement efficient LIFO and +FIFO queues. Storage use is optimized, and all operations are O(1) constant time. This includes [`CircularBuffer.clear`](#CircularBuffer-clear-struct-CircularBuffer-Bytes32CircularBuffer-), given that +the existing queue contents are left in storage. + +The struct is called `Bytes32Deque`. Other types can be cast to and from `bytes32`. This data structure can only be +used in storage, and not in memory. +```solidity +DoubleEndedQueue.Bytes32Deque queue; +``` + +
+

Functions

+
+- [pushBack(deque, value)](#DoubleEndedQueue-pushBack-struct-DoubleEndedQueue-Bytes32Deque-bytes32-) +- [popBack(deque)](#DoubleEndedQueue-popBack-struct-DoubleEndedQueue-Bytes32Deque-) +- [pushFront(deque, value)](#DoubleEndedQueue-pushFront-struct-DoubleEndedQueue-Bytes32Deque-bytes32-) +- [popFront(deque)](#DoubleEndedQueue-popFront-struct-DoubleEndedQueue-Bytes32Deque-) +- [front(deque)](#DoubleEndedQueue-front-struct-DoubleEndedQueue-Bytes32Deque-) +- [back(deque)](#DoubleEndedQueue-back-struct-DoubleEndedQueue-Bytes32Deque-) +- [at(deque, index)](#DoubleEndedQueue-at-struct-DoubleEndedQueue-Bytes32Deque-uint256-) +- [clear(deque)](#DoubleEndedQueue-clear-struct-DoubleEndedQueue-Bytes32Deque-) +- [length(deque)](#DoubleEndedQueue-length-struct-DoubleEndedQueue-Bytes32Deque-) +- [empty(deque)](#DoubleEndedQueue-empty-struct-DoubleEndedQueue-Bytes32Deque-) +
+
+ + + +
+
+

pushBack(struct DoubleEndedQueue.Bytes32Deque deque, bytes32 value)

+
+

internal

+# +
+
+
+ +Inserts an item at the end of the queue. + +Reverts with [`Panic.RESOURCE_ERROR`](#Panic-RESOURCE_ERROR-uint256) if the queue is full. + +
+
+ + + +
+
+

popBack(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Removes the item at the end of the queue and returns it. + +Reverts with [`Panic.EMPTY_ARRAY_POP`](#Panic-EMPTY_ARRAY_POP-uint256) if the queue is empty. + +
+
+ + + +
+
+

pushFront(struct DoubleEndedQueue.Bytes32Deque deque, bytes32 value)

+
+

internal

+# +
+
+
+ +Inserts an item at the beginning of the queue. + +Reverts with [`Panic.RESOURCE_ERROR`](#Panic-RESOURCE_ERROR-uint256) if the queue is full. + +
+
+ + + +
+
+

popFront(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Removes the item at the beginning of the queue and returns it. + +Reverts with [`Panic.EMPTY_ARRAY_POP`](#Panic-EMPTY_ARRAY_POP-uint256) if the queue is empty. + +
+
+ + + +
+
+

front(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Returns the item at the beginning of the queue. + +Reverts with [`Panic.ARRAY_OUT_OF_BOUNDS`](#Panic-ARRAY_OUT_OF_BOUNDS-uint256) if the queue is empty. + +
+
+ + + +
+
+

back(struct DoubleEndedQueue.Bytes32Deque deque) → bytes32 value

+
+

internal

+# +
+
+
+ +Returns the item at the end of the queue. + +Reverts with [`Panic.ARRAY_OUT_OF_BOUNDS`](#Panic-ARRAY_OUT_OF_BOUNDS-uint256) if the queue is empty. + +
+
+ + + +
+
+

at(struct DoubleEndedQueue.Bytes32Deque deque, uint256 index) → bytes32 value

+
+

internal

+# +
+
+
+ +Return the item at a position in the queue given by `index`, with the first item at 0 and last item at +`length(deque) - 1`. + +Reverts with [`Panic.ARRAY_OUT_OF_BOUNDS`](#Panic-ARRAY_OUT_OF_BOUNDS-uint256) if the index is out of bounds. + +
+
+ + + +
+
+

clear(struct DoubleEndedQueue.Bytes32Deque deque)

+
+

internal

+# +
+
+
+ +Resets the queue back to being empty. + + +The current items are left behind in storage. This does not affect the functioning of the queue, but misses +out on potential gas refunds. + + +
+
+ + + +
+
+

length(struct DoubleEndedQueue.Bytes32Deque deque) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of items in the queue. + +
+
+ + + +
+
+

empty(struct DoubleEndedQueue.Bytes32Deque deque) → bool

+
+

internal

+# +
+
+
+ +Returns true if the queue is empty. + +
+
+ + + +
+ +## `EnumerableMap` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; +``` + +Library for managing an enumerable variant of Solidity's +[`mapping`](https://solidity.readthedocs.io/en/latest/types.html#mapping-types) +type. + +Maps have the following properties: + +- Entries are added, removed, and checked for existence in constant time +(O(1)). +- Entries are enumerated in O(n). No guarantees are made on the ordering. +- Map can be cleared (all entries removed) in O(n). + +```solidity +contract Example { + // Add the library methods + using EnumerableMap for EnumerableMap.UintToAddressMap; + + // Declare a set state variable + EnumerableMap.UintToAddressMap private myMap; +} +``` + +The following map types are supported: + +- `uint256 -> address` (`UintToAddressMap`) since v3.0.0 +- `address -> uint256` (`AddressToUintMap`) since v4.6.0 +- `bytes32 -> bytes32` (`Bytes32ToBytes32Map`) since v4.6.0 +- `uint256 -> uint256` (`UintToUintMap`) since v4.7.0 +- `bytes32 -> uint256` (`Bytes32ToUintMap`) since v4.7.0 +- `uint256 -> bytes32` (`UintToBytes32Map`) since v5.1.0 +- `address -> address` (`AddressToAddressMap`) since v5.1.0 +- `address -> bytes32` (`AddressToBytes32Map`) since v5.1.0 +- `bytes32 -> address` (`Bytes32ToAddressMap`) since v5.1.0 +- `bytes -> bytes` (`BytesToBytesMap`) since v5.4.0 + + +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See [ethereum/solidity#11843](https://github.com/ethereum/solidity/pull/11843) for more info. + +In order to clean an EnumerableMap, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableMap. + + +
+

Functions

+
+- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-bytes32-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-Bytes32ToBytes32Map-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-Bytes32ToBytes32Map-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToBytes32Map-bytes32-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToBytes32Map-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-UintToUintMap-uint256-uint256-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-UintToUintMap-uint256-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-UintToUintMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-UintToUintMap-uint256-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-UintToUintMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-UintToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-UintToUintMap-uint256-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-UintToUintMap-uint256-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-UintToUintMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-UintToAddressMap-uint256-address-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-UintToAddressMap-uint256-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-UintToAddressMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-UintToAddressMap-uint256-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-UintToAddressMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-UintToAddressMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-UintToAddressMap-uint256-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-UintToAddressMap-uint256-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-UintToAddressMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-UintToBytes32Map-uint256-bytes32-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-UintToBytes32Map-uint256-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-UintToBytes32Map-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-UintToBytes32Map-uint256-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-UintToBytes32Map-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-UintToBytes32Map-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-UintToBytes32Map-uint256-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-UintToBytes32Map-uint256-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-UintToBytes32Map-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-UintToBytes32Map-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-AddressToUintMap-address-uint256-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-AddressToUintMap-address-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-AddressToUintMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-AddressToUintMap-address-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-AddressToUintMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-AddressToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-AddressToUintMap-address-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-AddressToUintMap-address-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-AddressToUintMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-AddressToAddressMap-address-address-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-AddressToAddressMap-address-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-AddressToAddressMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-AddressToAddressMap-address-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-AddressToAddressMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-AddressToAddressMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-AddressToAddressMap-address-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-AddressToAddressMap-address-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-AddressToAddressMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-AddressToAddressMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-AddressToBytes32Map-address-bytes32-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-AddressToBytes32Map-address-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-AddressToBytes32Map-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-AddressToBytes32Map-address-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-AddressToBytes32Map-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-AddressToBytes32Map-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-AddressToBytes32Map-address-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-AddressToBytes32Map-address-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-AddressToBytes32Map-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-AddressToBytes32Map-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-Bytes32ToUintMap-bytes32-uint256-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-Bytes32ToUintMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-Bytes32ToUintMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-Bytes32ToUintMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToUintMap-bytes32-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToUintMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-Bytes32ToAddressMap-bytes32-address-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-Bytes32ToAddressMap-bytes32-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-Bytes32ToAddressMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-Bytes32ToAddressMap-bytes32-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-Bytes32ToAddressMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-Bytes32ToAddressMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-Bytes32ToAddressMap-bytes32-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-Bytes32ToAddressMap-bytes32-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToAddressMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-Bytes32ToAddressMap-uint256-uint256-) +- [set(map, key, value)](#EnumerableMap-set-struct-EnumerableMap-BytesToBytesMap-bytes-bytes-) +- [remove(map, key)](#EnumerableMap-remove-struct-EnumerableMap-BytesToBytesMap-bytes-) +- [clear(map)](#EnumerableMap-clear-struct-EnumerableMap-BytesToBytesMap-) +- [contains(map, key)](#EnumerableMap-contains-struct-EnumerableMap-BytesToBytesMap-bytes-) +- [length(map)](#EnumerableMap-length-struct-EnumerableMap-BytesToBytesMap-) +- [at(map, index)](#EnumerableMap-at-struct-EnumerableMap-BytesToBytesMap-uint256-) +- [tryGet(map, key)](#EnumerableMap-tryGet-struct-EnumerableMap-BytesToBytesMap-bytes-) +- [get(map, key)](#EnumerableMap-get-struct-EnumerableMap-BytesToBytesMap-bytes-) +- [keys(map)](#EnumerableMap-keys-struct-EnumerableMap-BytesToBytesMap-) +- [keys(map, start, end)](#EnumerableMap-keys-struct-EnumerableMap-BytesToBytesMap-uint256-uint256-) +
+
+ +
+

Errors

+
+- [EnumerableMapNonexistentKey(key)](#EnumerableMap-EnumerableMapNonexistentKey-bytes32-) +- [EnumerableMapNonexistentBytesKey(key)](#EnumerableMap-EnumerableMapNonexistentBytesKey-bytes-) +
+
+ + + +
+
+

set(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Removes a key-value pair from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.Bytes32ToBytes32Map map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.Bytes32ToBytes32Map map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of key-value pairs in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.Bytes32ToBytes32Map map, uint256 index) → bytes32 key, bytes32 value

+
+

internal

+# +
+
+
+ +Returns the key-value pair stored at position `index` in the map. O(1). + +Note that there are no guarantees on the ordering of entries inside the +array, and it may change when more entries are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bool exists, bytes32 value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToBytes32Map map, bytes32 key) → bytes32

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToBytes32Map map) → bytes32[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToBytes32Map map, uint256 start, uint256 end) → bytes32[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.UintToUintMap map, uint256 key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.UintToUintMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.UintToUintMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.UintToUintMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.UintToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.UintToUintMap map, uint256 index) → uint256 key, uint256 value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.UintToUintMap map, uint256 key) → bool exists, uint256 value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToUintMap map, uint256 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToUintMap map) → uint256[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToUintMap map, uint256 start, uint256 end) → uint256[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.UintToAddressMap map, uint256 key, address value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.UintToAddressMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.UintToAddressMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.UintToAddressMap map, uint256 index) → uint256 key, address value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.UintToAddressMap map, uint256 key) → bool exists, address value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToAddressMap map, uint256 key) → address

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToAddressMap map) → uint256[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToAddressMap map, uint256 start, uint256 end) → uint256[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.UintToBytes32Map map, uint256 key, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.UintToBytes32Map map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.UintToBytes32Map map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.UintToBytes32Map map, uint256 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.UintToBytes32Map map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.UintToBytes32Map map, uint256 index) → uint256 key, bytes32 value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.UintToBytes32Map map, uint256 key) → bool exists, bytes32 value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.UintToBytes32Map map, uint256 key) → bytes32

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToBytes32Map map) → uint256[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.UintToBytes32Map map, uint256 start, uint256 end) → uint256[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.AddressToUintMap map, address key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.AddressToUintMap map, address key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.AddressToUintMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.AddressToUintMap map, address key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.AddressToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.AddressToUintMap map, uint256 index) → address key, uint256 value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.AddressToUintMap map, address key) → bool exists, uint256 value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.AddressToUintMap map, address key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToUintMap map) → address[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToUintMap map, uint256 start, uint256 end) → address[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.AddressToAddressMap map, address key, address value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.AddressToAddressMap map, address key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.AddressToAddressMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.AddressToAddressMap map, address key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.AddressToAddressMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.AddressToAddressMap map, uint256 index) → address key, address value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.AddressToAddressMap map, address key) → bool exists, address value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.AddressToAddressMap map, address key) → address

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToAddressMap map) → address[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToAddressMap map, uint256 start, uint256 end) → address[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.AddressToBytes32Map map, address key, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.AddressToBytes32Map map, address key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.AddressToBytes32Map map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.AddressToBytes32Map map, address key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.AddressToBytes32Map map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.AddressToBytes32Map map, uint256 index) → address key, bytes32 value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.AddressToBytes32Map map, address key) → bool exists, bytes32 value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.AddressToBytes32Map map, address key) → bytes32

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToBytes32Map map) → address[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.AddressToBytes32Map map, uint256 start, uint256 end) → address[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.Bytes32ToUintMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.Bytes32ToUintMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.Bytes32ToUintMap map, uint256 index) → bytes32 key, uint256 value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → bool exists, uint256 value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToUintMap map, bytes32 key) → uint256

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToUintMap map) → bytes32[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToUintMap map, uint256 start, uint256 end) → bytes32[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.Bytes32ToAddressMap map, bytes32 key, address value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.Bytes32ToAddressMap map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.Bytes32ToAddressMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +This function has an unbounded cost that scales with map size. Developers should keep in mind that +using it may render the function uncallable if the map grows to the point where clearing it consumes too much +gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.Bytes32ToAddressMap map, bytes32 key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.Bytes32ToAddressMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.Bytes32ToAddressMap map, uint256 index) → bytes32 key, address value

+
+

internal

+# +
+
+
+ +Returns the element stored at position `index` in the map. O(1). +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.Bytes32ToAddressMap map, bytes32 key) → bool exists, address value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.Bytes32ToAddressMap map, bytes32 key) → address

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToAddressMap map) → bytes32[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.Bytes32ToAddressMap map, uint256 start, uint256 end) → bytes32[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

set(struct EnumerableMap.BytesToBytesMap map, bytes key, bytes value) → bool

+
+

internal

+# +
+
+
+ +Adds a key-value pair to a map, or updates the value for an existing +key. O(1). + +Returns true if the key was added to the map, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableMap.BytesToBytesMap map, bytes key) → bool

+
+

internal

+# +
+
+
+ +Removes a key-value pair from a map. O(1). + +Returns true if the key was removed from the map, that is if it was present. + +
+
+ + + +
+
+

clear(struct EnumerableMap.BytesToBytesMap map)

+
+

internal

+# +
+
+
+ +Removes all the entries from a map. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the map grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableMap.BytesToBytesMap map, bytes key) → bool

+
+

internal

+# +
+
+
+ +Returns true if the key is in the map. O(1). + +
+
+ + + +
+
+

length(struct EnumerableMap.BytesToBytesMap map) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of key-value pairs in the map. O(1). + +
+
+ + + +
+
+

at(struct EnumerableMap.BytesToBytesMap map, uint256 index) → bytes key, bytes value

+
+

internal

+# +
+
+
+ +Returns the key-value pair stored at position `index` in the map. O(1). + +Note that there are no guarantees on the ordering of entries inside the +array, and it may change when more entries are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

tryGet(struct EnumerableMap.BytesToBytesMap map, bytes key) → bool exists, bytes value

+
+

internal

+# +
+
+
+ +Tries to return the value associated with `key`. O(1). +Does not revert if `key` is not in the map. + +
+
+ + + +
+
+

get(struct EnumerableMap.BytesToBytesMap map, bytes key) → bytes value

+
+

internal

+# +
+
+
+ +Returns the value associated with `key`. O(1). + +Requirements: + +- `key` must be in the map. + +
+
+ + + +
+
+

keys(struct EnumerableMap.BytesToBytesMap map) → bytes[]

+
+

internal

+# +
+
+
+ +Returns an array containing all the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

keys(struct EnumerableMap.BytesToBytesMap map, uint256 start, uint256 end) → bytes[]

+
+

internal

+# +
+
+
+ +Returns an array containing a slice of the keys + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the map grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

EnumerableMapNonexistentKey(bytes32 key)

+
+

error

+# +
+
+
+ +Query for a nonexistent map key. + +
+
+ + + +
+
+

EnumerableMapNonexistentBytesKey(bytes key)

+
+

error

+# +
+
+
+ +Query for a nonexistent map key. + +
+
+ + + +
+ +## `EnumerableSet` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +``` + +Library for managing +[sets](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) of primitive +types. + +Sets have the following properties: + +- Elements are added, removed, and checked for existence in constant time +(O(1)). +- Elements are enumerated in O(n). No guarantees are made on the ordering. +- Set can be cleared (all elements removed) in O(n). + +```solidity +contract Example { + // Add the library methods + using EnumerableSet for EnumerableSet.AddressSet; + + // Declare a set state variable + EnumerableSet.AddressSet private mySet; +} +``` + +The following types are supported: + +- `bytes32` (`Bytes32Set`) since v3.3.0 +- `address` (`AddressSet`) since v3.3.0 +- `uint256` (`UintSet`) since v3.3.0 +- `string` (`StringSet`) since v5.4.0 +- `bytes` (`BytesSet`) since v5.4.0 + + +Trying to delete such a structure from storage will likely result in data corruption, rendering the structure +unusable. +See [ethereum/solidity#11843](https://github.com/ethereum/solidity/pull/11843) for more info. + +In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an +array of EnumerableSet. + + +
+

Functions

+
+- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-Bytes32Set-bytes32-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-Bytes32Set-bytes32-) +- [clear(set)](#EnumerableSet-clear-struct-EnumerableSet-Bytes32Set-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-Bytes32Set-bytes32-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-Bytes32Set-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-Bytes32Set-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-Bytes32Set-) +- [values(set, start, end)](#EnumerableSet-values-struct-EnumerableSet-Bytes32Set-uint256-uint256-) +- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-AddressSet-address-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-AddressSet-address-) +- [clear(set)](#EnumerableSet-clear-struct-EnumerableSet-AddressSet-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-AddressSet-address-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-AddressSet-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-AddressSet-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-AddressSet-) +- [values(set, start, end)](#EnumerableSet-values-struct-EnumerableSet-AddressSet-uint256-uint256-) +- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-UintSet-uint256-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-UintSet-uint256-) +- [clear(set)](#EnumerableSet-clear-struct-EnumerableSet-UintSet-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-UintSet-uint256-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-UintSet-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-UintSet-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-UintSet-) +- [values(set, start, end)](#EnumerableSet-values-struct-EnumerableSet-UintSet-uint256-uint256-) +- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-StringSet-string-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-StringSet-string-) +- [clear(set)](#EnumerableSet-clear-struct-EnumerableSet-StringSet-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-StringSet-string-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-StringSet-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-StringSet-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-StringSet-) +- [values(set, start, end)](#EnumerableSet-values-struct-EnumerableSet-StringSet-uint256-uint256-) +- [add(set, value)](#EnumerableSet-add-struct-EnumerableSet-BytesSet-bytes-) +- [remove(set, value)](#EnumerableSet-remove-struct-EnumerableSet-BytesSet-bytes-) +- [clear(set)](#EnumerableSet-clear-struct-EnumerableSet-BytesSet-) +- [contains(set, value)](#EnumerableSet-contains-struct-EnumerableSet-BytesSet-bytes-) +- [length(set)](#EnumerableSet-length-struct-EnumerableSet-BytesSet-) +- [at(set, index)](#EnumerableSet-at-struct-EnumerableSet-BytesSet-uint256-) +- [values(set)](#EnumerableSet-values-struct-EnumerableSet-BytesSet-) +- [values(set, start, end)](#EnumerableSet-values-struct-EnumerableSet-BytesSet-uint256-uint256-) +
+
+ + + +
+
+

add(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

clear(struct EnumerableSet.Bytes32Set set)

+
+

internal

+# +
+
+
+ +Removes all the values from a set. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableSet.Bytes32Set set, bytes32 value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.Bytes32Set set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values in the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.Bytes32Set set, uint256 index) → bytes32

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

values(struct EnumerableSet.Bytes32Set set) → bytes32[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

values(struct EnumerableSet.Bytes32Set set, uint256 start, uint256 end) → bytes32[]

+
+

internal

+# +
+
+
+ +Return a slice of the set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

add(struct EnumerableSet.AddressSet set, address value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.AddressSet set, address value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

clear(struct EnumerableSet.AddressSet set)

+
+

internal

+# +
+
+
+ +Removes all the values from a set. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableSet.AddressSet set, address value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.AddressSet set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values in the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.AddressSet set, uint256 index) → address

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

values(struct EnumerableSet.AddressSet set) → address[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

values(struct EnumerableSet.AddressSet set, uint256 start, uint256 end) → address[]

+
+

internal

+# +
+
+
+ +Return a slice of the set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

add(struct EnumerableSet.UintSet set, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.UintSet set, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

clear(struct EnumerableSet.UintSet set)

+
+

internal

+# +
+
+
+ +Removes all the values from a set. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableSet.UintSet set, uint256 value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.UintSet set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values in the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.UintSet set, uint256 index) → uint256

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

values(struct EnumerableSet.UintSet set) → uint256[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

values(struct EnumerableSet.UintSet set, uint256 start, uint256 end) → uint256[]

+
+

internal

+# +
+
+
+ +Return a slice of the set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

add(struct EnumerableSet.StringSet set, string value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.StringSet set, string value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

clear(struct EnumerableSet.StringSet set)

+
+

internal

+# +
+
+
+ +Removes all the values from a set. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableSet.StringSet set, string value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.StringSet set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values on the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.StringSet set, uint256 index) → string

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

values(struct EnumerableSet.StringSet set) → string[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

values(struct EnumerableSet.StringSet set, uint256 start, uint256 end) → string[]

+
+

internal

+# +
+
+
+ +Return a slice of the set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

add(struct EnumerableSet.BytesSet set, bytes value) → bool

+
+

internal

+# +
+
+
+ +Add a value to a set. O(1). + +Returns true if the value was added to the set, that is if it was not +already present. + +
+
+ + + +
+
+

remove(struct EnumerableSet.BytesSet set, bytes value) → bool

+
+

internal

+# +
+
+
+ +Removes a value from a set. O(1). + +Returns true if the value was removed from the set, that is if it was +present. + +
+
+ + + +
+
+

clear(struct EnumerableSet.BytesSet set)

+
+

internal

+# +
+
+
+ +Removes all the values from a set. O(n). + + +Developers should keep in mind that this function has an unbounded cost and using it may render the +function uncallable if the set grows to the point where clearing it consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

contains(struct EnumerableSet.BytesSet set, bytes value) → bool

+
+

internal

+# +
+
+
+ +Returns true if the value is in the set. O(1). + +
+
+ + + +
+
+

length(struct EnumerableSet.BytesSet set) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of values on the set. O(1). + +
+
+ + + +
+
+

at(struct EnumerableSet.BytesSet set, uint256 index) → bytes

+
+

internal

+# +
+
+
+ +Returns the value stored at position `index` in the set. O(1). + +Note that there are no guarantees on the ordering of values inside the +array, and it may change when more values are added or removed. + +Requirements: + +- `index` must be strictly less than [`Memory.length`](#Memory-length-Memory-Slice-). + +
+
+ + + +
+
+

values(struct EnumerableSet.BytesSet set) → bytes[]

+
+

internal

+# +
+
+
+ +Return the entire set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+
+

values(struct EnumerableSet.BytesSet set, uint256 start, uint256 end) → bytes[]

+
+

internal

+# +
+
+
+ +Return a slice of the set in an array + + +This operation will copy the entire storage to memory, which can be quite expensive. This is designed +to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that +this function has an unbounded cost, and using it as part of a state-changing function may render the function +uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. + + +
+
+ + + +
+ +## `Heap` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/Heap.sol"; +``` + +Library for managing [binary heap](https://en.wikipedia.org/wiki/Binary_heap) that can be used as +[priority queue](https://en.wikipedia.org/wiki/Priority_queue). + +Heaps are represented as a tree of values where the first element (index 0) is the root, and where the node at +index i is the child of the node at index (i-1)/2 and the parent of nodes at index 2*i+1 and 2*i+2. Each node +stores an element of the heap. + +The structure is ordered so that each node is bigger than its parent. An immediate consequence is that the +highest priority value is the one at the root. This value can be looked up in constant time (O(1)) at +`heap.tree[0]` + +The structure is designed to perform the following operations with the corresponding complexities: + +* peek (get the highest priority value): O(1) +* insert (insert a value): O(log(n)) +* pop (remove the highest priority value): O(log(n)) +* replace (replace the highest priority value with a new value): O(log(n)) +* length (get the number of elements): O(1) +* clear (remove all elements): O(1) + + +This library allows for the use of custom comparator functions. Given that manipulating +memory can lead to unexpected behavior. Consider verifying that the comparator does not manipulate +the Heap's state directly and that it follows the Solidity memory safety rules. + + +_Available since v5.1._ + +
+

Functions

+
+- [peek(self)](#Heap-peek-struct-Heap-Uint256Heap-) +- [pop(self)](#Heap-pop-struct-Heap-Uint256Heap-) +- [pop(self, comp)](#Heap-pop-struct-Heap-Uint256Heap-function--uint256-uint256--view-returns--bool--) +- [insert(self, value)](#Heap-insert-struct-Heap-Uint256Heap-uint256-) +- [insert(self, value, comp)](#Heap-insert-struct-Heap-Uint256Heap-uint256-function--uint256-uint256--view-returns--bool--) +- [replace(self, newValue)](#Heap-replace-struct-Heap-Uint256Heap-uint256-) +- [replace(self, newValue, comp)](#Heap-replace-struct-Heap-Uint256Heap-uint256-function--uint256-uint256--view-returns--bool--) +- [length(self)](#Heap-length-struct-Heap-Uint256Heap-) +- [clear(self)](#Heap-clear-struct-Heap-Uint256Heap-) +
+
+ + + +
+
+

peek(struct Heap.Uint256Heap self) → uint256

+
+

internal

+# +
+
+
+ +Lookup the root element of the heap. + +
+
+ + + +
+
+

pop(struct Heap.Uint256Heap self) → uint256

+
+

internal

+# +
+
+
+ +Remove (and return) the root element for the heap using the default comparator. + + +All inserting and removal from a heap should always be done using the same comparator. Mixing comparator +during the lifecycle of a heap will result in undefined behavior. + + +
+
+ + + +
+
+

pop(struct Heap.Uint256Heap self, function (uint256,uint256) view returns (bool) comp) → uint256

+
+

internal

+# +
+
+
+ +Remove (and return) the root element for the heap using the provided comparator. + + +All inserting and removal from a heap should always be done using the same comparator. Mixing comparator +during the lifecycle of a heap will result in undefined behavior. + + +
+
+ + + +
+
+

insert(struct Heap.Uint256Heap self, uint256 value)

+
+

internal

+# +
+
+
+ +Insert a new element in the heap using the default comparator. + + +All inserting and removal from a heap should always be done using the same comparator. Mixing comparator +during the lifecycle of a heap will result in undefined behavior. + + +
+
+ + + +
+
+

insert(struct Heap.Uint256Heap self, uint256 value, function (uint256,uint256) view returns (bool) comp)

+
+

internal

+# +
+
+
+ +Insert a new element in the heap using the provided comparator. + + +All inserting and removal from a heap should always be done using the same comparator. Mixing comparator +during the lifecycle of a heap will result in undefined behavior. + + +
+
+ + + +
+
+

replace(struct Heap.Uint256Heap self, uint256 newValue) → uint256

+
+

internal

+# +
+
+
+ +Return the root element for the heap, and replace it with a new value, using the default comparator. +This is equivalent to using [`Heap.pop`](#Heap-pop-struct-Heap-Uint256Heap-function--uint256-uint256--view-returns--bool--) and [`Heap.insert`](#Heap-insert-struct-Heap-Uint256Heap-uint256-function--uint256-uint256--view-returns--bool--), but requires only one rebalancing operation. + + +All inserting and removal from a heap should always be done using the same comparator. Mixing comparator +during the lifecycle of a heap will result in undefined behavior. + + +
+
+ + + +
+
+

replace(struct Heap.Uint256Heap self, uint256 newValue, function (uint256,uint256) view returns (bool) comp) → uint256

+
+

internal

+# +
+
+
+ +Return the root element for the heap, and replace it with a new value, using the provided comparator. +This is equivalent to using [`Heap.pop`](#Heap-pop-struct-Heap-Uint256Heap-function--uint256-uint256--view-returns--bool--) and [`Heap.insert`](#Heap-insert-struct-Heap-Uint256Heap-uint256-function--uint256-uint256--view-returns--bool--), but requires only one rebalancing operation. + + +All inserting and removal from a heap should always be done using the same comparator. Mixing comparator +during the lifecycle of a heap will result in undefined behavior. + + +
+
+ + + +
+
+

length(struct Heap.Uint256Heap self) → uint256

+
+

internal

+# +
+
+
+ +Returns the number of elements in the heap. + +
+
+ + + +
+
+

clear(struct Heap.Uint256Heap self)

+
+

internal

+# +
+
+
+ +Removes all elements in the heap. + +
+
+ + + +
+ +## `MerkleTree` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/structs/MerkleTree.sol"; +``` + +Library for managing [Merkle Tree](https://wikipedia.org/wiki/Merkle_Tree) data structures. + +Each tree is a complete binary tree with the ability to sequentially insert leaves, changing them from a zero to a +non-zero value and updating its root. This structure allows inserting commitments (or other entries) that are not +stored, but can be proven to be part of the tree at a later time if the root is kept. See [`MerkleProof`](/contracts/5.x/api/utils/cryptography#MerkleProof). + +A tree is defined by the following parameters: + +* Depth: The number of levels in the tree, it also defines the maximum number of leaves as 2**depth. +* Zero value: The value that represents an empty leaf. Used to avoid regular zero values to be part of the tree. +* Hashing function: A cryptographic hash function used to produce internal nodes. Defaults to [`Hashes.commutativeKeccak256`](/contracts/5.x/api/utils/cryptography#Hashes-commutativeKeccak256-bytes32-bytes32-). + + +Building trees using non-commutative hashing functions (i.e. `H(a, b) != H(b, a)`) is supported. However, +proving the inclusion of a leaf in such trees is not possible with the [`MerkleProof`](/contracts/5.x/api/utils/cryptography#MerkleProof) library since it only supports +_commutative_ hashing functions. + + +_Available since v5.1._ + +
+

Functions

+
+- [setup(self, treeDepth, zero)](#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-) +- [setup(self, treeDepth, zero, fnHash)](#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-function--bytes32-bytes32--view-returns--bytes32--) +- [push(self, leaf)](#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-) +- [push(self, leaf, fnHash)](#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-function--bytes32-bytes32--view-returns--bytes32--) +- [update(self, index, oldValue, newValue, proof)](#MerkleTree-update-struct-MerkleTree-Bytes32PushTree-uint256-bytes32-bytes32-bytes32---) +- [update(self, index, oldValue, newValue, proof, fnHash)](#MerkleTree-update-struct-MerkleTree-Bytes32PushTree-uint256-bytes32-bytes32-bytes32---function--bytes32-bytes32--view-returns--bytes32--) +- [depth(self)](#MerkleTree-depth-struct-MerkleTree-Bytes32PushTree-) +
+
+ +
+

Errors

+
+- [MerkleTreeUpdateInvalidIndex(index, length)](#MerkleTree-MerkleTreeUpdateInvalidIndex-uint256-uint256-) +- [MerkleTreeUpdateInvalidProof()](#MerkleTree-MerkleTreeUpdateInvalidProof--) +
+
+ + + +
+
+

setup(struct MerkleTree.Bytes32PushTree self, uint8 treeDepth, bytes32 zero) → bytes32 initialRoot

+
+

internal

+# +
+
+
+ +Initialize a [`MerkleTree.Bytes32PushTree`](#MerkleTree-Bytes32PushTree) using [`Hashes.commutativeKeccak256`](/contracts/5.x/api/utils/cryptography#Hashes-commutativeKeccak256-bytes32-bytes32-) to hash internal nodes. +The capacity of the tree (i.e. number of leaves) is set to `2**treeDepth`. + +Calling this function on MerkleTree that was already setup and used will reset it to a blank state. + +Once a tree is setup, any push to it must use the same hashing function. This means that values +should be pushed to it using the default [push](#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-) function. + + +The zero value should be carefully chosen since it will be stored in the tree representing +empty leaves. It should be a value that is not expected to be part of the tree. + + +
+
+ + + +
+
+

setup(struct MerkleTree.Bytes32PushTree self, uint8 treeDepth, bytes32 zero, function (bytes32,bytes32) view returns (bytes32) fnHash) → bytes32 initialRoot

+
+

internal

+# +
+
+
+ +Same as [setup](#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-), but allows to specify a custom hashing function. + +Once a tree is setup, any push to it must use the same hashing function. This means that values +should be pushed to it using the custom push function, which should be the same one as used during the setup. + + +Providing a custom hashing function is a security-sensitive operation since it may +compromise the soundness of the tree. + + + +Consider verifying that the hashing function does not manipulate the memory state directly and that it +follows the Solidity memory safety rules. Otherwise, it may lead to unexpected behavior. + + +
+
+ + + +
+
+

push(struct MerkleTree.Bytes32PushTree self, bytes32 leaf) → uint256 index, bytes32 newRoot

+
+

internal

+# +
+
+
+ +Insert a new leaf in the tree, and compute the new root. Returns the position of the inserted leaf in the +tree, and the resulting root. + +Hashing the leaf before calling this function is recommended as a protection against +second pre-image attacks. + +This variant uses [`Hashes.commutativeKeccak256`](/contracts/5.x/api/utils/cryptography#Hashes-commutativeKeccak256-bytes32-bytes32-) to hash internal nodes. It should only be used on merkle trees +that were setup using the same (default) hashing function (i.e. by calling +[the default setup](#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-) function). + +
+
+ + + +
+
+

push(struct MerkleTree.Bytes32PushTree self, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) fnHash) → uint256 index, bytes32 newRoot

+
+

internal

+# +
+
+
+ +Insert a new leaf in the tree, and compute the new root. Returns the position of the inserted leaf in the +tree, and the resulting root. + +Hashing the leaf before calling this function is recommended as a protection against +second pre-image attacks. + +This variant uses a custom hashing function to hash internal nodes. It should only be called with the same +function as the one used during the initial setup of the merkle tree. + +
+
+ + + +
+
+

update(struct MerkleTree.Bytes32PushTree self, uint256 index, bytes32 oldValue, bytes32 newValue, bytes32[] proof) → bytes32 oldRoot, bytes32 newRoot

+
+

internal

+# +
+
+
+ +Change the value of the leaf at position `index` from `oldValue` to `newValue`. Returns the recomputed "old" +root (before the update) and "new" root (after the update). The caller must verify that the reconstructed old +root is the last known one. + +The `proof` must be an up-to-date inclusion proof for the leaf being updated. This means that this function is +vulnerable to front-running. Any [`RLP.push`](#RLP-push-struct-RLP-Encoder-struct-RLP-Encoder-) or [`MerkleTree.update`](#MerkleTree-update-struct-MerkleTree-Bytes32PushTree-uint256-bytes32-bytes32-bytes32---function--bytes32-bytes32--view-returns--bytes32--) operation (that changes the root of the tree) would render +all "in flight" updates invalid. + +This variant uses [`Hashes.commutativeKeccak256`](/contracts/5.x/api/utils/cryptography#Hashes-commutativeKeccak256-bytes32-bytes32-) to hash internal nodes. It should only be used on merkle trees +that were setup using the same (default) hashing function (i.e. by calling +[the default setup](#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-) function). + +
+
+ + + +
+
+

update(struct MerkleTree.Bytes32PushTree self, uint256 index, bytes32 oldValue, bytes32 newValue, bytes32[] proof, function (bytes32,bytes32) view returns (bytes32) fnHash) → bytes32 oldRoot, bytes32 newRoot

+
+

internal

+# +
+
+
+ +Change the value of the leaf at position `index` from `oldValue` to `newValue`. Returns the recomputed "old" +root (before the update) and "new" root (after the update). The caller must verify that the reconstructed old +root is the last known one. + +The `proof` must be an up-to-date inclusion proof for the leaf being update. This means that this function is +vulnerable to front-running. Any [`RLP.push`](#RLP-push-struct-RLP-Encoder-struct-RLP-Encoder-) or [`MerkleTree.update`](#MerkleTree-update-struct-MerkleTree-Bytes32PushTree-uint256-bytes32-bytes32-bytes32---function--bytes32-bytes32--view-returns--bytes32--) operation (that changes the root of the tree) would render +all "in flight" updates invalid. + +This variant uses a custom hashing function to hash internal nodes. It should only be called with the same +function as the one used during the initial setup of the merkle tree. + +
+
+ + + +
+
+

depth(struct MerkleTree.Bytes32PushTree self) → uint256

+
+

internal

+# +
+
+
+ +Tree's depth (set at initialization) + +
+
+ + + +
+
+

MerkleTreeUpdateInvalidIndex(uint256 index, uint256 length)

+
+

error

+# +
+
+
+ +Error emitted when trying to update a leaf that was not previously pushed. + +
+
+ + + +
+
+

MerkleTreeUpdateInvalidProof()

+
+

error

+# +
+
+
+ +Error emitted when the proof used during an update is invalid (could not reproduce the side). + +
+
+ + + +
+ +## `Time` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/types/Time.sol"; +``` + +This library provides helpers for manipulating time-related objects. + +It uses the following types: +- `uint48` for timepoints +- `uint32` for durations + +While the library doesn't provide specific types for timepoints and duration, it does provide: +- a `Delay` type to represent duration that can be programmed to change value automatically at a given point +- additional helper functions + +
+

Functions

+
+- [timestamp()](#Time-timestamp--) +- [blockNumber()](#Time-blockNumber--) +- [toDelay(duration)](#Time-toDelay-uint32-) +- [getFull(self)](#Time-getFull-Time-Delay-) +- [get(self)](#Time-get-Time-Delay-) +- [withUpdate(self, newValue, minSetback)](#Time-withUpdate-Time-Delay-uint32-uint32-) +- [unpack(self)](#Time-unpack-Time-Delay-) +- [pack(valueBefore, valueAfter, effect)](#Time-pack-uint32-uint32-uint48-) +
+
+ + + +
+
+

timestamp() → uint48

+
+

internal

+# +
+
+
+ +Get the block timestamp as a Timepoint. + +
+
+ + + +
+
+

blockNumber() → uint48

+
+

internal

+# +
+
+
+ +Get the block number as a Timepoint. + +
+
+ + + +
+
+

toDelay(uint32 duration) → Time.Delay

+
+

internal

+# +
+
+
+ +Wrap a duration into a Delay to add the one-step "update in the future" feature + +
+
+ + + +
+
+

getFull(Time.Delay self) → uint32 valueBefore, uint32 valueAfter, uint48 effect

+
+

internal

+# +
+
+
+ +Get the current value plus the pending value and effect timepoint if there is a scheduled change. If the +effect timepoint is 0, then the pending value should not be considered. + +
+
+ + + +
+
+

get(Time.Delay self) → uint32

+
+

internal

+# +
+
+
+ +Get the current value. + +
+
+ + + +
+
+

withUpdate(Time.Delay self, uint32 newValue, uint32 minSetback) → Time.Delay updatedDelay, uint48 effect

+
+

internal

+# +
+
+
+ +Update a Delay object so that it takes a new duration after a timepoint that is automatically computed to +enforce the old delay at the moment of the update. Returns the updated Delay object and the timestamp when the +new delay becomes effective. + +
+
+ + + +
+
+

unpack(Time.Delay self) → uint32 valueBefore, uint32 valueAfter, uint48 effect

+
+

internal

+# +
+
+
+ +Split a delay into its components: valueBefore, valueAfter and effect (transition timepoint). + +
+
+ + + +
+
+

pack(uint32 valueBefore, uint32 valueAfter, uint48 effect) → Time.Delay

+
+

internal

+# +
+
+
+ +pack the components into a Delay object. + +
+
diff --git a/docs/content/contracts/5.x/api/utils/cryptography.mdx b/docs/content/contracts/5.x/api/utils/cryptography.mdx new file mode 100644 index 00000000..4c19f648 --- /dev/null +++ b/docs/content/contracts/5.x/api/utils/cryptography.mdx @@ -0,0 +1,3755 @@ +--- +title: "Cryptography" +description: "Smart contract cryptography utilities and implementations" +--- + +A collection of contracts and libraries that implement various signature validation schemes and cryptographic primitives. These utilities enable secure authentication, multisignature operations, and advanced cryptographic operations in smart contracts. + +* [`ECDSA`](#ECDSA), [`MessageHashUtils`](#MessageHashUtils): Libraries for interacting with ECDSA signatures. +* [`P256`](#P256): Library for verifying and recovering public keys from secp256r1 signatures. +* [`RSA`](#RSA): Library with RSA PKCS#1 v1.5 signature verification utilities. +* [`SignatureChecker`](#SignatureChecker): A library helper to support regular ECDSA from EOAs as well as ERC-1271 signatures for smart contracts. +* [`Hashes`](#Hashes): Commonly used hash functions. +* [`MerkleProof`](#MerkleProof): Functions for verifying [Merkle Tree](https://en.wikipedia.org/wiki/Merkle_tree) proofs. +* [`EIP712`](#EIP712): Contract with functions to allow processing signed typed structure data according to [EIP-712](https://eips.ethereum.org/EIPS/eip-712). +* [`ERC7739Utils`](#ERC7739Utils): Utilities library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on ERC-7739. +* [`WebAuthn`](#WebAuthn): Library for verifying WebAuthn Authentication Assertions. +* [`AbstractSigner`](#AbstractSigner): Abstract contract for internal signature validation in smart contracts. +* [`ERC7739`](#ERC7739): An abstract contract to validate signatures following the rehashing scheme from [`ERC7739Utils`](#ERC7739Utils). +* [`SignerECDSA`](#SignerECDSA), [`SignerP256`](#SignerP256), [`SignerRSA`](#SignerRSA): Implementations of an [`AbstractSigner`](#AbstractSigner) with specific signature validation algorithms. +* [`SignerEIP7702`](#SignerEIP7702): Implementation of [`AbstractSigner`](#AbstractSigner) that validates signatures using the contract’s own address as the signer, useful for delegated accounts following EIP-7702. +* [`SignerWebAuthn`](#SignerWebAuthn): Implementation of [`SignerP256`](#SignerP256) that supports WebAuthn +* [`SignerERC7913`](#SignerERC7913), [`MultiSignerERC7913`](#MultiSignerERC7913), [`MultiSignerERC7913Weighted`](#MultiSignerERC7913Weighted): Implementations of [`AbstractSigner`](#AbstractSigner) that validate signatures based on ERC-7913. Including a simple and weighted multisignature scheme. +* [`ERC7913P256Verifier`](#ERC7913P256Verifier), [`ERC7913RSAVerifier`](#ERC7913RSAVerifier), [`ERC7913WebAuthnVerifier`](#ERC7913WebAuthnVerifier): Ready to use ERC-7913 signature verifiers for P256, RSA keys and WebAuthn. + +## Utils + +[`ECDSA`](#ECDSA) + +[`MessageHashUtils`](#MessageHashUtils) + +[`P256`](#P256) + +[`RSA`](#RSA) + +[`SignatureChecker`](#SignatureChecker) + +[`Hashes`](#Hashes) + +[`MerkleProof`](#MerkleProof) + +[`EIP712`](#EIP712) + +[`ERC7739Utils`](#ERC7739Utils) + +[`WebAuthn`](#WebAuthn) + +## Abstract Signers + +[`AbstractSigner`](#AbstractSigner) + +[`ERC7739`](#ERC7739) + +[`SignerECDSA`](#SignerECDSA) + +[`SignerP256`](#SignerP256) + +[`SignerRSA`](#SignerRSA) + +[`SignerEIP7702`](#SignerEIP7702) + +[`SignerERC7913`](#SignerERC7913) + +[`MultiSignerERC7913`](#MultiSignerERC7913) + +[`MultiSignerERC7913Weighted`](#MultiSignerERC7913Weighted) + +## Verifiers + +[`ERC7913P256Verifier`](#ERC7913P256Verifier) + +[`ERC7913RSAVerifier`](#ERC7913RSAVerifier) + +[`ERC7913WebAuthnVerifier`](#ERC7913WebAuthnVerifier) + + + +
+ +## `ECDSA` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +``` + +Elliptic Curve Digital Signature Algorithm (ECDSA) operations. + +These functions can be used to verify that a message was signed by the holder +of the private keys of a given address. + +
+

Functions

+
+- [tryRecover(hash, signature)](#ECDSA-tryRecover-bytes32-bytes-) +- [tryRecoverCalldata(hash, signature)](#ECDSA-tryRecoverCalldata-bytes32-bytes-) +- [recover(hash, signature)](#ECDSA-recover-bytes32-bytes-) +- [recoverCalldata(hash, signature)](#ECDSA-recoverCalldata-bytes32-bytes-) +- [tryRecover(hash, r, vs)](#ECDSA-tryRecover-bytes32-bytes32-bytes32-) +- [recover(hash, r, vs)](#ECDSA-recover-bytes32-bytes32-bytes32-) +- [tryRecover(hash, v, r, s)](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) +- [recover(hash, v, r, s)](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) +- [parse(signature)](#ECDSA-parse-bytes-) +- [parseCalldata(signature)](#ECDSA-parseCalldata-bytes-) +
+
+ +
+

Errors

+
+- [ECDSAInvalidSignature()](#ECDSA-ECDSAInvalidSignature--) +- [ECDSAInvalidSignatureLength(length)](#ECDSA-ECDSAInvalidSignatureLength-uint256-) +- [ECDSAInvalidSignatureS(s)](#ECDSA-ECDSAInvalidSignatureS-bytes32-) +
+
+ + + +
+
+

tryRecover(bytes32 hash, bytes signature) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg

+
+

internal

+# +
+
+
+ +Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not +return address(0) without also returning an error description. Errors are documented using an enum (error type) +and a bytes32 providing additional information about the error. + +If no error is returned, then the address can be used for verification purposes. + +The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: +this function rejects them by requiring the `s` value to be in the lower +half order, and the `v` value to be either 27 or 28. + + +This function only supports 65-byte signatures. ERC-2098 short signatures are rejected. This restriction +is DEPRECATED and will be removed in v6.0. Developers SHOULD NOT use signatures as unique identifiers; use hash +invalidation or nonces for replay protection. + + + +`hash` _must_ be the result of a hash operation for the +verification to be secure: it is possible to craft signatures that +recover to arbitrary addresses for non-hashed data. A safe way to ensure +this is by receiving a hash of the original message (which may otherwise +be too long), and then calling [`MessageHashUtils.toEthSignedMessageHash`](#MessageHashUtils-toEthSignedMessageHash-bytes-) on it. + + +Documentation for signature generation: + +- with [Web3.js](https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign) +- with [ethers](https://docs.ethers.io/v5/api/signer/#Signer-signMessage) + +
+
+ + + +
+
+

tryRecoverCalldata(bytes32 hash, bytes signature) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg

+
+

internal

+# +
+
+
+ +Variant of [`ECDSA.tryRecover`](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) that takes a signature in calldata + +
+
+ + + +
+
+

recover(bytes32 hash, bytes signature) → address

+
+

internal

+# +
+
+
+ +Returns the address that signed a hashed message (`hash`) with +`signature`. This address can then be used for verification purposes. + +The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: +this function rejects them by requiring the `s` value to be in the lower +half order, and the `v` value to be either 27 or 28. + + +This function only supports 65-byte signatures. ERC-2098 short signatures are rejected. This restriction +is DEPRECATED and will be removed in v6.0. Developers SHOULD NOT use signatures as unique identifiers; use hash +invalidation or nonces for replay protection. + + + +`hash` _must_ be the result of a hash operation for the +verification to be secure: it is possible to craft signatures that +recover to arbitrary addresses for non-hashed data. A safe way to ensure +this is by receiving a hash of the original message (which may otherwise +be too long), and then calling [`MessageHashUtils.toEthSignedMessageHash`](#MessageHashUtils-toEthSignedMessageHash-bytes-) on it. + + +
+
+ + + +
+
+

recoverCalldata(bytes32 hash, bytes signature) → address

+
+

internal

+# +
+
+
+ +Variant of [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) that takes a signature in calldata + +
+
+ + + +
+
+

tryRecover(bytes32 hash, bytes32 r, bytes32 vs) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.tryRecover`](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) that receives the `r` and `vs` short-signature fields separately. + +See [ERC-2098 short signatures](https://eips.ethereum.org/EIPS/eip-2098) + +
+
+ + + +
+
+

recover(bytes32 hash, bytes32 r, bytes32 vs) → address

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) that receives the `r and `vs` short-signature fields separately. + +
+
+ + + +
+
+

tryRecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address recovered, enum ECDSA.RecoverError err, bytes32 errArg

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.tryRecover`](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-) that receives the `v`, +`r` and `s` signature fields separately. + +
+
+ + + +
+
+

recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) → address

+
+

internal

+# +
+
+
+ +Overload of [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) that receives the `v`, +`r` and `s` signature fields separately. + +
+
+ + + +
+
+

parse(bytes signature) → uint8 v, bytes32 r, bytes32 s

+
+

internal

+# +
+
+
+ +Parse a signature into its `v`, `r` and `s` components. Supports 65-byte and 64-byte (ERC-2098) +formats. Returns (0,0,0) for invalid signatures. + +For 64-byte signatures, `v` is automatically normalized to 27 or 28. +For 65-byte signatures, `v` is returned as-is and MUST already be 27 or 28 for use with ecrecover. + +Consider validating the result before use, or use [`ECDSA.tryRecover`](#ECDSA-tryRecover-bytes32-uint8-bytes32-bytes32-)/[`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) which perform full validation. + +
+
+ + + +
+
+

parseCalldata(bytes signature) → uint8 v, bytes32 r, bytes32 s

+
+

internal

+# +
+
+
+ +Variant of [`CAIP10.parse`](#CAIP10-parse-string-) that takes a signature in calldata + +
+
+ + + +
+
+

ECDSAInvalidSignature()

+
+

error

+# +
+
+
+ +The signature derives the `address(0)`. + +
+
+ + + +
+
+

ECDSAInvalidSignatureLength(uint256 length)

+
+

error

+# +
+
+
+ +The signature has an invalid length. + +
+
+ + + +
+
+

ECDSAInvalidSignatureS(bytes32 s)

+
+

error

+# +
+
+
+ +The signature has an S value that is in the upper half order. + +
+
+ + + +
+ +## `EIP712` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +``` + +[EIP-712](https://eips.ethereum.org/EIPS/eip-712) is a standard for hashing and signing of typed structured data. + +The encoding scheme specified in the EIP requires a domain separator and a hash of the typed structured data, whose +encoding is very generic and therefore its implementation in Solidity is not feasible, thus this contract +does not implement the encoding itself. Protocols need to implement the type-specific encoding they need in order to +produce the hash of their typed data using a combination of `abi.encode` and `keccak256`. + +This contract implements the EIP-712 domain separator ([`EIP712._domainSeparatorV4`](#EIP712-_domainSeparatorV4--)) that is used as part of the encoding +scheme, and the final step of the encoding to obtain the message digest that is then signed via ECDSA +([`EIP712._hashTypedDataV4`](#EIP712-_hashTypedDataV4-bytes32-)). + +The implementation of the domain separator was designed to be as efficient as possible while still properly updating +the chain id to protect against replay attacks on an eventual fork of the chain. + + +This contract implements the version of the encoding known as "v4", as implemented by the JSON RPC method +[`eth_signTypedDataV4` in MetaMask](https://docs.metamask.io/guide/signing-data.html). + + + +In the upgradeable version of this contract, the cached values will correspond to the address, and the domain +separator of the implementation contract. This will cause the [`EIP712._domainSeparatorV4`](#EIP712-_domainSeparatorV4--) function to always rebuild the +separator from the immutable values, which is cheaper than accessing a cached version in cold storage. + + +
+

Functions

+
+- [constructor(name, version)](#EIP712-constructor-string-string-) +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +
+
+ +
+

Events

+
+#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +
+
+ + + +
+
+

constructor(string name, string version)

+
+

internal

+# +
+
+
+ +Initializes the domain separator and parameter caches. + +The meaning of `name` and `version` is specified in +[EIP-712](https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator): + +- `name`: the user readable name of the signing domain, i.e. the name of the DApp or the protocol. +- `version`: the current major version of the signing domain. + + +These parameters cannot be changed except through a [smart contract upgrade](/contracts/5.x/learn/upgrading-smart-contracts). + + +
+
+ + + +
+
+

_domainSeparatorV4() → bytes32

+
+

internal

+# +
+
+
+ +Returns the domain separator for the current chain. + +
+
+ + + +
+
+

_hashTypedDataV4(bytes32 structHash) → bytes32

+
+

internal

+# +
+
+
+ +Given an already [hashed struct](https://eips.ethereum.org/EIPS/eip-712#definition-of-hashstruct), this +function returns the hash of the fully encoded EIP712 message for this domain. + +This hash can be used together with [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-) to obtain the signer of a message. For example: + +```solidity +bytes32 digest = _hashTypedDataV4(keccak256(abi.encode( + keccak256("Mail(address to,string contents)"), + mailTo, + keccak256(bytes(mailContents)) +))); +address signer = ECDSA.recover(digest, signature); +``` + +
+
+ + + +
+
+

eip712Domain() → bytes1 fields, string name, string version, uint256 chainId, address verifyingContract, bytes32 salt, uint256[] extensions

+
+

public

+# +
+
+
+ +returns the fields and values that describe the domain separator used by this contract for EIP-712 +signature. + +
+
+ + + +
+
+

_EIP712Name() → string

+
+

internal

+# +
+
+
+ +The name parameter for the EIP712 domain. + + +By default this function reads _name which is an immutable value. +It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + + +
+
+ + + +
+
+

_EIP712Version() → string

+
+

internal

+# +
+
+
+ +The version parameter for the EIP712 domain. + + +By default this function reads _version which is an immutable value. +It only reads from storage if necessary (in case the value is too large to fit in a ShortString). + + +
+
+ + + +
+ +## `Hashes` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/Hashes.sol"; +``` + +Library of standard hash functions. + +_Available since v5.1._ + +
+

Functions

+
+- [commutativeKeccak256(a, b)](#Hashes-commutativeKeccak256-bytes32-bytes32-) +- [efficientKeccak256(a, b)](#Hashes-efficientKeccak256-bytes32-bytes32-) +
+
+ + + +
+
+

commutativeKeccak256(bytes32 a, bytes32 b) → bytes32

+
+

internal

+# +
+
+
+ +Commutative Keccak256 hash of a sorted pair of bytes32. Frequently used when working with merkle proofs. + + +Equivalent to the `standardNodeHash` in our [JavaScript library](https://github.com/OpenZeppelin/merkle-tree). + + +
+
+ + + +
+
+

efficientKeccak256(bytes32 a, bytes32 b) → bytes32 value

+
+

internal

+# +
+
+
+ +Implementation of keccak256(abi.encode(a, b)) that doesn't allocate or expand memory. + +
+
+ + + +
+ +## `MerkleProof` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; +``` + +These functions deal with verification of Merkle Tree proofs. + +The tree and the proofs can be generated using our +[JavaScript library](https://github.com/OpenZeppelin/merkle-tree). +You will find a quickstart guide in the readme. + + +You should avoid using leaf values that are 64 bytes long prior to +hashing, or use a hash function other than keccak256 for hashing leaves. +This is because the concatenation of a sorted pair of internal nodes in +the Merkle tree could be reinterpreted as a leaf value. +OpenZeppelin's JavaScript library generates Merkle trees that are safe +against this attack out of the box. + + + +Consider memory side-effects when using custom hashing functions +that access memory in an unsafe way. + + + +This library supports proof verification for merkle trees built using +custom _commutative_ hashing functions (i.e. `H(a, b) == H(b, a)`). Proving +leaf inclusion in trees built using non-commutative hashing functions requires +additional logic that is not supported by this library. + + +
+

Functions

+
+- [verify(proof, root, leaf)](#MerkleProof-verify-bytes32---bytes32-bytes32-) +- [processProof(proof, leaf)](#MerkleProof-processProof-bytes32---bytes32-) +- [verify(proof, root, leaf, hasher)](#MerkleProof-verify-bytes32---bytes32-bytes32-function--bytes32-bytes32--view-returns--bytes32--) +- [processProof(proof, leaf, hasher)](#MerkleProof-processProof-bytes32---bytes32-function--bytes32-bytes32--view-returns--bytes32--) +- [verifyCalldata(proof, root, leaf)](#MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-) +- [processProofCalldata(proof, leaf)](#MerkleProof-processProofCalldata-bytes32---bytes32-) +- [verifyCalldata(proof, root, leaf, hasher)](#MerkleProof-verifyCalldata-bytes32---bytes32-bytes32-function--bytes32-bytes32--view-returns--bytes32--) +- [processProofCalldata(proof, leaf, hasher)](#MerkleProof-processProofCalldata-bytes32---bytes32-function--bytes32-bytes32--view-returns--bytes32--) +- [multiProofVerify(proof, proofFlags, root, leaves)](#MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---) +- [processMultiProof(proof, proofFlags, leaves)](#MerkleProof-processMultiProof-bytes32---bool---bytes32---) +- [multiProofVerify(proof, proofFlags, root, leaves, hasher)](#MerkleProof-multiProofVerify-bytes32---bool---bytes32-bytes32---function--bytes32-bytes32--view-returns--bytes32--) +- [processMultiProof(proof, proofFlags, leaves, hasher)](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--) +- [multiProofVerifyCalldata(proof, proofFlags, root, leaves)](#MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---) +- [processMultiProofCalldata(proof, proofFlags, leaves)](#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---) +- [multiProofVerifyCalldata(proof, proofFlags, root, leaves, hasher)](#MerkleProof-multiProofVerifyCalldata-bytes32---bool---bytes32-bytes32---function--bytes32-bytes32--view-returns--bytes32--) +- [processMultiProofCalldata(proof, proofFlags, leaves, hasher)](#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--) +
+
+ +
+

Errors

+
+- [MerkleProofInvalidMultiproof()](#MerkleProof-MerkleProofInvalidMultiproof--) +
+
+ + + +
+
+

verify(bytes32[] proof, bytes32 root, bytes32 leaf) → bool

+
+

internal

+# +
+
+
+ +Returns true if a `leaf` can be proved to be a part of a Merkle tree +defined by `root`. For this, a `proof` must be provided, containing +sibling hashes on the branch from the leaf to the root of the tree. Each +pair of leaves and each pair of pre-images are assumed to be sorted. + +This version handles proofs in memory with the default hashing function. + +
+
+ + + +
+
+

processProof(bytes32[] proof, bytes32 leaf) → bytes32

+
+

internal

+# +
+
+
+ +Returns the rebuilt hash obtained by traversing a Merkle tree up +from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt +hash matches the root of the tree. When processing the proof, the pairs +of leaves & pre-images are assumed to be sorted. + +This version handles proofs in memory with the default hashing function. + +
+
+ + + +
+
+

verify(bytes32[] proof, bytes32 root, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bool

+
+

internal

+# +
+
+
+ +Returns true if a `leaf` can be proved to be a part of a Merkle tree +defined by `root`. For this, a `proof` must be provided, containing +sibling hashes on the branch from the leaf to the root of the tree. Each +pair of leaves and each pair of pre-images are assumed to be sorted. + +This version handles proofs in memory with a custom hashing function. + +
+
+ + + +
+
+

processProof(bytes32[] proof, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32

+
+

internal

+# +
+
+
+ +Returns the rebuilt hash obtained by traversing a Merkle tree up +from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt +hash matches the root of the tree. When processing the proof, the pairs +of leaves & pre-images are assumed to be sorted. + +This version handles proofs in memory with a custom hashing function. + +
+
+ + + +
+
+

verifyCalldata(bytes32[] proof, bytes32 root, bytes32 leaf) → bool

+
+

internal

+# +
+
+
+ +Returns true if a `leaf` can be proved to be a part of a Merkle tree +defined by `root`. For this, a `proof` must be provided, containing +sibling hashes on the branch from the leaf to the root of the tree. Each +pair of leaves and each pair of pre-images are assumed to be sorted. + +This version handles proofs in calldata with the default hashing function. + +
+
+ + + +
+
+

processProofCalldata(bytes32[] proof, bytes32 leaf) → bytes32

+
+

internal

+# +
+
+
+ +Returns the rebuilt hash obtained by traversing a Merkle tree up +from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt +hash matches the root of the tree. When processing the proof, the pairs +of leaves & pre-images are assumed to be sorted. + +This version handles proofs in calldata with the default hashing function. + +
+
+ + + +
+
+

verifyCalldata(bytes32[] proof, bytes32 root, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bool

+
+

internal

+# +
+
+
+ +Returns true if a `leaf` can be proved to be a part of a Merkle tree +defined by `root`. For this, a `proof` must be provided, containing +sibling hashes on the branch from the leaf to the root of the tree. Each +pair of leaves and each pair of pre-images are assumed to be sorted. + +This version handles proofs in calldata with a custom hashing function. + +
+
+ + + +
+
+

processProofCalldata(bytes32[] proof, bytes32 leaf, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32

+
+

internal

+# +
+
+
+ +Returns the rebuilt hash obtained by traversing a Merkle tree up +from `leaf` using `proof`. A `proof` is valid if and only if the rebuilt +hash matches the root of the tree. When processing the proof, the pairs +of leaves & pre-images are assumed to be sorted. + +This version handles proofs in calldata with a custom hashing function. + +
+
+ + + +
+
+

multiProofVerify(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) → bool

+
+

internal

+# +
+
+
+ +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by +`root`, according to `proof` and `proofFlags` as described in [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + +This version handles multiproofs in memory with the default hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--) for details. + + +Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. +The `leaves` must be validated independently. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + + +
+
+ + + +
+
+

processMultiProof(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) → bytes32 merkleRoot

+
+

internal

+# +
+
+
+ +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction +proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another +leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false +respectively. + +This version handles multiproofs in memory with the default hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree +is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the +tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + + +The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, +and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not +validating the leaves elsewhere. + + +
+
+ + + +
+
+

multiProofVerify(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bool

+
+

internal

+# +
+
+
+ +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by +`root`, according to `proof` and `proofFlags` as described in [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + +This version handles multiproofs in memory with a custom hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--) for details. + + +Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. +The `leaves` must be validated independently. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + + +
+
+ + + +
+
+

processMultiProof(bytes32[] proof, bool[] proofFlags, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32 merkleRoot

+
+

internal

+# +
+
+
+ +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction +proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another +leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false +respectively. + +This version handles multiproofs in memory with a custom hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree +is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the +tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + + +The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, +and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not +validating the leaves elsewhere. + + +
+
+ + + +
+
+

multiProofVerifyCalldata(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves) → bool

+
+

internal

+# +
+
+
+ +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by +`root`, according to `proof` and `proofFlags` as described in [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + +This version handles multiproofs in calldata with the default hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--) for details. + + +Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. +The `leaves` must be validated independently. See [`MerkleProof.processMultiProofCalldata`](#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + + +
+
+ + + +
+
+

processMultiProofCalldata(bytes32[] proof, bool[] proofFlags, bytes32[] leaves) → bytes32 merkleRoot

+
+

internal

+# +
+
+
+ +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction +proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another +leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false +respectively. + +This version handles multiproofs in calldata with the default hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree +is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the +tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + + +The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, +and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not +validating the leaves elsewhere. + + +
+
+ + + +
+
+

multiProofVerifyCalldata(bytes32[] proof, bool[] proofFlags, bytes32 root, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bool

+
+

internal

+# +
+
+
+ +Returns true if the `leaves` can be simultaneously proven to be a part of a Merkle tree defined by +`root`, according to `proof` and `proofFlags` as described in [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + +This version handles multiproofs in calldata with a custom hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. See [`MerkleProof.processMultiProof`](#MerkleProof-processMultiProof-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--) for details. + + +Consider the case where `root == proof[0] && leaves.length == 0` as it will return `true`. +The `leaves` must be validated independently. See [`MerkleProof.processMultiProofCalldata`](#MerkleProof-processMultiProofCalldata-bytes32---bool---bytes32---function--bytes32-bytes32--view-returns--bytes32--). + + +
+
+ + + +
+
+

processMultiProofCalldata(bytes32[] proof, bool[] proofFlags, bytes32[] leaves, function (bytes32,bytes32) view returns (bytes32) hasher) → bytes32 merkleRoot

+
+

internal

+# +
+
+
+ +Returns the root of a tree reconstructed from `leaves` and sibling nodes in `proof`. The reconstruction +proceeds by incrementally reconstructing all inner nodes by combining a leaf/inner node with either another +leaf/inner node or a proof sibling node, depending on whether each `proofFlags` item is true or false +respectively. + +This version handles multiproofs in calldata with a custom hashing function. + +CAUTION: Not all Merkle trees admit multiproofs. To use multiproofs, it is sufficient to ensure that: 1) the tree +is complete (but not necessarily perfect), 2) the leaves to be proven are in the opposite order they are in the +tree (i.e., as seen from right to left starting at the deepest layer and continuing at the next layer). + + +The _empty set_ (i.e. the case where `proof.length == 1 && leaves.length == 0`) is considered a no-op, +and therefore a valid multiproof (i.e. it returns `proof[0]`). Consider disallowing this case if you're not +validating the leaves elsewhere. + + +
+
+ + + +
+
+

MerkleProofInvalidMultiproof()

+
+

error

+# +
+
+
+ +The multiproof provided is not valid. + +
+
+ + + +
+ +## `MessageHashUtils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; +``` + +Signature message hash utilities for producing digests to be consumed by [`ECDSA`](#ECDSA) recovery or signing. + +The library provides methods for generating a hash of a message that conforms to the +[ERC-191](https://eips.ethereum.org/EIPS/eip-191) and [EIP 712](https://eips.ethereum.org/EIPS/eip-712) +specifications. + +
+

Functions

+
+- [toEthSignedMessageHash(messageHash)](#MessageHashUtils-toEthSignedMessageHash-bytes32-) +- [toEthSignedMessageHash(message)](#MessageHashUtils-toEthSignedMessageHash-bytes-) +- [toDataWithIntendedValidatorHash(validator, data)](#MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes-) +- [toDataWithIntendedValidatorHash(validator, messageHash)](#MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes32-) +- [toTypedDataHash(domainSeparator, structHash)](#MessageHashUtils-toTypedDataHash-bytes32-bytes32-) +
+
+ + + +
+
+

toEthSignedMessageHash(bytes32 messageHash) → bytes32 digest

+
+

internal

+# +
+
+
+ +Returns the keccak256 digest of an ERC-191 signed data with version +`0x45` (`personal_sign` messages). + +The digest is calculated by prefixing a bytes32 `messageHash` with +`"\x19Ethereum Signed Message:\n32"` and hashing the result. It corresponds with the +hash signed when using the [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) JSON-RPC method. + + +The `messageHash` parameter is intended to be the result of hashing a raw message with +keccak256, although any bytes32 value can be safely used because the final digest will +be re-hashed. + + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

toEthSignedMessageHash(bytes message) → bytes32

+
+

internal

+# +
+
+
+ +Returns the keccak256 digest of an ERC-191 signed data with version +`0x45` (`personal_sign` messages). + +The digest is calculated by prefixing an arbitrary `message` with +`"\x19Ethereum Signed Message:\n" + len(message)` and hashing the result. It corresponds with the +hash signed when using the [`eth_sign`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sign) JSON-RPC method. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

toDataWithIntendedValidatorHash(address validator, bytes data) → bytes32

+
+

internal

+# +
+
+
+ +Returns the keccak256 digest of an ERC-191 signed data with version +`0x00` (data with intended validator). + +The digest is calculated by prefixing an arbitrary `data` with `"\x19\x00"` and the intended +`validator` address. Then hashing the result. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+
+

toDataWithIntendedValidatorHash(address validator, bytes32 messageHash) → bytes32 digest

+
+

internal

+# +
+
+
+ +Variant of #MessageHashUtils-toDataWithIntendedValidatorHash-address-bytes- optimized for cases where `data` is a bytes32. + +
+
+ + + +
+
+

toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) → bytes32 digest

+
+

internal

+# +
+
+
+ +Returns the keccak256 digest of an EIP-712 typed data (ERC-191 version `0x01`). + +The digest is calculated from a `domainSeparator` and a `structHash`, by prefixing them with +`\x19\x01` and hashing the result. It corresponds to the hash signed by the +[`eth_signTypedData`](https://eips.ethereum.org/EIPS/eip-712) JSON-RPC method as part of EIP-712. + +See [`ECDSA.recover`](#ECDSA-recover-bytes32-uint8-bytes32-bytes32-). + +
+
+ + + +
+ +## `P256` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/P256.sol"; +``` + +Implementation of secp256r1 verification and recovery functions. + +The secp256r1 curve (also known as P256) is a NIST standard curve with wide support in modern devices +and cryptographic standards. Some notable examples include Apple's Secure Enclave and Android's Keystore +as well as authentication protocols like FIDO2. + +Based on the original [implementation of itsobvioustech](https://github.com/itsobvioustech/aa-passkeys-wallet/blob/d3d423f28a4d8dfcb203c7fa0c47f42592a7378e/src/Secp256r1.sol) (GNU General Public License v3.0). +Heavily inspired in [maxrobot](https://github.com/maxrobot/elliptic-solidity/blob/c4bb1b6e8ae89534d8db3a6b3a6b52219100520f/contracts/Secp256r1.sol) and +[tdrerup](https://github.com/tdrerup/elliptic-curve-solidity/blob/59a9c25957d4d190eff53b6610731d81a077a15e/contracts/curves/EllipticCurve.sol) implementations. + +_Available since v5.1._ + +
+

Functions

+
+- [verify(h, r, s, qx, qy)](#P256-verify-bytes32-bytes32-bytes32-bytes32-bytes32-) +- [verifyNative(h, r, s, qx, qy)](#P256-verifyNative-bytes32-bytes32-bytes32-bytes32-bytes32-) +- [verifySolidity(h, r, s, qx, qy)](#P256-verifySolidity-bytes32-bytes32-bytes32-bytes32-bytes32-) +- [recovery(h, v, r, s)](#P256-recovery-bytes32-uint8-bytes32-bytes32-) +- [isValidPublicKey(x, y)](#P256-isValidPublicKey-bytes32-bytes32-) +
+
+ + + +
+
+

verify(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) → bool

+
+

internal

+# +
+
+
+ +Verifies a secp256r1 signature using the RIP-7212 precompile and falls back to the Solidity implementation +if the precompile is not available. This version should work on all chains, but requires the deployment of more +bytecode. + +
+
+ + + +
+
+

verifyNative(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) → bool

+
+

internal

+# +
+
+
+ +Same as [`IERC7913SignatureVerifier.verify`](../interfaces#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-), but it will revert if the required precompile is not available. + +Make sure any logic (code or precompile) deployed at that address is the expected one, +otherwise the returned value may be misinterpreted as a positive boolean. + +
+
+ + + +
+
+

verifySolidity(bytes32 h, bytes32 r, bytes32 s, bytes32 qx, bytes32 qy) → bool

+
+

internal

+# +
+
+
+ +Same as [`IERC7913SignatureVerifier.verify`](../interfaces#IERC7913SignatureVerifier-verify-bytes-bytes32-bytes-), but only the Solidity implementation is used. + +
+
+ + + +
+
+

recovery(bytes32 h, uint8 v, bytes32 r, bytes32 s) → bytes32 x, bytes32 y

+
+

internal

+# +
+
+
+ +Public key recovery + +
+
+ + + +
+
+

isValidPublicKey(bytes32 x, bytes32 y) → bool result

+
+

internal

+# +
+
+
+ +Checks if (x, y) are valid coordinates of a point on the curve. +In particular this function checks that x < P and y < P. + +
+
+ + + +
+ +## `RSA` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/RSA.sol"; +``` + +RSA PKCS#1 v1.5 signature verification implementation according to [RFC8017](https://datatracker.ietf.org/doc/html/rfc8017). + +This library supports PKCS#1 v1.5 padding to avoid malleability via chosen plaintext attacks in practical implementations. +The padding follows the EMSA-PKCS1-v1_5-ENCODE encoding definition as per section 9.2 of the RFC. This padding makes +RSA semantically secure for signing messages. + +Inspired by [Adrià Massanet's work](https://github.com/adria0/SolRsaVerify/blob/79c6182cabb9102ea69d4a2e996816091d5f1cd1) (GNU General Public License v3.0). + +_Available since v5.1._ + +
+

Functions

+
+- [pkcs1Sha256(data, s, e, n)](#RSA-pkcs1Sha256-bytes-bytes-bytes-bytes-) +- [pkcs1Sha256(digest, s, e, n)](#RSA-pkcs1Sha256-bytes32-bytes-bytes-bytes-) +
+
+ + + +
+
+

pkcs1Sha256(bytes data, bytes s, bytes e, bytes n) → bool

+
+

internal

+# +
+
+
+ +Same as [`RSA.pkcs1Sha256`](#RSA-pkcs1Sha256-bytes32-bytes-bytes-bytes-) but using SHA256 to calculate the digest of `data`. + +
+
+ + + +
+
+

pkcs1Sha256(bytes32 digest, bytes s, bytes e, bytes n) → bool

+
+

internal

+# +
+
+
+ +Verifies a PKCSv1.5 signature given a digest according to the verification +method described in [section 8.2.2 of RFC8017](https://datatracker.ietf.org/doc/html/rfc8017#section-8.2.2) with +support for explicit or implicit NULL parameters in the DigestInfo (no other optional parameters are supported). + + +For security reason, this function requires the signature and modulus to have a length of at least +2048 bits. If you use a smaller key, consider replacing it with a larger, more secure, one. + + + +This verification algorithm doesn't prevent replayability. If called multiple times with the same +digest, public key and (valid signature), it will return true every time. Consider including an onchain nonce +or unique identifier in the message to prevent replay attacks. + + + +This verification algorithm supports any exponent. NIST recommends using `65537` (or higher). +That is the default value many libraries use, such as OpenSSL. Developers may choose to reject public keys +using a low exponent out of security concerns. + + +
+
+ + + +
+ +## `SignatureChecker` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +``` + +Signature verification helper that can be used instead of `ECDSA.recover` to seamlessly support: + +* ECDSA signatures from externally owned accounts (EOAs) +* ERC-1271 signatures from smart contract wallets like Argent and Safe Wallet (previously Gnosis Safe) +* ERC-7913 signatures from keys that do not have an Ethereum address of their own + +See [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) and [ERC-7913](https://eips.ethereum.org/EIPS/eip-7913). + +
+

Functions

+
+- [isValidSignatureNow(signer, hash, signature)](#SignatureChecker-isValidSignatureNow-address-bytes32-bytes-) +- [isValidSignatureNowCalldata(signer, hash, signature)](#SignatureChecker-isValidSignatureNowCalldata-address-bytes32-bytes-) +- [isValidERC1271SignatureNow(signer, hash, signature)](#SignatureChecker-isValidERC1271SignatureNow-address-bytes32-bytes-) +- [isValidSignatureNow(signer, hash, signature)](#SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes-) +- [areValidSignaturesNow(hash, signers, signatures)](#SignatureChecker-areValidSignaturesNow-bytes32-bytes---bytes---) +
+
+ + + +
+
+

isValidSignatureNow(address signer, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Checks if a signature is valid for a given signer and data hash. If the signer has code, the +signature is validated against it using ERC-1271, otherwise it's validated using `ECDSA.recover`. + + +Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus +change through time. It could return true at block N and false at block N+1 (or the opposite). + + + +For an extended version of this function that supports ERC-7913 signatures, see #SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes-. + + +
+
+ + + +
+
+

isValidSignatureNowCalldata(address signer, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Variant of [`SignatureChecker.isValidSignatureNow`](#SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes-) that takes a signature in calldata + +
+
+ + + +
+
+

isValidERC1271SignatureNow(address signer, bytes32 hash, bytes signature) → bool result

+
+

internal

+# +
+
+
+ +Checks if a signature is valid for a given signer and data hash. The signature is validated +against the signer smart contract using ERC-1271. + + +Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus +change through time. It could return true at block N and false at block N+1 (or the opposite). + + +
+
+ + + +
+
+

isValidSignatureNow(bytes signer, bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Verifies a signature for a given ERC-7913 signer and hash. + +The signer is a `bytes` object that is the concatenation of an address and optionally a key: +`verifier || key`. A signer must be at least 20 bytes long. + +Verification is done as follows: + +* If `signer.length < 20`: verification fails +* If `signer.length == 20`: verification is done using [`SignatureChecker.isValidSignatureNow`](#SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes-) +* Otherwise: verification is done using [`IERC7913SignatureVerifier`](../interfaces#IERC7913SignatureVerifier) + + +Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus +change through time. It could return true at block N and false at block N+1 (or the opposite). + + +
+
+ + + +
+
+

areValidSignaturesNow(bytes32 hash, bytes[] signers, bytes[] signatures) → bool

+
+

internal

+# +
+
+
+ +Verifies multiple ERC-7913 `signatures` for a given `hash` using a set of `signers`. +Returns `false` if the number of signers and signatures is not the same. + +The signers should be ordered by their `keccak256` hash to ensure efficient duplication check. Unordered +signers are supported, but the uniqueness check will be more expensive. + + +Unlike ECDSA signatures, contract signatures are revocable, and the outcome of this function can thus +change through time. It could return true at block N and false at block N+1 (or the opposite). + + +
+
+ + + +
+ +## `WebAuthn` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/WebAuthn.sol"; +``` + +Library for verifying WebAuthn Authentication Assertions. + +WebAuthn enables strong authentication for smart contracts using +[P256](https://docs.openzeppelin.com/contracts/5.x/api/utils#P256) +as an alternative to traditional secp256k1 ECDSA signatures. This library verifies +signatures generated during WebAuthn authentication ceremonies as specified in the +[WebAuthn Level 2 standard](https://www.w3.org/TR/webauthn-2/). + +For blockchain use cases, the following WebAuthn validations are intentionally omitted: + +* Origin validation: Origin verification in `clientDataJSON` is omitted as blockchain + contexts rely on authenticator and dapp frontend enforcement. Standard authenticators + implement proper origin validation. +* RP ID hash validation: Verification of `rpIdHash` in authenticatorData against expected + RP ID hash is omitted. This is typically handled by platform-level security measures. + Including an expiry timestamp in signed data is recommended for enhanced security. +* Signature counter: Verification of signature counter increments is omitted. While + useful for detecting credential cloning, on-chain operations typically include nonce + protection, making this check redundant. +* Extension outputs: Extension output value verification is omitted as these are not + essential for core authentication security in blockchain applications. +* Attestation: Attestation object verification is omitted as this implementation + focuses on authentication (`webauthn.get`) rather than registration ceremonies. + +Inspired by: + +* [daimo-eth implementation](https://github.com/daimo-eth/p256-verifier/blob/master/src/WebAuthn.sol) +* [base implementation](https://github.com/base/webauthn-sol/blob/main/src/WebAuthn.sol) + +
+

Functions

+
+- [verify(challenge, auth, qx, qy)](#WebAuthn-verify-bytes-struct-WebAuthn-WebAuthnAuth-bytes32-bytes32-) +- [verify(challenge, auth, qx, qy, requireUV)](#WebAuthn-verify-bytes-struct-WebAuthn-WebAuthnAuth-bytes32-bytes32-bool-) +- [tryDecodeAuth(input)](#WebAuthn-tryDecodeAuth-bytes-) +
+
+ + + +
+
+

verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy) → bool

+
+

internal

+# +
+
+
+ +Performs standard verification of a WebAuthn Authentication Assertion. + +
+
+ + + +
+
+

verify(bytes challenge, struct WebAuthn.WebAuthnAuth auth, bytes32 qx, bytes32 qy, bool requireUV) → bool

+
+

internal

+# +
+
+
+ +Performs verification of a WebAuthn Authentication Assertion. This variants allow the caller to select +whether of not to require the UV flag (step 17). + +Verifies: + +1. Type is "webauthn.get" (see [`WebAuthn._validateExpectedTypeHash`](#WebAuthn-_validateExpectedTypeHash-string-uint256-)) +2. Challenge matches the expected value (see [`WebAuthn._validateChallenge`](#WebAuthn-_validateChallenge-string-uint256-bytes-)) +3. Cryptographic signature is valid for the given public key +4. confirming physical user presence during authentication +5. (if `requireUV` is true) confirming stronger user authentication (biometrics/PIN) +6. Backup Eligibility (`BE`) and Backup State (BS) bits relationship is valid + +
+
+ + + +
+
+

tryDecodeAuth(bytes input) → bool success, struct WebAuthn.WebAuthnAuth auth

+
+

internal

+# +
+
+
+ +Verifies that calldata bytes (`input`) represents a valid `WebAuthnAuth` object. If encoding is valid, +returns true and the calldata view at the object. Otherwise, returns false and an invalid calldata object. + + +The returned `auth` object should not be accessed if `success` is false. Trying to access the data may +cause revert/panic. + + +
+
+ + + +
+ +## `ERC7739Utils` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/draft-ERC7739Utils.sol"; +``` + +Utilities to process [ERC-7739](https://ercs.ethereum.org/ERCS/erc-7739) typed data signatures +that are specific to an EIP-712 domain. + +This library provides methods to wrap, unwrap and operate over typed data signatures with a defensive rehashing mechanism that includes the app's [EIP-712](/contracts/5.x/api/utils/cryptography#EIP712-_domainSeparatorV4) +and preserves readability of the signed content using an EIP-712 nested approach. + +A smart contract domain can validate a signature for a typed data structure in two ways: + +- As an application validating a typed data signature. See [`ERC7739Utils.typedDataSignStructHash`](#ERC7739Utils-typedDataSignStructHash-string-bytes32-bytes-). +- As a smart contract validating a raw message signature. See [`ERC7739Utils.personalSignStructHash`](#ERC7739Utils-personalSignStructHash-bytes32-). + + +A provider for a smart contract wallet would need to return this signature as the +result of a call to `personal_sign` or `eth_signTypedData`, and this may be unsupported by +API clients that expect a return value of 129 bytes, or specifically the `r,s,v` parameters +of an [ECDSA](/contracts/5.x/api/utils/cryptography#ECDSA) signature, as is for example specified for [EIP-712](/contracts/5.x/api/utils/cryptography#EIP712). + + +
+

Functions

+
+- [encodeTypedDataSig(signature, appSeparator, contentsHash, contentsDescr)](#ERC7739Utils-encodeTypedDataSig-bytes-bytes32-bytes32-string-) +- [decodeTypedDataSig(encodedSignature)](#ERC7739Utils-decodeTypedDataSig-bytes-) +- [personalSignStructHash(contents)](#ERC7739Utils-personalSignStructHash-bytes32-) +- [typedDataSignStructHash(contentsName, contentsType, contentsHash, domainBytes)](#ERC7739Utils-typedDataSignStructHash-string-string-bytes32-bytes-) +- [typedDataSignStructHash(contentsDescr, contentsHash, domainBytes)](#ERC7739Utils-typedDataSignStructHash-string-bytes32-bytes-) +- [typedDataSignTypehash(contentsName, contentsType)](#ERC7739Utils-typedDataSignTypehash-string-string-) +- [decodeContentsDescr(contentsDescr)](#ERC7739Utils-decodeContentsDescr-string-) +
+
+ + + +
+
+

encodeTypedDataSig(bytes signature, bytes32 appSeparator, bytes32 contentsHash, string contentsDescr) → bytes

+
+

internal

+# +
+
+
+ +Nest a signature for a given EIP-712 type into a nested signature for the domain of the app. + +Counterpart of [`ERC7739Utils.decodeTypedDataSig`](#ERC7739Utils-decodeTypedDataSig-bytes-) to extract the original signature and the nested components. + +
+
+ + + +
+
+

decodeTypedDataSig(bytes encodedSignature) → bytes signature, bytes32 appSeparator, bytes32 contentsHash, string contentsDescr

+
+

internal

+# +
+
+
+ +Parses a nested signature into its components. + +Constructed as follows: + +`signature ‖ APP_DOMAIN_SEPARATOR ‖ contentsHash ‖ contentsDescr ‖ uint16(contentsDescr.length)` + +- `signature` is the signature for the (ERC-7739) nested struct hash. This signature indirectly signs over the + original "contents" hash (from the app) and the account's domain separator. +- `APP_DOMAIN_SEPARATOR` is the EIP-712 [`EIP712._domainSeparatorV4`](#EIP712-_domainSeparatorV4--) of the application smart contract that is + requesting the signature verification (through ERC-1271). +- `contentsHash` is the hash of the underlying data structure or message. +- `contentsDescr` is a descriptor of the "contents" part of the EIP-712 type of the nested signature. + + +This function returns empty if the input format is invalid instead of reverting. +data instead. + + +
+
+ + + +
+
+

personalSignStructHash(bytes32 contents) → bytes32

+
+

internal

+# +
+
+
+ +Nests an `ERC-191` digest into a `PersonalSign` EIP-712 struct, and returns the corresponding struct hash. +This struct hash must be combined with a domain separator, using [`MessageHashUtils.toTypedDataHash`](#MessageHashUtils-toTypedDataHash-bytes32-bytes32-) before +being verified/recovered. + +This is used to simulates the `personal_sign` RPC method in the context of smart contracts. + +
+
+ + + +
+
+

typedDataSignStructHash(string contentsName, string contentsType, bytes32 contentsHash, bytes domainBytes) → bytes32 result

+
+

internal

+# +
+
+
+ +Nests an `EIP-712` hash (`contents`) into a `TypedDataSign` EIP-712 struct, and returns the corresponding +struct hash. This struct hash must be combined with a domain separator, using [`MessageHashUtils.toTypedDataHash`](#MessageHashUtils-toTypedDataHash-bytes32-bytes32-) +before being verified/recovered. + +
+
+ + + +
+
+

typedDataSignStructHash(string contentsDescr, bytes32 contentsHash, bytes domainBytes) → bytes32 result

+
+

internal

+# +
+
+
+ +Variant of #ERC7739Utils-typedDataSignStructHash-string-string-bytes32-bytes- that takes a content descriptor +and decodes the `contentsName` and `contentsType` out of it. + +
+
+ + + +
+
+

typedDataSignTypehash(string contentsName, string contentsType) → bytes32

+
+

internal

+# +
+
+
+ +Compute the EIP-712 typehash of the `TypedDataSign` structure for a given type (and typename). + +
+
+ + + +
+
+

decodeContentsDescr(string contentsDescr) → string contentsName, string contentsType

+
+

internal

+# +
+
+
+ +Parse the type name out of the ERC-7739 contents type description. Supports both the implicit and explicit +modes. + +Following ERC-7739 specifications, a `contentsName` is considered invalid if it's empty or it contains +any of the following bytes , )\x00 + +If the `contentsType` is invalid, this returns an empty string. Otherwise, the return string has non-zero +length. + +
+
+ + + +
+ +## `AbstractSigner` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/AbstractSigner.sol"; +``` + +Abstract contract for signature validation. + +Developers must implement [`AccountERC7579._rawSignatureValidation`](../account#AccountERC7579-_rawSignatureValidation-bytes32-bytes-) and use it as the lowest-level signature validation mechanism. + +@custom:stateless + +
+

Functions

+
+- [_rawSignatureValidation(hash, signature)](#AbstractSigner-_rawSignatureValidation-bytes32-bytes-) +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Signature validation algorithm. + + +Implementing a signature validation algorithm is a security-sensitive operation as it involves +cryptographic verification. It is important to review and test thoroughly before deployment. Consider using one of the signature verification libraries ([ECDSA](/contracts/5.x/api/utils/cryptography#ECDSA), +[P256](/contracts/5.x/api/utils/cryptography#P256) or [RSA](/contracts/5.x/api/utils/cryptography#RSA)). + + +
+
+ + + +
+ +## `MultiSignerERC7913` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913.sol"; +``` + +Implementation of [`AbstractSigner`](#AbstractSigner) using multiple ERC-7913 signers with a threshold-based +signature verification system. + +This contract allows managing a set of authorized signers and requires a minimum number of +signatures (threshold) to approve operations. It uses ERC-7913 formatted signers, which +makes it natively compatible with ECDSA and ERC-1271 signers. + +Example of usage: + +```solidity +contract MyMultiSignerAccount is Account, MultiSignerERC7913, Initializable { + function initialize(bytes[] memory signers, uint64 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(uint64 threshold) public onlyEntryPointOrSelf { + _setThreshold(threshold); + } +} +``` + + +Failing to properly initialize the signers and threshold either during construction +(if used standalone) or during initialization (if used as a clone) may leave the contract +either front-runnable or unusable. + + +
+

Functions

+
+- [constructor(signers_, threshold_)](#MultiSignerERC7913-constructor-bytes---uint64-) +- [getSigners(start, end)](#MultiSignerERC7913-getSigners-uint64-uint64-) +- [getSignerCount()](#MultiSignerERC7913-getSignerCount--) +- [isSigner(signer)](#MultiSignerERC7913-isSigner-bytes-) +- [threshold()](#MultiSignerERC7913-threshold--) +- [_addSigners(newSigners)](#MultiSignerERC7913-_addSigners-bytes---) +- [_removeSigners(oldSigners)](#MultiSignerERC7913-_removeSigners-bytes---) +- [_setThreshold(newThreshold)](#MultiSignerERC7913-_setThreshold-uint64-) +- [_validateReachableThreshold()](#MultiSignerERC7913-_validateReachableThreshold--) +- [_rawSignatureValidation(hash, signature)](#MultiSignerERC7913-_rawSignatureValidation-bytes32-bytes-) +- [_validateSignatures(hash, signers, signatures)](#MultiSignerERC7913-_validateSignatures-bytes32-bytes---bytes---) +- [_validateThreshold(validatingSigners)](#MultiSignerERC7913-_validateThreshold-bytes---) +#### AbstractSigner [!toc] +
+
+ +
+

Events

+
+- [ERC7913SignerAdded(signers)](#MultiSignerERC7913-ERC7913SignerAdded-bytes-) +- [ERC7913SignerRemoved(signers)](#MultiSignerERC7913-ERC7913SignerRemoved-bytes-) +- [ERC7913ThresholdSet(threshold)](#MultiSignerERC7913-ERC7913ThresholdSet-uint64-) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+- [MultiSignerERC7913AlreadyExists(signer)](#MultiSignerERC7913-MultiSignerERC7913AlreadyExists-bytes-) +- [MultiSignerERC7913NonexistentSigner(signer)](#MultiSignerERC7913-MultiSignerERC7913NonexistentSigner-bytes-) +- [MultiSignerERC7913InvalidSigner(signer)](#MultiSignerERC7913-MultiSignerERC7913InvalidSigner-bytes-) +- [MultiSignerERC7913ZeroThreshold()](#MultiSignerERC7913-MultiSignerERC7913ZeroThreshold--) +- [MultiSignerERC7913UnreachableThreshold(signers, threshold)](#MultiSignerERC7913-MultiSignerERC7913UnreachableThreshold-uint64-uint64-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

constructor(bytes[] signers_, uint64 threshold_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

getSigners(uint64 start, uint64 end) → bytes[]

+
+

public

+# +
+
+
+ +Returns a slice of the set of authorized signers. + +Using `start = 0` and `end = type(uint64).max` will return the entire set of signers. + + +Depending on the `start` and `end`, this operation can copy a large amount of data to memory, which +can be expensive. This is designed for view accessors queried without gas fees. Using it in state-changing +functions may become uncallable if the slice grows too large. + + +
+
+ + + +
+
+

getSignerCount() → uint256

+
+

public

+# +
+
+
+ +Returns the number of authorized signers + +
+
+ + + +
+
+

isSigner(bytes signer) → bool

+
+

public

+# +
+
+
+ +Returns whether the `signer` is an authorized signer. + +
+
+ + + +
+
+

threshold() → uint64

+
+

public

+# +
+
+
+ +Returns the minimum number of signers required to approve a multisignature operation. + +
+
+ + + +
+
+

_addSigners(bytes[] newSigners)

+
+

internal

+# +
+
+
+ +Adds the `newSigners` to those allowed to sign on behalf of this contract. +Internal version without access control. + +Requirements: + +* Each of `newSigners` must be at least 20 bytes long. Reverts with [`MultiSignerERC7913.MultiSignerERC7913InvalidSigner`](#MultiSignerERC7913-MultiSignerERC7913InvalidSigner-bytes-) if not. +* Each of `newSigners` must not be authorized. See [`MultiSignerERC7913.isSigner`](#MultiSignerERC7913-isSigner-bytes-). Reverts with [`MultiSignerERC7913.MultiSignerERC7913AlreadyExists`](#MultiSignerERC7913-MultiSignerERC7913AlreadyExists-bytes-) if so. + +
+
+ + + +
+
+

_removeSigners(bytes[] oldSigners)

+
+

internal

+# +
+
+
+ +Removes the `oldSigners` from the authorized signers. Internal version without access control. + +Requirements: + +* Each of `oldSigners` must be authorized. See [`MultiSignerERC7913.isSigner`](#MultiSignerERC7913-isSigner-bytes-). Otherwise [`MultiSignerERC7913.MultiSignerERC7913NonexistentSigner`](#MultiSignerERC7913-MultiSignerERC7913NonexistentSigner-bytes-) is thrown. +* See [`MultiSignerERC7913._validateReachableThreshold`](#MultiSignerERC7913-_validateReachableThreshold--) for the threshold validation. + +
+
+ + + +
+
+

_setThreshold(uint64 newThreshold)

+
+

internal

+# +
+
+
+ +Sets the signatures `threshold` required to approve a multisignature operation. +Internal version without access control. + +Requirements: + +* See [`MultiSignerERC7913._validateReachableThreshold`](#MultiSignerERC7913-_validateReachableThreshold--) for the threshold validation. + +
+
+ + + +
+
+

_validateReachableThreshold()

+
+

internal

+# +
+
+
+ +Validates the current threshold is reachable. + +Requirements: + +* The [`MultiSignerERC7913.getSignerCount`](#MultiSignerERC7913-getSignerCount--) must be greater or equal than to the [`MultiSignerERC7913.threshold`](#MultiSignerERC7913-threshold--). Throws +[`MultiSignerERC7913.MultiSignerERC7913UnreachableThreshold`](#MultiSignerERC7913-MultiSignerERC7913UnreachableThreshold-uint64-uint64-) if not. + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Decodes, validates the signature and checks the signers are authorized. +See [`MultiSignerERC7913._validateSignatures`](#MultiSignerERC7913-_validateSignatures-bytes32-bytes---bytes---) and [`MultiSignerERC7913._validateThreshold`](#MultiSignerERC7913-_validateThreshold-bytes---) for more details. + +Example of signature encoding: + +```solidity +// Encode signers (verifier || key) +bytes memory signer1 = abi.encodePacked(verifier1, key1); +bytes memory signer2 = abi.encodePacked(verifier2, key2); + +// Order signers by their id +if (keccak256(signer1) > keccak256(signer2)) { + (signer1, signer2) = (signer2, signer1); + (signature1, signature2) = (signature2, signature1); +} + +// Assign ordered signers and signatures +bytes[] memory signers = new bytes[](2); +bytes[] memory signatures = new bytes[](2); +signers[0] = signer1; +signatures[0] = signature1; +signers[1] = signer2; +signatures[1] = signature2; + +// Encode the multi signature +bytes memory signature = abi.encode(signers, signatures); +``` + +Requirements: + +* The `signature` must be encoded as `abi.encode(signers, signatures)`. + +
+
+ + + +
+
+

_validateSignatures(bytes32 hash, bytes[] signers, bytes[] signatures) → bool valid

+
+

internal

+# +
+
+
+ +Validates the signatures using the signers and their corresponding signatures. +Returns whether the signers are authorized and the signatures are valid for the given hash. + + +Sorting the signers by their `keccak256` hash will improve the gas efficiency of this function. +See [`SignatureChecker.areValidSignaturesNow`](#SignatureChecker-areValidSignaturesNow-bytes32-bytes---bytes---) for more details. + + +Requirements: + +* The `signatures` and `signers` arrays must be equal in length. Returns false otherwise. + +
+
+ + + +
+
+

_validateThreshold(bytes[] validatingSigners) → bool

+
+

internal

+# +
+
+
+ +Validates that the number of signers meets the [`MultiSignerERC7913.threshold`](#MultiSignerERC7913-threshold--) requirement. +Assumes the signers were already validated. See [`MultiSignerERC7913._validateSignatures`](#MultiSignerERC7913-_validateSignatures-bytes32-bytes---bytes---) for more details. + +
+
+ + + +
+
+

ERC7913SignerAdded(bytes indexed signers)

+
+

event

+# +
+
+ +
+ +Emitted when a signer is added. + +
+
+ + +
+
+

ERC7913SignerRemoved(bytes indexed signers)

+
+

event

+# +
+
+ +
+ +Emitted when a signers is removed. + +
+
+ + +
+
+

ERC7913ThresholdSet(uint64 threshold)

+
+

event

+# +
+
+ +
+ +Emitted when the threshold is updated. + +
+
+ + + +
+
+

MultiSignerERC7913AlreadyExists(bytes signer)

+
+

error

+# +
+
+
+ +The `signer` already exists. + +
+
+ + + +
+
+

MultiSignerERC7913NonexistentSigner(bytes signer)

+
+

error

+# +
+
+
+ +The `signer` does not exist. + +
+
+ + + +
+
+

MultiSignerERC7913InvalidSigner(bytes signer)

+
+

error

+# +
+
+
+ +The `signer` is less than 20 bytes long. + +
+
+ + + +
+
+

MultiSignerERC7913ZeroThreshold()

+
+

error

+# +
+
+
+ +The `threshold` is zero. + +
+
+ + + +
+
+

MultiSignerERC7913UnreachableThreshold(uint64 signers, uint64 threshold)

+
+

error

+# +
+
+
+ +The `threshold` is unreachable given the number of `signers`. + +
+
+ + + +
+ +## `MultiSignerERC7913Weighted` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol"; +``` + +Extension of [`MultiSignerERC7913`](#MultiSignerERC7913) that supports weighted signatures. + +This contract allows assigning different weights to each signer, enabling more +flexible governance schemes. For example, some signers could have higher weight +than others, allowing for weighted voting or prioritized authorization. + +Example of usage: + +```solidity +contract MyWeightedMultiSignerAccount is Account, MultiSignerERC7913Weighted, Initializable { + function initialize(bytes[] memory signers, uint64[] memory weights, uint64 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(uint64 threshold) public onlyEntryPointOrSelf { + _setThreshold(threshold); + } + + function setSignerWeights(bytes[] memory signers, uint64[] memory weights) public onlyEntryPointOrSelf { + _setSignerWeights(signers, weights); + } +} +``` + + +When setting a threshold value, ensure it 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). See [`MultiSignerERC7913Weighted.signerWeight`](#MultiSignerERC7913Weighted-signerWeight-bytes-). + + +
+

Functions

+
+- [constructor(signers_, weights_, threshold_)](#MultiSignerERC7913Weighted-constructor-bytes---uint64---uint64-) +- [signerWeight(signer)](#MultiSignerERC7913Weighted-signerWeight-bytes-) +- [totalWeight()](#MultiSignerERC7913Weighted-totalWeight--) +- [_setSignerWeights(signers, weights)](#MultiSignerERC7913Weighted-_setSignerWeights-bytes---uint64---) +- [_addSigners(newSigners)](#MultiSignerERC7913Weighted-_addSigners-bytes---) +- [_removeSigners(signers)](#MultiSignerERC7913Weighted-_removeSigners-bytes---) +- [_validateReachableThreshold()](#MultiSignerERC7913Weighted-_validateReachableThreshold--) +- [_validateThreshold(signers)](#MultiSignerERC7913Weighted-_validateThreshold-bytes---) +#### MultiSignerERC7913 [!toc] +- [getSigners(start, end)](#MultiSignerERC7913-getSigners-uint64-uint64-) +- [getSignerCount()](#MultiSignerERC7913-getSignerCount--) +- [isSigner(signer)](#MultiSignerERC7913-isSigner-bytes-) +- [threshold()](#MultiSignerERC7913-threshold--) +- [_setThreshold(newThreshold)](#MultiSignerERC7913-_setThreshold-uint64-) +- [_rawSignatureValidation(hash, signature)](#MultiSignerERC7913-_rawSignatureValidation-bytes32-bytes-) +- [_validateSignatures(hash, signers, signatures)](#MultiSignerERC7913-_validateSignatures-bytes32-bytes---bytes---) +#### AbstractSigner [!toc] +
+
+ +
+

Events

+
+- [ERC7913SignerWeightChanged(signer, weight)](#MultiSignerERC7913Weighted-ERC7913SignerWeightChanged-bytes-uint64-) +#### MultiSignerERC7913 [!toc] +- [ERC7913SignerAdded(signers)](#MultiSignerERC7913-ERC7913SignerAdded-bytes-) +- [ERC7913SignerRemoved(signers)](#MultiSignerERC7913-ERC7913SignerRemoved-bytes-) +- [ERC7913ThresholdSet(threshold)](#MultiSignerERC7913-ERC7913ThresholdSet-uint64-) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+- [MultiSignerERC7913WeightedInvalidWeight(signer, weight)](#MultiSignerERC7913Weighted-MultiSignerERC7913WeightedInvalidWeight-bytes-uint64-) +- [MultiSignerERC7913WeightedMismatchedLength()](#MultiSignerERC7913Weighted-MultiSignerERC7913WeightedMismatchedLength--) +#### MultiSignerERC7913 [!toc] +- [MultiSignerERC7913AlreadyExists(signer)](#MultiSignerERC7913-MultiSignerERC7913AlreadyExists-bytes-) +- [MultiSignerERC7913NonexistentSigner(signer)](#MultiSignerERC7913-MultiSignerERC7913NonexistentSigner-bytes-) +- [MultiSignerERC7913InvalidSigner(signer)](#MultiSignerERC7913-MultiSignerERC7913InvalidSigner-bytes-) +- [MultiSignerERC7913ZeroThreshold()](#MultiSignerERC7913-MultiSignerERC7913ZeroThreshold--) +- [MultiSignerERC7913UnreachableThreshold(signers, threshold)](#MultiSignerERC7913-MultiSignerERC7913UnreachableThreshold-uint64-uint64-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

constructor(bytes[] signers_, uint64[] weights_, uint64 threshold_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

signerWeight(bytes signer) → uint64

+
+

public

+# +
+
+
+ +Gets the weight of a signer. Returns 0 if the signer is not authorized. + +
+
+ + + +
+
+

totalWeight() → uint64

+
+

public

+# +
+
+
+ +Gets the total weight of all signers. + +
+
+ + + +
+
+

_setSignerWeights(bytes[] signers, uint64[] weights)

+
+

internal

+# +
+
+
+ +Sets weights for multiple signers at once. Internal version without access control. + +Requirements: + +* `signers` and `weights` arrays must have the same length. Reverts with [`MultiSignerERC7913Weighted.MultiSignerERC7913WeightedMismatchedLength`](#MultiSignerERC7913Weighted-MultiSignerERC7913WeightedMismatchedLength--) on mismatch. +* Each signer must exist in the set of authorized signers. Otherwise reverts with [`MultiSignerERC7913.MultiSignerERC7913NonexistentSigner`](#MultiSignerERC7913-MultiSignerERC7913NonexistentSigner-bytes-) +* Each weight must be greater than 0. Otherwise reverts with [`MultiSignerERC7913Weighted.MultiSignerERC7913WeightedInvalidWeight`](#MultiSignerERC7913Weighted-MultiSignerERC7913WeightedInvalidWeight-bytes-uint64-) +* See [`MultiSignerERC7913._validateReachableThreshold`](#MultiSignerERC7913-_validateReachableThreshold--) for the threshold validation. + +Emits [`MultiSignerERC7913Weighted.ERC7913SignerWeightChanged`](#MultiSignerERC7913Weighted-ERC7913SignerWeightChanged-bytes-uint64-) for each signer. + +
+
+ + + +
+
+

_addSigners(bytes[] newSigners)

+
+

internal

+# +
+
+
+ +See [`MultiSignerERC7913._addSigners`](#MultiSignerERC7913-_addSigners-bytes---). + +In cases where [`MultiSignerERC7913Weighted.totalWeight`](#MultiSignerERC7913Weighted-totalWeight--) is almost `type(uint64).max` (due to a large `_totalExtraWeight`), adding new +signers could cause the [`MultiSignerERC7913Weighted.totalWeight`](#MultiSignerERC7913Weighted-totalWeight--) computation to overflow. Adding a [`MultiSignerERC7913Weighted.totalWeight`](#MultiSignerERC7913Weighted-totalWeight--) calls after the new +signers are added ensures no such overflow happens. + +
+
+ + + +
+
+

_removeSigners(bytes[] signers)

+
+

internal

+# +
+
+
+ +See [`MultiSignerERC7913._removeSigners`](#MultiSignerERC7913-_removeSigners-bytes---). + +Just like [`MultiSignerERC7913._addSigners`](#MultiSignerERC7913-_addSigners-bytes---), this function does not emit [`MultiSignerERC7913Weighted.ERC7913SignerWeightChanged`](#MultiSignerERC7913Weighted-ERC7913SignerWeightChanged-bytes-uint64-) events. The +[`MultiSignerERC7913.ERC7913SignerRemoved`](#MultiSignerERC7913-ERC7913SignerRemoved-bytes-) event emitted by [`MultiSignerERC7913._removeSigners`](#MultiSignerERC7913-_removeSigners-bytes---) is enough to track weights here. + +
+
+ + + +
+
+

_validateReachableThreshold()

+
+

internal

+# +
+
+
+ +Sets the threshold for the multisignature operation. Internal version without access control. + +Requirements: + +* The [`MultiSignerERC7913Weighted.totalWeight`](#MultiSignerERC7913Weighted-totalWeight--) must be `>=` the [`MultiSignerERC7913.threshold`](#MultiSignerERC7913-threshold--). Otherwise reverts with [`MultiSignerERC7913.MultiSignerERC7913UnreachableThreshold`](#MultiSignerERC7913-MultiSignerERC7913UnreachableThreshold-uint64-uint64-) + + +This function intentionally does not call `super._validateReachableThreshold` because the base implementation +assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple +implementations of this function may exist in the contract, so important side effects may be missed +depending on the linearization order. + + +
+
+ + + +
+
+

_validateThreshold(bytes[] signers) → bool

+
+

internal

+# +
+
+
+ +Validates that the total weight of signers meets the threshold requirement. + + +This function intentionally does not call `super._validateThreshold` because the base implementation +assumes each signer has a weight of 1, which is a subset of this weighted implementation. Consider that multiple +implementations of this function may exist in the contract, so important side effects may be missed +depending on the linearization order. + + +
+
+ + + +
+
+

ERC7913SignerWeightChanged(bytes indexed signer, uint64 weight)

+
+

event

+# +
+
+ +
+ +Emitted when a signer's weight is changed. + + +Not emitted in [`MultiSignerERC7913._addSigners`](#MultiSignerERC7913-_addSigners-bytes---) or [`MultiSignerERC7913._removeSigners`](#MultiSignerERC7913-_removeSigners-bytes---). Indexers must rely on [`MultiSignerERC7913.ERC7913SignerAdded`](#MultiSignerERC7913-ERC7913SignerAdded-bytes-) +and [`MultiSignerERC7913.ERC7913SignerRemoved`](#MultiSignerERC7913-ERC7913SignerRemoved-bytes-) to index a default weight of 1. See [`MultiSignerERC7913Weighted.signerWeight`](#MultiSignerERC7913Weighted-signerWeight-bytes-). + + +
+
+ + + +
+
+

MultiSignerERC7913WeightedInvalidWeight(bytes signer, uint64 weight)

+
+

error

+# +
+
+
+ +Thrown when a signer's weight is invalid. + +
+
+ + + +
+
+

MultiSignerERC7913WeightedMismatchedLength()

+
+

error

+# +
+
+
+ +Thrown when the arrays lengths don't match. See [`MultiSignerERC7913Weighted._setSignerWeights`](#MultiSignerERC7913Weighted-_setSignerWeights-bytes---uint64---). + +
+
+ + + +
+ +## `SignerECDSA` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/SignerECDSA.sol"; +``` + +Implementation of [`AbstractSigner`](#AbstractSigner) using [ECDSA](/contracts/5.x/api/utils/cryptography#ECDSA) signatures. + +For [`Account`](../account#Account) usage, a [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) function is provided to set the [`SignerECDSA.signer`](#SignerECDSA-signer--) address. +Doing so is easier for a factory, who is likely to use initializable clones of this contract. + +Example of usage: + +```solidity +contract MyAccountECDSA is Account, SignerECDSA, Initializable { + function initialize(address signerAddr) public initializer { + _setSigner(signerAddr); + } +} +``` + + +Failing to call [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) either during construction (if used standalone) +or during initialization (if used as a clone) may leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [constructor(signerAddr)](#SignerECDSA-constructor-address-) +- [_setSigner(signerAddr)](#SignerECDSA-_setSigner-address-) +- [signer()](#SignerECDSA-signer--) +- [_rawSignatureValidation(hash, signature)](#SignerECDSA-_rawSignatureValidation-bytes32-bytes-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

constructor(address signerAddr)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_setSigner(address signerAddr)

+
+

internal

+# +
+
+
+ +Sets the signer with the address of the native signer. This function should be called during construction +or through an initializer. + +
+
+ + + +
+
+

signer() → address

+
+

public

+# +
+
+
+ +Return the signer's address. + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Signature validation algorithm. + + +Implementing a signature validation algorithm is a security-sensitive operation as it involves +cryptographic verification. It is important to review and test thoroughly before deployment. Consider using one of the signature verification libraries ([ECDSA](/contracts/5.x/api/utils/cryptography#ECDSA), [P256](/contracts/5.x/api/utils/cryptography#P256) or [RSA](/contracts/5.x/api/utils/cryptography#RSA)). + + +
+
+ + + +
+ +## `SignerEIP7702` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/SignerEIP7702.sol"; +``` + +Implementation of [`AbstractSigner`](#AbstractSigner) for implementation for an EOA. Useful for ERC-7702 accounts. + +@custom:stateless + +
+

Functions

+
+- [_rawSignatureValidation(hash, signature)](#SignerEIP7702-_rawSignatureValidation-bytes32-bytes-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Validates the signature using the EOA's address (i.e. `address(this)`). + +
+
+ + + +
+ +## `SignerERC7913` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/SignerERC7913.sol"; +``` + +Implementation of [`AbstractSigner`](#AbstractSigner) using +[ERC-7913](https://eips.ethereum.org/EIPS/eip-7913) signature verification. + +For [`Account`](../account#Account) usage, a [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) function is provided to set the ERC-7913 formatted [`SignerECDSA.signer`](#SignerECDSA-signer--). +Doing so is easier for a factory, who is likely to use initializable clones of this contract. + +The signer is a `bytes` object that concatenates a verifier address and a key: `verifier || key`. + +Example of usage: + +```solidity +contract MyAccountERC7913 is Account, SignerERC7913, Initializable { + function initialize(bytes memory signer_) public initializer { + _setSigner(signer_); + } + + function setSigner(bytes memory signer_) public onlyEntryPointOrSelf { + _setSigner(signer_); + } +} +``` + + +Failing to call [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) either during construction (if used standalone) +or during initialization (if used as a clone) may leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [constructor(signer_)](#SignerERC7913-constructor-bytes-) +- [signer()](#SignerERC7913-signer--) +- [_setSigner(signer_)](#SignerERC7913-_setSigner-bytes-) +- [_rawSignatureValidation(hash, signature)](#SignerERC7913-_rawSignatureValidation-bytes32-bytes-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

constructor(bytes signer_)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

signer() → bytes

+
+

public

+# +
+
+
+ +Return the ERC-7913 signer (i.e. `verifier || key`). + +
+
+ + + +
+
+

_setSigner(bytes signer_)

+
+

internal

+# +
+
+
+ +Sets the signer (i.e. `verifier || key`) with an ERC-7913 formatted signer. + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Verifies a signature using #SignatureChecker-isValidSignatureNow-bytes-bytes32-bytes- +with [`SignerECDSA.signer`](#SignerECDSA-signer--), `hash` and `signature`. + +
+
+ + + +
+ +## `SignerP256` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/SignerP256.sol"; +``` + +Implementation of [`AbstractSigner`](#AbstractSigner) using [P256](/contracts/5.x/api/utils/cryptography#P256) signatures. + +For [`Account`](../account#Account) usage, a [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) function is provided to set the [`SignerECDSA.signer`](#SignerECDSA-signer--) public key. +Doing so is easier for a factory, who is likely to use initializable clones of this contract. + +Example of usage: + +```solidity +contract MyAccountP256 is Account, SignerP256, Initializable { + function initialize(bytes32 qx, bytes32 qy) public initializer { + _setSigner(qx, qy); + } +} +``` + + +Failing to call [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) either during construction (if used standalone) +or during initialization (if used as a clone) may leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [constructor(qx, qy)](#SignerP256-constructor-bytes32-bytes32-) +- [_setSigner(qx, qy)](#SignerP256-_setSigner-bytes32-bytes32-) +- [signer()](#SignerP256-signer--) +- [_rawSignatureValidation(hash, signature)](#SignerP256-_rawSignatureValidation-bytes32-bytes-) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+- [SignerP256InvalidPublicKey(qx, qy)](#SignerP256-SignerP256InvalidPublicKey-bytes32-bytes32-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

constructor(bytes32 qx, bytes32 qy)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_setSigner(bytes32 qx, bytes32 qy)

+
+

internal

+# +
+
+
+ +Sets the signer with a P256 public key. This function should be called during construction +or through an initializer. + +
+
+ + + +
+
+

signer() → bytes32 qx, bytes32 qy

+
+

public

+# +
+
+
+ +Return the signer's P256 public key. + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Signature validation algorithm. + + +Implementing a signature validation algorithm is a security-sensitive operation as it involves +cryptographic verification. It is important to review and test thoroughly before deployment. Consider using one of the signature verification libraries ([ECDSA](/contracts/5.x/api/utils/cryptography#ECDSA), [P256](/contracts/5.x/api/utils/cryptography#P256) or [RSA](/contracts/5.x/api/utils/cryptography#RSA)). + + +
+
+ + + +
+
+

SignerP256InvalidPublicKey(bytes32 qx, bytes32 qy)

+
+

error

+# +
+
+
+ +
+
+ + + +
+ +## `SignerRSA` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/SignerRSA.sol"; +``` + +Implementation of [`AbstractSigner`](#AbstractSigner) using [RSA](/contracts/5.x/api/utils/cryptography#RSA) signatures. + +For [`Account`](../account#Account) usage, a [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) function is provided to set the [`SignerECDSA.signer`](#SignerECDSA-signer--) public key. +Doing so is easier for a factory, who is likely to use initializable clones of this contract. + +Example of usage: + +```solidity +contract MyAccountRSA is Account, SignerRSA, Initializable { + function initialize(bytes memory e, bytes memory n) public initializer { + _setSigner(e, n); + } +} +``` + + +Failing to call [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) either during construction (if used standalone) +or during initialization (if used as a clone) may leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [constructor(e, n)](#SignerRSA-constructor-bytes-bytes-) +- [_setSigner(e, n)](#SignerRSA-_setSigner-bytes-bytes-) +- [signer()](#SignerRSA-signer--) +- [_rawSignatureValidation(hash, signature)](#SignerRSA-_rawSignatureValidation-bytes32-bytes-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

constructor(bytes e, bytes n)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_setSigner(bytes e, bytes n)

+
+

internal

+# +
+
+
+ +Sets the signer with a RSA public key. This function should be called during construction +or through an initializer. + +
+
+ + + +
+
+

signer() → bytes e, bytes n

+
+

public

+# +
+
+
+ +Return the signer's RSA public key. + +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +See [`AbstractSigner._rawSignatureValidation`](#AbstractSigner-_rawSignatureValidation-bytes32-bytes-). Verifies a PKCSv1.5 signature by calling +[RSA.pkcs1Sha256](/contracts/5.x/api/utils/cryptography#RSA-pkcs1Sha256-bytes-bytes-bytes-bytes-). + + +Following the RSASSA-PKCS1-V1_5-VERIFY procedure outlined in RFC8017 (section 8.2.2), the +provided `hash` is used as the `M` (message) and rehashed using SHA256 according to EMSA-PKCS1-v1_5 +encoding as per section 9.2 (step 1) of the RFC. + + +
+
+ + + +
+ +## `SignerWebAuthn` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/SignerWebAuthn.sol"; +``` + +Implementation of [`SignerP256`](#SignerP256) that supports WebAuthn authentication assertions. + +This contract enables signature validation using WebAuthn authentication assertions, +leveraging the P256 public key stored in the contract. It allows for both WebAuthn +and raw P256 signature validation, providing compatibility with both signature types. + +The signature is expected to be an abi-encoded [`WebAuthn.WebAuthnAuth`](#WebAuthn-WebAuthnAuth) struct. + +Example usage: + +```solidity +contract MyAccountWebAuthn is Account, SignerWebAuthn, Initializable { + function initialize(bytes32 qx, bytes32 qy) public initializer { + _setSigner(qx, qy); + } +} +``` + + +Failing to call [`SignerECDSA._setSigner`](#SignerECDSA-_setSigner-address-) either during construction (if used standalone) +or during initialization (if used as a clone) may leave the signer either front-runnable or unusable. + + +
+

Functions

+
+- [_rawSignatureValidation(hash, signature)](#SignerWebAuthn-_rawSignatureValidation-bytes32-bytes-) +#### SignerP256 [!toc] +- [_setSigner(qx, qy)](#SignerP256-_setSigner-bytes32-bytes32-) +- [signer()](#SignerP256-signer--) +#### AbstractSigner [!toc] +
+
+ +
+

Errors

+
+#### SignerP256 [!toc] +- [SignerP256InvalidPublicKey(qx, qy)](#SignerP256-SignerP256InvalidPublicKey-bytes32-bytes32-) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

_rawSignatureValidation(bytes32 hash, bytes signature) → bool

+
+

internal

+# +
+
+
+ +Validates a raw signature using the WebAuthn authentication assertion. + +In case the signature can't be validated, it falls back to the +[`SignerP256._rawSignatureValidation`](#SignerP256-_rawSignatureValidation-bytes32-bytes-) method for raw P256 signature validation by passing +the raw `r` and `s` values from the signature. + +
+
+ + + +
+ +## `ERC7739` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol"; +``` + +Validates signatures wrapping the message hash in a nested EIP712 type. See [`ERC7739Utils`](#ERC7739Utils). + +Linking the signature to the EIP-712 domain separator is a security measure to prevent signature replay across different +EIP-712 domains (e.g. a single offchain owner of multiple contracts). + +This contract requires implementing the [`AccountERC7579._rawSignatureValidation`](../account#AccountERC7579-_rawSignatureValidation-bytes32-bytes-) function, which passes the wrapped message hash, +which may be either an typed data or a personal sign nested type. + + +[EIP-712](/contracts/5.x/api/utils/cryptography#EIP712) uses [ShortStrings](/contracts/5.x/api/utils/cryptography#ShortStrings) to optimize gas costs for short strings (up to 31 characters). Consider that strings longer than that will use storage, which may limit the ability of the signer to be used within the ERC-4337 validation phase (due to +[ERC-7562 storage access rules](https://eips.ethereum.org/EIPS/eip-7562#storage-rules)). + + +
+

Functions

+
+- [isValidSignature(hash, signature)](#ERC7739-isValidSignature-bytes32-bytes-) +#### IERC1271 [!toc] +#### EIP712 [!toc] +- [_domainSeparatorV4()](#EIP712-_domainSeparatorV4--) +- [_hashTypedDataV4(structHash)](#EIP712-_hashTypedDataV4-bytes32-) +- [eip712Domain()](#EIP712-eip712Domain--) +- [_EIP712Name()](#EIP712-_EIP712Name--) +- [_EIP712Version()](#EIP712-_EIP712Version--) +#### IERC5267 [!toc] +#### AbstractSigner [!toc] +- [_rawSignatureValidation(hash, signature)](#AbstractSigner-_rawSignatureValidation-bytes32-bytes-) +
+
+ +
+

Events

+
+#### IERC1271 [!toc] +#### EIP712 [!toc] +#### IERC5267 [!toc] +- [EIP712DomainChanged()](#IERC5267-EIP712DomainChanged--) +#### AbstractSigner [!toc] +
+
+ + + +
+
+

isValidSignature(bytes32 hash, bytes signature) → bytes4 result

+
+

public

+# +
+
+
+ +Attempts validating the signature in a nested EIP-712 type. + +A nested EIP-712 type might be presented in 2 different ways: + +- As a nested EIP-712 typed data +- As a _personal_ signature (an EIP-712 mimic of the `eth_personalSign` for a smart contract) + +
+
+ + + +
+ +## `ERC7913P256Verifier` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913P256Verifier.sol"; +``` + +ERC-7913 signature verifier that support P256 (secp256r1) keys. + +@custom:stateless + +
+

Functions

+
+- [verify(key, hash, signature)](#ERC7913P256Verifier-verify-bytes-bytes32-bytes-) +#### IERC7913SignatureVerifier [!toc] +
+
+ + + +
+
+

verify(bytes key, bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +Verifies `signature` as a valid signature of `hash` by `key`. + +MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. +SHOULD return 0xffffffff or revert if the signature is not valid. +SHOULD return 0xffffffff or revert if the key is empty + +
+
+ + + +
+ +## `ERC7913RSAVerifier` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913RSAVerifier.sol"; +``` + +ERC-7913 signature verifier that support RSA keys. + +@custom:stateless + +
+

Functions

+
+- [verify(key, hash, signature)](#ERC7913RSAVerifier-verify-bytes-bytes32-bytes-) +#### IERC7913SignatureVerifier [!toc] +
+
+ + + +
+
+

verify(bytes key, bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +Verifies `signature` as a valid signature of `hash` by `key`. + +MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. +SHOULD return 0xffffffff or revert if the signature is not valid. +SHOULD return 0xffffffff or revert if the key is empty + +
+
+ + + +
+ +## `ERC7913WebAuthnVerifier` + + + + + +
+ +```solidity +import "@openzeppelin/contracts/utils/cryptography/verifiers/ERC7913WebAuthnVerifier.sol"; +``` + +ERC-7913 signature verifier that supports WebAuthn authentication assertions. + +This verifier enables the validation of WebAuthn signatures using P256 public keys. +The key is expected to be a 64-byte concatenation of the P256 public key coordinates (qx || qy). +The signature is expected to be an abi-encoded [`WebAuthn.WebAuthnAuth`](#WebAuthn-WebAuthnAuth) struct. + +Uses `WebAuthn-verifyMinimal` for signature verification, which performs the essential +WebAuthn checks: type validation, challenge matching, and cryptographic signature verification. + + +Wallets that may require default P256 validation may install a P256 verifier separately. + + +@custom:stateless + +
+

Functions

+
+- [verify(key, hash, signature)](#ERC7913WebAuthnVerifier-verify-bytes-bytes32-bytes-) +#### IERC7913SignatureVerifier [!toc] +
+
+ + + +
+
+

verify(bytes key, bytes32 hash, bytes signature) → bytes4

+
+

public

+# +
+
+
+ +Verifies `signature` as a valid signature of `hash` by `key`. + +MUST return the bytes4 magic value IERC7913SignatureVerifier.verify.selector if the signature is valid. +SHOULD return 0xffffffff or revert if the signature is not valid. +SHOULD return 0xffffffff or revert if the key is empty + +
+
diff --git a/docs/content/contracts/5.x/backwards-compatibility.mdx b/docs/content/contracts/5.x/backwards-compatibility.mdx new file mode 100644 index 00000000..719d6c2a --- /dev/null +++ b/docs/content/contracts/5.x/backwards-compatibility.mdx @@ -0,0 +1,51 @@ +--- +title: Backwards Compatibility +--- + +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 [Extending Contracts](./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 [OpenZeppelin Upgrades Plugins or CLI](/upgrades-plugins) 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. diff --git a/docs/content/contracts/5.x/changelog.mdx b/docs/content/contracts/5.x/changelog.mdx new file mode 100644 index 00000000..c8494289 --- /dev/null +++ b/docs/content/contracts/5.x/changelog.mdx @@ -0,0 +1,2060 @@ +--- +title: Changelog +--- + + +# [v5.4.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.4.0) - 2025-07-17 + +### Breaking changes + +- Update minimum pragma to 0.8.24 in `SignatureChecker`, `Governor` and Governor's extensions. ([#5716](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5716)). + +### Pragma changes + +- Reduced pragma requirement of interface files + +### Changes by category + +#### Account + +- `Account`: Added a simple ERC-4337 account implementation with minimal logic to process user operations. ([#5657](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5657)) +- `AccountERC7579`: Extension of `Account` that implements support for ERC-7579 modules of type executor, validator, and fallback handler. ([#5657](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5657)) +- `AccountERC7579Hooked`: Extension of `AccountERC7579` that implements support for ERC-7579 hook modules. ([#5657](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5657)) +- `EIP7702Utils`: Add a library for checking if an address has an EIP-7702 delegation in place. ([#5587](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5587)) +- `IERC7821`, `ERC7821`: Interface and logic for minimal batch execution. No support for additional `opData` is included. ([#5657](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5657)) + +#### Governance + +- `GovernorNoncesKeyed`: Extension of `Governor` that adds support for keyed nonces when voting by sig. ([#5574](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5574)) + +#### Tokens + +- `ERC20Bridgeable`: Implementation of ERC-7802 that makes an ERC-20 compatible with crosschain bridges. ([#5739](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5739)) + +#### Cryptography + +##### Signers + +- `AbstractSigner`, `SignerECDSA`, `SignerP256`, and `SignerRSA`: Add an abstract contract and various implementations for contracts that deal with signature verification. ([#5657](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5657)) +- `SignerERC7702`: Implementation of `AbstractSigner` for Externally Owned Accounts (EOAs). Useful with ERC-7702. ([#5657](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5657)) +- `SignerERC7913`: Abstract signer that verifies signatures using the ERC-7913 workflow. ([#5659](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5659)) +- `MultiSignerERC7913`: Implementation of `AbstractSigner` that supports multiple ERC-7913 signers with a threshold-based signature verification system. ([#5659](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5659)) +- `MultiSignerERC7913Weighted`: Extension of `MultiSignerERC7913` that supports assigning different weights to each signer, enabling more flexible governance schemes. ([#5741](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5741)) + +##### Verifiers + +- `ERC7913P256Verifier` and `ERC7913RSAVerifier`: Ready to use ERC-7913 verifiers that implement key verification for P256 (secp256r1) and RSA keys. ([#5659](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5659)) + +##### Other + +- `SignatureChecker`: Add support for ERC-7913 signatures alongside existing ECDSA and ERC-1271 signature verification. ([#5659](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5659)) +- `ERC7739`: An abstract contract to validate signatures following the rehashing scheme from `ERC7739Utils`. ([#5664](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5664)) +- `ERC7739Utils`: Add a library that implements a defensive rehashing mechanism to prevent replayability of smart contract signatures based on the ERC-7739. ([#5664](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5664)) + +#### Structures + +- `EnumerableMap`: Add support for `BytesToBytesMap` type. ([#5658](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5658)) +- `EnumerableMap`: Add `keys(uint256,uint256)` that returns a subset (slice) of the keys in the map. ([#5713](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5713)) +- `EnumerableSet`: Add support for `StringSet` and `BytesSet` types. ([#5658](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5658)) +- `EnumerableSet`: Add `values(uint256,uint256)` that returns a subset (slice) of the values in the set. ([#5713](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5713)) + +#### Utils + +- `Arrays`: Add `unsafeAccess`, `unsafeMemoryAccess` and `unsafeSetLength` for `bytes[]` and `string[]`. ([#5568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5568)) +- `Blockhash`: Add a library that provides access to historical block hashes using EIP-2935's history storage, extending the standard 256-block limit to 8191 blocks. ([#5642](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5642)) +- `Bytes`: Fix `lastIndexOf(bytes,byte,uint256)` with empty buffers and finite position to correctly return `type(uint256).max` instead of accessing uninitialized memory sections. ([#5797](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5797)) + + + +[Changes][v5.4.0] + + + +# [v5.3.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.3.0) - 2025-04-09 + +### Breaking Changes + +- Replace `GovernorCountingOverridable.VoteReceipt` struct parameter member names `hasOverriden` and `overridenWeight` for `hasOverridden` and `overriddenWeight` respectively. + +#### Custom error changes + +- Replace `GovernorAlreadyOverridenVote` with `GovernorAlreadyOverriddenVote`. +- Replace `GovernorOnlyProposer` with `GovernorUnableToCancel`. + +### Changes by category + +#### Account + +- `ERC4337Utils`: Update the `hash` function to call `getUserOpHash` on the specified entrypoint and add an `ENTRYPOINT_V08` constant. ([#5614](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5614)) +- `ERC7579Utils`: Add ABI decoding checks on calldata bounds within `decodeBatch`. ([#5371](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5371)) +- `ERC7579Utils`: Replace `address(0)` with `address(this)` during execution for calldata compression efficiency. ([#5614](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5614)) + +#### Governance + +- `IGovernor`: Add the `getProposalId` function to the governor interface. ([#5290](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5290)) +- `GovernorProposalGuardian`: Add a governance extension that defines a proposal guardian who can cancel proposals at any stage in their lifecycle. ([#5303](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5303)) +- `GovernorSequentialProposalId`: Adds a `Governor` extension that sequentially numbers proposal ids instead of using the hash. ([#5290](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5290)) +- `GovernorSuperQuorum`: Add a governance extension to support a super quorum. Proposals that meet the super quorum (and have a majority of for votes) advance to the `Succeeded` state before the proposal deadline. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) +- `GovernorVotesSuperQuorumFraction`: Add a variant of the `GovernorSuperQuorum` extensions where the super quorum is expressed as a fraction of the total supply. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) +- `TimelockController`: Receive function is now virtual. ([#5509](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5509)) + +#### Structures + +- `EnumerableSet`: Add `clear` function to EnumerableSets which deletes all values in the set. ([#5486](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5486)) +- `EnumerableMap`: Add `clear` function to EnumerableMaps which deletes all entries in the map. ([#5486](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5486)) +- `MerkleTree`: Add an update function that replaces a previously inserted leaf with a new value, updating the tree root along the way. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) + +#### Tokens + +- `ERC4626`: Use the `asset` getter in `totalAssets`, `_deposit` and `_withdraw`. ([#5322](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5322)) +- `IERC6909`: Add the interface for ERC-6909. ([#5343](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5343)) +- `ERC6909`: Add a standard implementation of ERC6909. ([#5394](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5394)) +- `ERC6909TokenSupply`: Add an extension of ERC6909 which tracks total supply for each token id. ([#5394](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5394)) +- `ERC6909Metadata`: Add an extension of ERC6909 which adds metadata functionality. ([#5394](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5394)) +- `ERC6909ContentURI`: Add an extension of ERC6909 which adds content URI functionality. ([#5394](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5394)) +- `SafeERC20`: Add `trySafeTransfer` and `trySafeTransferFrom` that do not revert and return false if the transfer is not successful. ([#5483](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5483)) + +#### Other + +- `Address`: bubble up revert data on `sendValue` failed call. ([#5379](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5379)) +- `Calldata`: Library with `emptyBytes` and `emptyString` functions to generate empty `bytes` and `string` calldata types. ([#5422](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5422)) +- `ERC2771Forwarder`: Expose the `_isTrustedByTarget` internal function to check whether a target trusts the forwarder. ([#5416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5416)) +- `Hashes`: Expose `efficientKeccak256` for hashing non-commutative pairs of bytes32 without allocating extra memory. ([#5442](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5442)) +- `Initializable`: Add `_initializableStorageSlot` function that returns a pointer to the storage struct. The function allows customizing with a custom storage slot with an `override`. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) +- `Math`: Add `add512`, `mul512` and `mulShr`. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) +- `Math`: Add saturating arithmetic operations `saturatingAdd`, `saturatingSub` and `saturatingMul`. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) +- `MessageHashUtils`: Add `toDataWithIntendedValidatorHash(address, bytes32)`. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) +- `P256`: Adjust precompile detection in `verifyNative` to consider empty `returndata` on invalid verification. Previously, invalid signatures would've reverted with a `MissingPrecompile` error in chains with RIP-7212 support. ([#5620](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5620)) +- `Pausable`: Stop explicitly setting `paused` to `false` during construction. ([#5448](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5448)) +- `Strings`: Add `espaceJSON` that escapes special characters in JSON strings. ([#5526](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5526)) + + + +[Changes][v5.3.0] + + + +# [v5.2.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.2.0) - 2025-01-09 + +### Breaking Changes + +#### Custom error changes + +This version comes with changes to the custom error identifiers. Contracts previously depending on the following errors should be replaced accordingly: + +- Replace `Errors.FailedCall` with a bubbled-up revert reason in `Address.sendValue`. + +### Changes by category + +#### General + +- Update some pragma directives to ensure that all file requirements match that of the files they import. ([#5273](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5273)) + +#### Account + +- `ERC4337Utils`: Add a reusable library to manipulate user operations and interact with ERC-4337 contracts ([#5274](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5274)) +- `ERC7579Utils`: Add a reusable library to interact with ERC-7579 modular accounts ([#5274](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5274)) + +#### Governance + +- `GovernorCountingOverridable`: Add a governor counting module that enables token holders to override the vote of their delegate. ([#5192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5192)) +- `VotesExtended`: Create an extension of `Votes` which checkpoints balances and delegates. ([#5192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5192)) + +### Proxy + +- `Clones`: Add `cloneWithImmutableArgs` and `cloneDeterministicWithImmutableArgs` variants that create clones with per-instance immutable arguments. The immutable arguments can be retrieved using `fetchCloneArgs`. The corresponding `predictDeterministicWithImmutableArgs` function is also included. ([#5109](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5109)) + +### Tokens + +- `ERC1363Utils`: Add helper similar to the existing `ERC721Utils` and `ERC1155Utils` ([#5133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5133)) + +### Utils + +- `Address`: bubble up revert data on `sendValue` failed call ([#5418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5418)) +- `Bytes`: Add a library of common operations that operate on `bytes` objects. ([#5252](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5252)) +- `CAIP2` and `CAIP10`: Add libraries for formatting and parsing CAIP-2 and CAIP-10 identifiers. ([#5252](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5252)) +- `NoncesKeyed`: Add a variant of `Nonces` that implements the ERC-4337 entrypoint nonce system. ([#5272](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5272)) +- `Packing`: Add variants for packing `bytes10` and `bytes22` ([#5274](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5274)) +- `Strings`: Add `parseUint`, `parseInt`, `parseHexUint` and `parseAddress` to parse strings into numbers and addresses. Also provide variants of these functions that parse substrings, and `tryXxx` variants that do not revert on invalid input. ([#5166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5166)) + + + +[Changes][v5.2.0] + + + +# [v5.1.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.1.0) - 2024-10-23 + +### Breaking changes + +- `ERC1967Utils`: Removed duplicate declaration of the `Upgraded`, `AdminChanged` and `BeaconUpgraded` events. These events are still available through the `IERC1967` interface located under the `contracts/interfaces/` directory. Minimum pragma version is now 0.8.21. +- `Governor`, `GovernorCountingSimple`: The `_countVote` virtual function now returns an `uint256` with the total votes casted. This change allows for more flexibility for partial and fractional voting. Upgrading users may get a compilation error that can be fixed by adding a return statement to the `_countVote` function. + +#### Custom error changes + +This version comes with changes to the custom error identifiers. Contracts previously depending on the following errors should be replaced accordingly: + +- Replace `Address.FailedInnerCall` with `Errors.FailedCall` +- Replace `Address.AddressInsufficientBalance` with `Errors.InsufficientBalance` +- Replace `Clones.Create2InsufficientBalance` with `Errors.InsufficientBalance` +- Replace `Clones.ERC1167FailedCreateClone` with `Errors.FailedDeployment` +- Replace `Clones.Create2FailedDeployment` with `Errors.FailedDeployment` +- `SafeERC20`: Replace `Address.AddressEmptyCode` with `SafeERC20FailedOperation` if there is no code at the token's address. +- `SafeERC20`: Replace generic `Error(string)` with `SafeERC20FailedOperation` if the returned data can't be decoded as `bool`. +- `SafeERC20`: Replace generic `SafeERC20FailedOperation` with the revert message from the contract call if it fails. + +### Changes by category + +#### General + +- `AccessManager`, `VestingWallet`, `TimelockController` and `ERC2771Forwarder`: Added a public `initializer` function in their corresponding upgradeable variants. ([#5008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5008)) + +#### Access + +- `AccessControlEnumerable`: Add a `getRoleMembers` method to return all accounts that have `role`. ([#4546](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4546)) +- `AccessManager`: Allow the `onlyAuthorized` modifier to restrict functions added to the manager. ([#5014](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5014)) + +#### Finance + +- `VestingWalletCliff`: Add an extension of the `VestingWallet` contract with an added cliff. ([#4870](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4870)) + +#### Governance + +- `GovernorCountingFractional`: Add a governor counting module that allows distributing voting power amongst 3 options (For, Against, Abstain). ([#5045](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5045)) +- `Votes`: Set `_moveDelegateVotes` visibility to internal instead of private. ([#5007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5007)) + +#### Proxy + +- `Clones`: Add version of `clone` and `cloneDeterministic` that support sending value at creation. ([#4936](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4936)) +- `TransparentUpgradeableProxy`: Make internal `_proxyAdmin()` getter have `view` visibility. ([#4688](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4688)) +- `ProxyAdmin`: Fixed documentation for `UPGRADE_INTERFACE_VERSION` getter. ([#5031](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5031)) + +#### Tokens + +- `ERC1363`: Add implementation of the token payable standard allowing execution of contract code after transfers and approvals. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) +- `ERC20TemporaryApproval`: Add an ERC-20 extension that implements temporary approval using transient storage, based on ERC7674 (draft). ([#5071](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5071)) +- `SafeERC20`: Add "relaxed" function for interacting with ERC-1363 functions in a way that is compatible with EOAs. ([#4631](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4631)) +- `SafeERC20`: Document risks of `safeIncreaseAllowance` and `safeDecreaseAllowance` when associated with ERC-7674. ([#5262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5262)) +- `ERC721Utils` and `ERC1155Utils`: Add reusable libraries with functions to perform acceptance checks on `IERC721Receiver` and `IERC1155Receiver` implementers. ([#4845](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4845)) +- `ERC1363Utils`: Add helper similar to the existing ERC721Utils and ERC1155Utils. ([#5133](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5133)) + +#### Utils + +- `Arrays`: add a `sort` functions for `address[]`, `bytes32[]` and `uint256[]` memory arrays. ([#4846](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4846)) +- `Arrays`: add new functions `lowerBound`, `upperBound`, `lowerBoundMemory` and `upperBoundMemory` for lookups in sorted arrays with potential duplicates. ([#4842](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4842)) +- `Arrays`: deprecate `findUpperBound` in favor of the new `lowerBound`. ([#4842](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4842)) +- `Base64`: Add `encodeURL` following section 5 of RFC4648 for URL encoding ([#4822](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4822)) +- `Comparator`: A library of comparator functions, useful for customizing the behavior of the Heap structure. ([#5084](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5084)) +- `Create2`: Bubbles up returndata from a deployed contract that reverted during construction. ([#5052](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5052)) +- `Create2`, `Clones`: Mask `computeAddress` and `cloneDeterministic` outputs to produce a clean value for an `address` type (i.e. only use 20 bytes) ([#4941](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4941)) +- `Errors`: New library of common custom errors. ([#4936](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4936)) +- `Hashes`: A library with commonly used hash functions. ([#3617](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3617)) +- `Packing`: Added a new utility for packing, extracting and replacing bytesXX values. ([#4992](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4992)) +- `Panic`: Add a library for reverting with panic codes. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `ReentrancyGuardTransient`: Added a variant of `ReentrancyGuard` that uses transient storage. ([#4988](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4988)) +- `Strings`: Added a utility function for converting an address to checksummed string. ([#5067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5067)) +- `SlotDerivation`: Add a library of methods for derivating common storage slots. ([#4975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4975)) +- `TransientSlot`: Add primitives for operating on the transient storage space using a typed-slot representation. ([#4980](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4980)) + +##### Cryptography + +- `SignatureChecker`: refactor `isValidSignatureNow` to avoid validating ECDSA signatures if there is code deployed at the signer's address. ([#4951](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4951)) +- `MerkleProof`: Add variations of `verify`, `processProof`, `multiProofVerify` and `processMultiProof` (and equivalent calldata version) with support for custom hashing functions. ([#4887](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4887)) +- `P256`: Library for verification and public key recovery of P256 (aka secp256r1) signatures. ([#4881](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4881)) +- `RSA`: Library to verify signatures according to RFC 8017 Signature Verification Operation ([#4952](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4952)) + +#### Math + +- `Math`: add an `invMod` function to get the modular multiplicative inverse of a number in Z/nZ. ([#4839](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4839)) +- `Math`: Add `modExp` function that exposes the `EIP-198` precompile. Includes `uint256` and `bytes memory` versions. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `Math`: Custom errors replaced with native panic codes. ([#3298](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3298)) +- `Math`, `SignedMath`: Add a branchless `ternary` function that computes`cond ? a : b` in constant gas cost. ([#4976](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4976)) +- `SafeCast`: Add `toUint(bool)` for operating on `bool` values as `uint256`. ([#4878](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4878)) + +#### Structures + +- `CircularBuffer`: Add a data structure that stores the last `N` values pushed to it. ([#4913](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4913)) +- `DoubleEndedQueue`: Custom errors replaced with native panic codes. ([#4872](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4872)) +- `EnumerableMap`: add `UintToBytes32Map`, `AddressToAddressMap`, `AddressToBytes32Map` and `Bytes32ToAddressMap`. ([#4843](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4843)) +- `Heap`: A data structure that implements a heap-based priority queue. ([#5084](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/5084)) +- `MerkleTree`: A data structure that allows inserting elements into a merkle tree and updating its root hash. ([#3617](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3617)) + + + +[Changes][v5.1.0] + + + +# [v5.0.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.2) - 2024-02-29 + +- `Base64`: Fix issue where dirty memory located just after the input buffer is affecting the result. ([#4926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4926)) + + + +[Changes][v5.0.2] + + + +# [v4.9.6](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.6) - 2024-02-29 + +- `Base64`: Fix issue where dirty memory located just after the input buffer is affecting the result. ([#4929](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4929)) + + + +[Changes][v4.9.6] + + + +# [v4.9.5](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.5) - 2023-12-08 + +- `Multicall`: Make aware of non-canonical context (i.e. `msg.sender` is not `_msgSender()`), allowing compatibility with `ERC2771Context`. Patch duplicated `Address.functionDelegateCall` in v4.9.4 (removed). + +[Changes][v4.9.5] + + + +# [v5.0.1](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.1) - 2023-12-07 + +- `ERC2771Context` and `Context`: Introduce a `_contextPrefixLength()` getter, used to trim extra information appended to `msg.data`. +- `Multicall`: Make aware of non-canonical context (i.e. `msg.sender` is not `_msgSender()`), allowing compatibility with `ERC2771Context`. + +[Changes][v5.0.1] + + + +# [v4.9.4](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.4) - 2023-12-07 + +- `ERC2771Context` and `Context`: Introduce a `_contextPrefixLength()` getter, used to trim extra information appended to `msg.data`. +- `Multicall`: Make aware of non-canonical context (i.e. `msg.sender` is not `_msgSender()`), allowing compatibility with `ERC2771Context`. + +[Changes][v4.9.4] + + + +# [v5.0.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v5.0.0) - 2023-10-05 + +### Additions Summary + +The following contracts and libraries were added: + +- `AccessManager`: A consolidated system for managing access control in complex systems. + - `AccessManaged`: A module for connecting a contract to an authority in charge of its access control. + - `GovernorTimelockAccess`: An adapter for time-locking governance proposals using an `AccessManager`. + - `AuthorityUtils`: A library of utilities for interacting with authority contracts. +- `GovernorStorage`: A Governor module that stores proposal details in storage. +- `ERC2771Forwarder`: An ERC2771 forwarder for meta transactions. +- `ERC1967Utils`: A library with ERC1967 events, errors and getters. +- `Nonces`: An abstraction for managing account nonces. +- `MessageHashUtils`: A library for producing digests for ECDSA operations. +- `Time`: A library with helpers for manipulating time-related objects. + +### Removals Summary + +The following contracts, libraries, and functions were removed: + +- `Address.isContract` (because of its ambiguous nature and potential for misuse) +- `Checkpoints.History` +- `Counters` +- `ERC20Snapshot` +- `ERC20VotesComp` +- `ERC165Storage` (in favor of inheritance based approach) +- `ERC777` +- `ERC1820Implementer` +- `GovernorVotesComp` +- `GovernorProposalThreshold` (deprecated since 4.4) +- `PaymentSplitter` +- `PullPayment` +- `SafeMath` +- `SignedSafeMath` +- `Timers` +- `TokenTimelock` (in favor of `VestingWallet`) +- All escrow contracts (`Escrow`, `ConditionalEscrow` and `RefundEscrow`) +- All cross-chain contracts, including `AccessControlCrossChain` and all the vendored bridge interfaces +- All presets in favor of [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/) + +These removals were implemented in the following PRs: [#3637](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3637), [#3880](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3880), [#3945](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3945), [#4258](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4258), [#4276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4276), [#4289](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4289) + +### Changes by category + +#### General + +- Replaced revert strings and require statements with custom errors. ([#4261](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4261)) +- Bumped minimum compiler version required to 0.8.20 ([#4288](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4288)) +- Use of `abi.encodeCall` in place of `abi.encodeWithSelector` and `abi.encodeWithSignature` for improved type-checking of parameters ([#4293](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4293)) +- Replaced some uses of `abi.encodePacked` with clearer alternatives (e.g. `bytes.concat`, `string.concat`). ([#4504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4504)) ([#4296](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4296)) +- Overrides are now used internally for a number of functions that were previously hardcoded to their default implementation in certain locations: `ERC1155Supply.totalSupply`, `ERC721.ownerOf`, `ERC721.balanceOf` and `ERC721.totalSupply` in `ERC721Enumerable`, `ERC20.totalSupply` in `ERC20FlashMint`, and `ERC1967._getImplementation` in `ERC1967Proxy`. ([#4299](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4299)) +- Removed the `override` specifier from functions that only override a single interface function. ([#4315](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4315)) +- Switched to using explicit Solidity import statements. Some previously available symbols may now have to be separately imported. ([#4399](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4399)) +- `Governor`, `Initializable`, and `UUPSUpgradeable`: Use internal functions in modifiers to optimize bytecode size. ([#4472](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4472)) +- Upgradeable contracts now use namespaced storage (EIP-7201). ([#4534](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4534)) +- Upgradeable contracts no longer transpile interfaces and libraries. ([#4628](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4628)) + +#### Access + +- `Ownable`: Added an `initialOwner` parameter to the constructor, making the ownership initialization explicit. ([#4267](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4267)) +- `Ownable`: Prevent using address(0) as the initial owner. ([#4531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4531)) +- `AccessControl`: Added a boolean return value to the internal `_grantRole` and `_revokeRole` functions indicating whether the role was granted or revoked. ([#4241](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4241)) +- `access`: Moved `AccessControl` extensions to a dedicated directory. ([#4359](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4359)) +- `AccessManager`: Added a new contract for managing access control of complex systems in a consolidated location. ([#4121](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4121)) +- `AccessManager`, `AccessManaged`, `GovernorTimelockAccess`: Ensure that calldata shorter than 4 bytes is not padded to 4 bytes. ([#4624](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4624)) +- `AccessManager`: Use named return parameters in functions that return multiple values. ([#4624](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4624)) +- `AccessManager`: Make `schedule` and `execute` more conservative when delay is 0. ([#4644](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4644)) + +#### Finance + +- `VestingWallet`: Fixed revert during 1 second time window when duration is 0. ([#4502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4502)) +- `VestingWallet`: Use `Ownable` instead of an immutable `beneficiary`. ([#4508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4508)) + +#### Governance + +- `Governor`: Optimized use of storage for proposal data ([#4268](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4268)) +- `Governor`: Added validation in ERC1155 and ERC721 receiver hooks to ensure Governor is the executor. ([#4314](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4314)) +- `Governor`: Refactored internals to implement common queuing logic in the core module of the Governor. Added `queue` and `_queueOperations` functions that act at different levels. Modules that implement queuing via timelocks are expected to override `_queueOperations` to implement the timelock-specific logic. Added `_executeOperations` as the equivalent for execution. ([#4360](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4360)) +- `Governor`: Added `voter` and `nonce` parameters in signed ballots, to avoid forging signatures for random addresses, prevent signature replay, and allow invalidating signatures. Add `voter` as a new parameter in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. ([#4378](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4378)) +- `Governor`: Added support for casting votes with ERC-1271 signatures by using a `bytes memory signature` instead of `r`, `s` and `v` arguments in the `castVoteBySig` and `castVoteWithReasonAndParamsBySig` functions. ([#4418](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4418)) +- `Governor`: Added a mechanism to restrict the address of the proposer using a suffix in the description. +- `GovernorStorage`: Added a new governor extension that stores the proposal details in storage, with an interface that operates on `proposalId`, as well as proposal enumerability. This replaces the old `GovernorCompatibilityBravo` module. ([#4360](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4360)) +- `GovernorTimelockAccess`: Added a module to connect a governor with an instance of `AccessManager`, allowing the governor to make calls that are delay-restricted by the manager using the normal `queue` workflow. ([#4523](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4523)) +- `GovernorTimelockControl`: Clean up timelock id on execution for gas refund. ([#4118](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4118)) +- `GovernorTimelockControl`: Added the Governor instance address as part of the TimelockController operation `salt` to avoid operation id collisions between governors using the same TimelockController. ([#4432](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4432)) +- `TimelockController`: Changed the role architecture to use `DEFAULT_ADMIN_ROLE` as the admin for all roles, instead of the bespoke `TIMELOCK_ADMIN_ROLE` that was used previously. This aligns with the general recommendation for `AccessControl` and makes the addition of new roles easier. Accordingly, the `admin` parameter and timelock will now be granted `DEFAULT_ADMIN_ROLE` instead of `TIMELOCK_ADMIN_ROLE`. ([#3799](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3799)) +- `TimelockController`: Added a state getter that returns an `OperationState` enum. ([#4358](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4358)) +- `Votes`: Use Trace208 for checkpoints. This enables EIP-6372 clock support for keys but reduces the max supported voting power to uint208. ([#4539](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4539)) + +#### Metatx + +- `ERC2771Forwarder`: Added `deadline` for expiring transactions, batching, and more secure handling of `msg.value`. ([#4346](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4346)) +- `ERC2771Context`: Return the forwarder address whenever the `msg.data` of a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes), as specified by ERC-2771. ([#4481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4481)) +- `ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case. ([#4484](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4484)) + +#### Proxy + +- `ProxyAdmin`: Removed `getProxyAdmin` and `getProxyImplementation` getters. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) +- `TransparentUpgradeableProxy`: Removed `admin` and `implementation` getters, which were only callable by the proxy owner and thus not very useful. ([#3820](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3820)) +- `ERC1967Utils`: Refactored the `ERC1967Upgrade` abstract contract as a library. ([#4325](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4325)) +- `TransparentUpgradeableProxy`: Admin is now stored in an immutable variable (set during construction) to avoid unnecessary storage reads on every proxy call. This removed the ability to ever change the admin. Transfer of the upgrade capability is exclusively handled through the ownership of the `ProxyAdmin`. ([#4354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4354)) +- Moved the logic to validate ERC-1822 during an upgrade from `ERC1967Utils` to `UUPSUpgradeable`. ([#4356](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4356)) +- `UUPSUpgradeable`, `TransparentUpgradeableProxy` and `ProxyAdmin`: Removed `upgradeTo` and `upgrade` functions, and made `upgradeToAndCall` and `upgradeAndCall` ignore the data argument if it is empty. It is no longer possible to invoke the receive function (or send value with empty data) along with an upgrade. ([#4382](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4382)) +- `BeaconProxy`: Reject value in initialization unless a payable function is explicitly invoked. ([#4382](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4382)) +- `Proxy`: Removed redundant `receive` function. ([#4434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4434)) +- `BeaconProxy`: Use an immutable variable to store the address of the beacon. It is no longer possible for a `BeaconProxy` to upgrade by changing to another beacon. ([#4435](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4435)) +- `Initializable`: Use the namespaced storage pattern to avoid putting critical variables in slot 0. Allow reinitializer versions greater than 256. ([#4460](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4460)) +- `Initializable`: Use intermediate variables to improve readability. ([#4576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4576)) + +#### Token + +- `ERC20`, `ERC721`, `ERC1155`: Deleted `_beforeTokenTransfer` and `_afterTokenTransfer` hooks, added a new internal `_update` function for customizations, and refactored all extensions using those hooks to use `_update` instead. ([#3838](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3838), [#3876](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3876), [#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) +- `ERC20`: Removed `Approval` event previously emitted in `transferFrom` to indicate that part of the allowance was consumed. With this change, allowances are no longer reconstructible from events. See the code for guidelines on how to re-enable this event if needed. ([#4370](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4370)) +- `ERC20`: Removed the non-standard `increaseAllowance` and `decreaseAllowance` functions. ([#4585](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4585)) +- `ERC20Votes`: Changed internal vote accounting to reusable `Votes` module previously used by `ERC721Votes`. Removed implicit `ERC20Permit` inheritance. Note that the `DOMAIN_SEPARATOR` getter was previously guaranteed to be available for `ERC20Votes` contracts, but is no longer available unless `ERC20Permit` is explicitly used; ERC-5267 support is included in `ERC20Votes` with `EIP712` and is recommended as an alternative. ([#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816)) +- `SafeERC20`: Refactored `safeDecreaseAllowance` and `safeIncreaseAllowance` to support USDT-like tokens. ([#4260](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4260)) +- `SafeERC20`: Removed `safePermit` in favor of documentation-only `permit` recommendations. Based on recommendations from [@trust1995](https://github.com/trust1995) ([#4582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4582)) +- `ERC721`: `_approve` no longer allows approving the owner of the tokenId. ([#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/4377)) `_setApprovalForAll` no longer allows setting address(0) as an operator. ([#4377](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4377)) +- `ERC721`: Renamed `_requireMinted` to `_requireOwned` and added a return value with the current owner. Implemented `ownerOf` in terms of `_requireOwned`. ([#4566](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4566)) +- `ERC721Consecutive`: Added a `_firstConsecutiveId` internal function that can be overridden to change the id of the first token minted through `_mintConsecutive`. ([#4097](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4097)) +- `ERC721URIStorage`: Allow setting the token URI prior to minting. ([#4559](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4559)) +- `ERC721URIStorage`, `ERC721Royalty`: Stop resetting token-specific URI and royalties when burning. ([#4561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4561)) +- `ERC1155`: Optimized array allocation. ([#4196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4196)) +- `ERC1155`: Removed check for address zero in `balanceOf`. ([#4263](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4263)) +- `ERC1155`: Optimized array accesses by skipping bounds checking when unnecessary. ([#4300](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4300)) +- `ERC1155`: Bubble errors triggered in the `onERC1155Received` and `onERC1155BatchReceived` hooks. ([#4314](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4314)) +- `ERC1155Supply`: Added a `totalSupply()` function that returns the total amount of token circulating, this change will restrict the total tokens minted across all ids to 2**256-1 . ([#3962](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3962)) +- `ERC1155Receiver`: Removed in favor of `ERC1155Holder`. ([#4450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4450)) + +#### Utils + +- `Address`: Removed the ability to customize error messages. A common custom error is always used if the underlying revert reason cannot be bubbled up. ([#4502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4502)) +- `Arrays`: Added `unsafeMemoryAccess` helpers to read from a memory array without checking the length. ([#4300](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4300)) +- `Arrays`: Optimized `findUpperBound` by removing redundant SLOAD. ([#4442](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4442)) +- `Checkpoints`: Library moved from `utils` to `utils/structs` ([#4275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4275)) +- `DoubleEndedQueue`: Refactored internal structure to use `uint128` instead of `int128`. This has no effect on the library interface. ([#4150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4150)) +- `ECDSA`: Use unchecked arithmetic for the `tryRecover` function that receives the `r` and `vs` short-signature fields separately. ([#4301](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4301)) +- `EIP712`: Added internal getters for the name and version strings ([#4303](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4303)) +- `Math`: Makes `ceilDiv` to revert on 0 division even if the numerator is 0 ([#4348](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4348)) +- `Math`: Optimized stack operations in `mulDiv`. ([#4494](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4494)) +- `Math`: Renamed members of `Rounding` enum, and added a new rounding mode for "away from zero". ([#4455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4455)) +- `MerkleProof`: Use custom error to report invalid multiproof instead of reverting with overflow panic. ([#4564](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4564)) +- `MessageHashUtils`: Added a new library for creating message digest to be used along with signing or recovery such as ECDSA or ERC-1271. These functions are moved from the `ECDSA` library. ([#4430](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4430)) +- `Nonces`: Added a new contract to keep track of user nonces. Used for signatures in `ERC20Permit`, `ERC20Votes`, and `ERC721Votes`. ([#3816](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3816)) +- `ReentrancyGuard`, `Pausable`: Moved to `utils` directory. ([#4551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4551)) +- `Strings`: Renamed `toString(int256)` to `toStringSigned(int256)`. ([#4330](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4330)) +- Optimized `Strings.equal` ([#4262](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4262)) + +### How to migrate from 4.x + +#### ERC20, ERC721, and ERC1155 + +These breaking changes will require modifications to ERC20, ERC721, and ERC1155 contracts, since the `_afterTokenTransfer` and `_beforeTokenTransfer` functions were removed. Thus, any customization made through those hooks should now be done overriding the new `_update` function instead. + +Minting and burning are implemented by `_update` and customizations should be done by overriding this function as well. `_transfer`, `_mint` and `_burn` are no longer virtual (meaning they are not overridable) to guard against possible inconsistencies. + +For example, a contract using `ERC20`'s `_beforeTokenTransfer` hook would have to be changed in the following way. + +```diff +-function _beforeTokenTransfer( ++function _update( + address from, + address to, + uint256 amount + ) internal virtual override { +- super._beforeTokenTransfer(from, to, amount); + require(!condition(), "ERC20: wrong condition"); ++ super._update(from, to, amount); + } +``` + +#### More about ERC721 + +In the case of `ERC721`, the `_update` function does not include a `from` parameter, as the sender is implicitly the previous owner of the `tokenId`. The address of this previous owner is returned by the `_update` function, so it can be used for a posteriori checks. In addition to `to` and `tokenId`, a third parameter (`auth`) is present in this function. This parameter enabled an optional check that the caller/spender is approved to do the transfer. This check cannot be performed after the transfer (because the transfer resets the approval), and doing it before `_update` would require a duplicate call to `_ownerOf`. + +In this logic of removing hidden SLOADs, the `_isApprovedOrOwner` function was removed in favor of a new `_isAuthorized` function. Overrides that used to target the `_isApprovedOrOwner` should now be performed on the `_isAuthorized` function. Calls to `_isApprovedOrOwner` that preceded a call to `_transfer`, `_burn` or `_approve` should be removed in favor of using the `auth` argument in `_update` and `_approve`. This is showcased in `ERC721Burnable.burn` and in `ERC721Wrapper.withdrawTo`. + +The `_exists` function was removed. Calls to this function can be replaced by `_ownerOf(tokenId) != address(0)`. + +#### More about ERC1155 + +Batch transfers will now emit `TransferSingle` if the batch consists of a single token, while in previous versions the `TransferBatch` event would be used for all transfers initiated through `safeBatchTransferFrom`. Both behaviors are compliant with the ERC-1155 specification. + +#### ERC165Storage + +Users that were registering EIP-165 interfaces with `_registerInterface` from `ERC165Storage` should instead do so so by overriding the `supportsInterface` function as seen below: + +```solidity +function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId); +} +``` + +#### SafeMath + +Methods in SafeMath superseded by native overflow checks in Solidity 0.8.0 were removed along with operations providing an interface for revert strings. The remaining methods were moved to `utils/Math.sol`. + +```diff +- import "@openzeppelin/contracts/utils/math/SafeMath.sol"; ++ import "@openzeppelin/contracts/utils/math/Math.sol"; + + function tryOperations(uint256 x, uint256 y) external view { +- (bool overflowsAdd, uint256 resultAdd) = SafeMath.tryAdd(x, y); ++ (bool overflowsAdd, uint256 resultAdd) = Math.tryAdd(x, y); +- (bool overflowsSub, uint256 resultSub) = SafeMath.trySub(x, y); ++ (bool overflowsSub, uint256 resultSub) = Math.trySub(x, y); +- (bool overflowsMul, uint256 resultMul) = SafeMath.tryMul(x, y); ++ (bool overflowsMul, uint256 resultMul) = Math.tryMul(x, y); +- (bool overflowsDiv, uint256 resultDiv) = SafeMath.tryDiv(x, y); ++ (bool overflowsDiv, uint256 resultDiv) = Math.tryDiv(x, y); + // ... + } +``` + +#### Adapting Governor modules + +Custom Governor modules that override internal functions may require modifications if migrated to v5. In particular, the new internal functions `_queueOperations` and `_executeOperations` may need to be used. If assistance with this migration is needed reach out via the [OpenZeppelin Support Forum](https://forum.openzeppelin.com/c/support/contracts/18). + +#### ECDSA and MessageHashUtils + +The `ECDSA` library is now focused on signer recovery. Previously it also included utility methods for producing digests to be used with signing or recovery. These utilities have been moved to the `MessageHashUtils` library and should be imported if needed: + +```diff + import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; ++import {MessageHashUtils} from "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol"; + + contract Verifier { + 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; + } + } +``` + +#### Interfaces and libraries in upgradeable contracts + +The upgradeable version of the contracts library used to include a variant suffixed with `Upgradeable` for every contract. These variants, which are produced automatically, mainly include changes for dealing with storage that don't apply to libraries and interfaces. + +The upgradeable library no longer includes upgradeable variants for libraries and interfaces. Projects migrating to 5.0 should replace their library and interface imports with their corresponding non-upgradeable version: + +```diff + // Libraries +-import {AddressUpgradeable} from '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol'; ++import {Address} from '@openzeppelin/contracts/utils/Address.sol'; + + // Interfaces +-import {IERC20Upgradeable} from '@openzeppelin/contracts-upgradeable/interfaces/IERC20.sol'; ++import {IERC20} from '@openzeppelin/contracts/interfaces/IERC20.sol'; +``` + +#### Offchain Considerations + +Some changes may affect offchain systems if they rely on assumptions that are changed along with these new breaking changes. These cases are: + +##### Relying on revert strings for processing errors + +A concrete example is AccessControl, where it was previously advised to catch revert reasons using the following regex: + +``` +/^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/ +``` + +Instead, contracts now revert with custom errors. Systems that interact with smart contracts outside of the network should consider reliance on revert strings and possibly support the new custom errors. + +##### Relying on storage locations for retrieving data + +After 5.0, the storage location of some variables were changed. This is the case for `Initializable` and all the upgradeable contracts since they now use namespaced storaged locations. Any system relying on storage locations for retrieving data or detecting capabilities should be updated to support these new locations. + +[Changes][v5.0.0] + + + +# [v4.9.3](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.3) - 2023-07-28 + +> **Note** +> This release contains a fix for [GHSA-g4vp-m682-qqmp](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-g4vp-m682-qqmp). + +- `ERC2771Context`: Return the forwarder address whenever the `msg.data` of a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes), as specified by ERC-2771. ([#4481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4481)) +- `ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case. ([#4484](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4484)) + + + + +[Changes][v4.9.3] + + + +# [v4.9.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.2) - 2023-06-16 + +> **Note** +> This release contains a fix for [GHSA-wprv-93r4-jj2p](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wprv-93r4-jj2p). + +- `MerkleProof`: Fix a bug in `processMultiProof` and `processMultiProofCalldata` that allows proving arbitrary leaves if the tree contains a node with value 0 at depth 1. + + +[Changes][v4.9.2] + + + +# [v4.9.1](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.1) - 2023-06-07 + +> **Note** +> This release contains a fix for [GHSA-5h3x-9wvq-w4m2](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-5h3x-9wvq-w4m2). + +- `Governor`: Add a mechanism to restrict the address of the proposer using a suffix in the description. + +[Changes][v4.9.1] + + + +# [v4.9.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.9.0) - 2023-05-23 + +- `ReentrancyGuard`: Add a `_reentrancyGuardEntered` function to expose the guard status. ([#3714](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3714)) +- `ERC721Wrapper`: add a new extension of the `ERC721` token which wraps an underlying token. Deposit and withdraw guarantee that the ownership of each token is backed by a corresponding underlying token with the same identifier. ([#3863](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3863)) +- `EnumerableMap`: add a `keys()` function that returns an array containing all the keys. ([#3920](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3920)) +- `Governor`: add a public `cancel(uint256)` function. ([#3983](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3983)) +- `Governor`: Enable timestamp operation for blockchains without a stable block time. This is achieved by connecting a Governor's internal clock to match a voting token's EIP-6372 interface. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934)) +- `Strings`: add `equal` method. ([#3774](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3774)) +- `IERC5313`: Add an interface for EIP-5313 that is now final. ([#4013](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4013)) +- `IERC4906`: Add an interface for ERC-4906 that is now Final. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012)) +- `StorageSlot`: Add support for `string` and `bytes`. ([#4008](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4008)) +- `Votes`, `ERC20Votes`, `ERC721Votes`: support timestamp checkpointing using EIP-6372. ([#3934](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3934)) +- `ERC4626`: Add mitigation to the inflation attack through virtual shares and assets. ([#3979](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3979)) +- `Strings`: add `toString` method for signed integers. ([#3773](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3773)) +- `ERC20Wrapper`: Make the `underlying` variable private and add a public accessor. ([#4029](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4029)) +- `EIP712`: add EIP-5267 support for better domain discovery. ([#3969](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3969)) +- `AccessControlDefaultAdminRules`: Add an extension of `AccessControl` with additional security rules for the `DEFAULT_ADMIN_ROLE`. ([#4009](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4009)) +- `SignatureChecker`: Add `isValidERC1271SignatureNow` for checking a signature directly against a smart contract using ERC-1271. ([#3932](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3932)) +- `SafeERC20`: Add a `forceApprove` function to improve compatibility with tokens behaving like USDT. ([#4067](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4067)) +- `ERC1967Upgrade`: removed contract-wide `oz-upgrades-unsafe-allow delegatecall` annotation, replaced by granular annotation in `UUPSUpgradeable`. ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971)) +- `ERC20Wrapper`: self wrapping and deposit by the wrapper itself are now explicitly forbidden. ([#4100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4100)) +- `ECDSA`: optimize bytes32 computation by using assembly instead of `abi.encodePacked`. ([#3853](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3853)) +- `ERC721URIStorage`: Emit ERC-4906 `MetadataUpdate` in `_setTokenURI`. ([#4012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4012)) +- `ShortStrings`: Added a library for handling short strings in a gas efficient way, with fallback to storage for longer strings. ([#4023](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4023)) +- `SignatureChecker`: Allow return data length greater than 32 from EIP-1271 signers. ([#4038](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4038)) +- `UUPSUpgradeable`: added granular `oz-upgrades-unsafe-allow-reachable` annotation to improve upgrade safety checks on latest version of the Upgrades Plugins (starting with `@openzeppelin/upgrades-core@1.21.0`). ([#3971](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3971)) +- `Initializable`: optimize `_disableInitializers` by using `!=` instead of `<`. ([#3787](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3787)) +- `Ownable2Step`: make `acceptOwnership` public virtual to enable usecases that require overriding it. ([#3960](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3960)) +- `UUPSUpgradeable.sol`: Change visibility to the functions `upgradeTo ` and `upgradeToAndCall ` from `external` to `public`. ([#3959](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3959)) +- `TimelockController`: Add the `CallSalt` event to emit on operation schedule. ([#4001](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4001)) +- Reformatted codebase with latest version of Prettier Solidity. ([#3898](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3898)) +- `Math`: optimize `log256` rounding check. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) +- `ERC20Votes`: optimize by using unchecked arithmetic. ([#3748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3748)) +- `Multicall`: annotate `multicall` function as upgrade safe to not raise a flag for its delegatecall. ([#3961](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3961)) +- `ERC20Pausable`, `ERC721Pausable`, `ERC1155Pausable`: Add note regarding missing public pausing functionality ([#4007](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4007)) +- `ECDSA`: Add a function `toDataWithIntendedValidatorHash` that encodes data with version 0x00 following EIP-191. ([#4063](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4063)) +- `MerkleProof`: optimize by using unchecked arithmetic. ([#3745](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3745)) + +### Breaking changes + +- `EIP712`: Addition of ERC5267 support requires support for user defined value types, which was released in Solidity version 0.8.8. This requires a pragma change from `^0.8.0` to `^0.8.8`. +- `EIP712`: Optimization of the cache for the upgradeable version affects the way `name` and `version` are set. This is no longer done through an initializer, and is instead part of the implementation's constructor. As a consequence, all proxies using the same implementation will necessarily share the same `name` and `version`. Additionally, an implementation upgrade risks changing the EIP712 domain unless the same `name` and `version` are used when deploying the new implementation contract. + +### Deprecations + +- `ERC20Permit`: Added the file `IERC20Permit.sol` and `ERC20Permit.sol` and deprecated `draft-IERC20Permit.sol` and `draft-ERC20Permit.sol` since [EIP-2612](https://eips.ethereum.org/EIPS/eip-2612) is no longer a Draft. Developers are encouraged to update their imports. ([#3793](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3793)) +- `Timers`: The `Timers` library is now deprecated and will be removed in the next major release. ([#4062](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4062)) +- `ERC777`: The `ERC777` token standard is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066)) +- `ERC1820Implementer`: The `ERC1820` pseudo-introspection mechanism is no longer supported by OpenZeppelin. Our implementation is now deprecated and will be removed in the next major release. The corresponding standard interfaces remain available. ([#4066](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4066)) + + + +[Changes][v4.9.0] + + + +# [v4.8.3](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.8.3) - 2023-04-13 + +> **Note** +> This release contains fixes for https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-mx2q-35m2-x2rh and https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-93hq-5wgc-jc82. + +- `GovernorCompatibilityBravo`: Fix encoding of proposal data when signatures are missing. +- `TransparentUpgradeableProxy`: Fix transparency in case of selector clash with non-decodable calldata or payable mutability. ([#4154](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4154)) + + +[Changes][v4.8.3] + + + +# [v4.8.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.8.2) - 2023-03-02 + +> **Note** +> This release contains a fix for https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-878m-3g6q-594q. + +- `ERC721Consecutive`: Fixed a bug when `_mintConsecutive` is used for batches of size 1 that could lead to balance overflow. Refer to the breaking changes section in the changelog for a note on the behavior of `ERC721._beforeTokenTransfer`. + +### Breaking changes + +- `ERC721`: The internal function `_beforeTokenTransfer` no longer updates balances, which it previously did when `batchSize` was greater than 1. This change has no consequence unless a custom ERC721 extension is explicitly invoking `_beforeTokenTransfer`. Balance updates in extensions must now be done explicitly using `__unsafe_increaseBalance`, with a name that indicates that there is an invariant that has to be manually verified. + + +[Changes][v4.8.2] + + + +# [v4.8.1](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.8.1) - 2023-01-13 + + * `ERC4626`: Use staticcall instead of call when fetching underlying ERC-20 decimals. ([#3943](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3943)) + + +[Changes][v4.8.1] + + + +# [v4.8.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.8.0) - 2022-11-08 + +> **Note** +> Don't miss the section on **Breaking changes** at the end. + + * `TimelockController`: Added a new `admin` constructor parameter that is assigned the admin role instead of the deployer account. ([#3722](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3722)) + * `Initializable`: add internal functions `_getInitializedVersion` and `_isInitializing` ([#3598](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3598)) + * `ERC165Checker`: add `supportsERC165InterfaceUnchecked` for consulting individual interfaces without the full ERC165 protocol. ([#3339](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3339)) + * `Address`: optimize `functionCall` by calling `functionCallWithValue` directly. ([#3468](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3468)) + * `Address`: optimize `functionCall` functions by checking contract size only if there is no returned data. ([#3469](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3469)) + * `Governor`: make the `relay` function payable, and add support for EOA payments. ([#3730](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3730)) + * `GovernorCompatibilityBravo`: remove unused `using` statements. ([#3506](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3506)) + * `ERC20`: optimize `_transfer`, `_mint` and `_burn` by using `unchecked` arithmetic when possible. ([#3513](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3513)) + * `ERC20Votes`, `ERC721Votes`: optimize `getPastVotes` for looking up recent checkpoints. ([#3673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3673)) + * `ERC20FlashMint`: add an internal `_flashFee` function for overriding. ([#3551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3551)) + * `ERC4626`: use the same `decimals()` as the underlying asset by default (if available). ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639)) + * `ERC4626`: add internal `_initialConvertToShares` and `_initialConvertToAssets` functions to customize empty vaults behavior. ([#3639](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3639)) + * `ERC721`: optimize transfers by making approval clearing implicit instead of emitting an event. ([#3481](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3481)) + * `ERC721`: optimize burn by making approval clearing implicit instead of emitting an event. ([#3538](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3538)) + * `ERC721`: Fix balance accounting when a custom `_beforeTokenTransfer` hook results in a transfer of the token under consideration. ([#3611](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3611)) + * `ERC721`: use unchecked arithmetic for balance updates. ([#3524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3524)) + * `ERC721Consecutive`: Implementation of EIP-2309 that allows batch minting of ERC721 tokens during construction. ([#3311](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3311)) + * `ReentrancyGuard`: Reduce code size impact of the modifier by using internal functions. ([#3515](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3515)) + * `SafeCast`: optimize downcasting of signed integers. ([#3565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3565)) + * `ECDSA`: Remove redundant check on the `v` value. ([#3591](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3591)) + * `VestingWallet`: add `releasable` getters. ([#3580](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3580)) + * `VestingWallet`: remove unused library `Math.sol`. ([#3605](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3605)) + * `VestingWallet`: make constructor payable. ([#3665](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3665)) + * `Create2`: optimize address computation by using assembly instead of `abi.encodePacked`. ([#3600](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3600)) + * `Clones`: optimized the assembly to use only the scratch space during deployments, and optimized `predictDeterministicAddress` to use fewer operations. ([#3640](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3640)) + * `Checkpoints`: Use procedural generation to support multiple key/value lengths. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) + * `Checkpoints`: Add new lookup mechanisms. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) + * `Arrays`: Add `unsafeAccess` functions that allow reading and writing to an element in a storage array bypassing Solidity's "out-of-bounds" check. ([#3589](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3589)) + * `Strings`: optimize `toString`. ([#3573](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3573)) + * `Ownable2Step`: extension of `Ownable` that makes the ownership transfers a two step process. ([#3620](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3620)) + * `Math` and `SignedMath`: optimize function `max` by using `>` instead of `>=`. ([#3679](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3679)) + * `Math`: Add `log2`, `log10` and `log256`. ([#3670](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3670)) + * Arbitrum: Update the vendored arbitrum contracts to match the nitro upgrade. ([#3692](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3692)) + +### Breaking changes + + * `ERC721`: In order to add support for batch minting via `ERC721Consecutive` it was necessary to make a minor breaking change in the internal interface of `ERC721`. Namely, the hooks `_beforeTokenTransfer` and `_afterTokenTransfer` have one additional argument that may need to be added to overrides: + +```diff + function _beforeTokenTransfer( + address from, + address to, + uint256 tokenId, ++ uint256 batchSize + ) internal virtual override +``` + + * `ERC4626`: Conversion from shares to assets (and vice-versa) in an empty vault used to consider the possible mismatch between the underlying asset's and the vault's decimals. This initial conversion rate is now set to 1-to-1 irrespective of decimals, which are meant for usability purposes only. The vault now uses the assets decimals by default, so off-chain the numbers should appear the same. Developers overriding the vault decimals to a value that does not match the underlying asset may want to override the `_initialConvertToShares` and `_initialConvertToAssets` to replicate the previous behavior. + + * `TimelockController`: During deployment, the TimelockController used to grant the `TIMELOCK_ADMIN_ROLE` to the deployer and to the timelock itself. The deployer was then expected to renounce this role once configuration of the timelock is over. Failing to renounce that role allows the deployer to change the timelock permissions (but not to bypass the delay for any time-locked actions). The role is no longer given to the deployer by default. A new parameter `admin` can be set to a non-zero address to grant the admin role during construction (to the deployer or any other address). Just like previously, this admin role should be renounced after configuration. If this param is given `address(0)`, the role is not allocated and doesn't need to be revoked. In any case, the timelock itself continues to have this role. + +### Deprecations + + * `EIP712`: Added the file `EIP712.sol` and deprecated `draft-EIP712.sol` since the EIP is no longer a Draft. Developers are encouraged to update their imports. ([#3621](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3621)) + +```diff +-import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol"; ++import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +``` + + * `ERC721Votes`: Added the file `ERC721Votes.sol` and deprecated `draft-ERC721Votes.sol` since it no longer depends on a Draft EIP (EIP-712). Developers are encouraged to update their imports. ([#3699](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3699)) + +```diff +-import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol"; ++import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Votes.sol"; +``` + +### ERC-721 Compatibility Note + +ERC-721 integrators that interpret contract state from events should make sure that they implement the clearing of approval that is implicit in every transfer according to the EIP. Previous versions of OpenZeppelin Contracts emitted an explicit `Approval` event even though it was not required by the specification, and this is no longer the case. + +With the new `ERC721Consecutive` extension, the internal workings of `ERC721` are slightly changed. Custom extensions to ERC721 should be reviewed to ensure they remain correct. The internal functions that should be considered are `_ownerOf` (new), `_beforeTokenTransfer`, and `_afterTokenTransfer`. + + +[Changes][v4.8.0] + + + +# [v4.7.3](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.7.3) - 2022-08-10 + +:warning: This is a patch for a high severity issue. For more information [visit the security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4h98-2769-gh6h). + +### Breaking changes + + * `ECDSA`: `recover(bytes32,bytes)` and `tryRecover(bytes32,bytes)` no longer accept compact signatures to prevent malleability. Compact signature support remains available using `recover(bytes32,bytes32,bytes32)` and `tryRecover(bytes32,bytes32,bytes32)`. + +[Changes][v4.7.3] + + + +# [v4.7.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.7.2) - 2022-07-28 + +:warning: This is a patch for three issues, including a high severity issue in `GovernorVotesQuorumFraction`. For more information visit the security advisories ([1](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-xrc4-737v-9q75), [2](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-7grf-83vw-6f5x), [3](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-9j3m-g383-29qr)). + + 1. `GovernorVotesQuorumFraction`: Fixed quorum updates so they do not affect past proposals that failed due to lack of quorum. ([#3561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3561)) + 2. `ERC165Checker`: Added protection against large returndata. ([#3587](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3587)) + 3. `LibArbitrumL2`, `CrossChainEnabledArbitrumL2`: Fixed detection of cross-chain calls for EOAs. Previously, calls from EOAs would be classified as cross-chain calls. ([#3578](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3578)) + +[Changes][v4.7.2] + + + +# [v4.7.1](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.7.1) - 2022-07-20 + +:warning: This is a patch for a medium severity issue affecting `SignatureChecker` and a high severity issue affecting `ERC165Checker`. For more information visit the security advisories ([1](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-4g63-c64m-25w9), [2](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-qh9x-gcfh-pcrw)). + + * `SignatureChecker`: Fix an issue that causes `isValidSignatureNow` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552)) + * `ERC165Checker`: Fix an issue that causes `supportsInterface` to revert when the target contract returns ill-encoded data. ([#3552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3552)) + + +[Changes][v4.7.1] + + + +# [v4.7.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.7.0) - 2022-06-30 + + * `TimelockController`: Migrate `_call` to `_execute` and allow inheritance and overriding similar to `Governor`. ([#3317](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3317)) + * `CrossChainEnabledPolygonChild`: replace the `require` statement with the custom error `NotCrossChainCall`. ([#3380](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3380)) + * `ERC20FlashMint`: Add customizable flash fee receiver. ([#3327](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3327)) + * `ERC4626`: add an extension of `ERC20` that implements the ERC4626 Tokenized Vault Standard. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171)) + * `SafeERC20`: add `safePermit` as mitigation against phantom permit functions. ([#3280](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3280)) + * `Math`: add a `mulDiv` function that can round the result either up or down. ([#3171](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3171)) + * `Math`: Add a `sqrt` function to compute square roots of integers, rounding either up or down. ([#3242](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3242)) + * `Strings`: add a new overloaded function `toHexString` that converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. ([#3403](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3403)) + * `EnumerableMap`: add new `UintToUintMap` map type. ([#3338](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3338)) + * `EnumerableMap`: add new `Bytes32ToUintMap` map type. ([#3416](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3416)) + * `SafeCast`: add support for many more types, using procedural code generation. ([#3245](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3245)) + * `MerkleProof`: add `multiProofVerify` to prove multiple values are part of a Merkle tree. ([#3276](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3276)) + * `MerkleProof`: add calldata versions of the functions to avoid copying input arrays to memory and save gas. ([#3200](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3200)) + * `ERC721`, `ERC1155`: simplified revert reasons. ([#3254](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3254), ([#3438](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3438))) + * `ERC721`: removed redundant require statement. ([#3434](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3434)) + * `PaymentSplitter`: add `releasable` getters. ([#3350](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3350)) + * `Initializable`: refactored implementation of modifiers for easier understanding. ([#3450](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3450)) + * `Proxies`: remove runtime check of ERC1967 storage slots. ([#3455](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3455)) + +### Breaking changes + + * `Initializable`: functions decorated with the modifier `reinitializer(1)` may no longer invoke each other. + +[Changes][v4.7.0] + + + +# [v4.6.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.6.0) - 2022-04-29 + + * `crosschain`: Add a new set of contracts for cross-chain applications. `CrossChainEnabled` is a base contract with instantiations for several chains and bridges, and `AccessControlCrossChain` is an extension of access control that allows cross-chain operation. ([#3183](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3183)) + * `AccessControl`: add a virtual `_checkRole(bytes32)` function that can be overridden to alter the `onlyRole` modifier behavior. ([#3137](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3137)) + * `EnumerableMap`: add new `AddressToUintMap` map type. ([#3150](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3150)) + * `EnumerableMap`: add new `Bytes32ToBytes32Map` map type. ([#3192](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3192)) + * `ERC20FlashMint`: support infinite allowance when paying back a flash loan. ([#3226](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3226)) + * `ERC20Wrapper`: the `decimals()` function now tries to fetch the value from the underlying token instance. If that calls revert, then the default value is used. ([#3259](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3259)) + * `draft-ERC20Permit`: replace `immutable` with `constant` for `_PERMIT_TYPEHASH` since the `keccak256` of string literals is treated specially and the hash is evaluated at compile time. ([#3196](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3196)) + * `ERC1155`: Add a `_afterTokenTransfer` hook for improved extensibility. ([#3166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3166)) + * `ERC1155URIStorage`: add a new extension that implements a `_setURI` behavior similar to ERC721's `_setTokenURI`. ([#3210](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3210)) + * `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153)) + * `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147)) + * `Governor`: Add a way to parameterize votes. This can be used to implement voting systems such as fractionalized voting, ERC721 based voting, or any number of other systems. The `params` argument added to `_countVote` method, and included in the newly added `_getVotes` method, can be used by counting and voting modules respectively for such purposes. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) + * `Governor`: rewording of revert reason for consistency. ([#3275](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3275)) + * `Governor`: fix an inconsistency in data locations that could lead to invalid bytecode being produced. ([#3295](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3295)) + * `Governor`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by governors. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230)) + * `TimelockController`: Implement `IERC721Receiver` and `IERC1155Receiver` to improve token custody by timelocks. ([#3230](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3230)) + * `TimelockController`: Add a separate canceller role for the ability to cancel. ([#3165](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3165)) + * `Initializable`: add a reinitializer modifier that enables the initialization of new modules, added to already initialized contracts through upgradeability. ([#3232](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3232)) + * `Initializable`: add an Initialized event that tracks initialized version numbers. ([#3294](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3294)) + * `ERC2981`: make `royaltiInfo` public to allow super call in overrides. ([#3305](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3305)) + +### Upgradeability notice + +* `TimelockController`: **(Action needed)** The upgrade from `<4.6 to >=4.6` introduces a new `CANCELLER_ROLE` that requires set up to be assignable. After the upgrade, only addresses with this role will have the ability to cancel. Proposers will no longer be able to cancel. Assigning cancellers can be done by an admin (including the timelock itself) once the role admin is set up. To do this, we recommend upgrading to the `TimelockControllerWith46MigrationUpgradeable` contract and then calling the `migrateTo46` function. + +### Breaking changes + +* `Governor`: Adds internal virtual `_getVotes` method that must be implemented; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing voting module extension, rename `getVotes` to `_getVotes` and add a `bytes memory` argument. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +* `Governor`: Adds `params` parameter to internal virtual `_countVote ` method; this is a breaking change for existing concrete extensions to `Governor`. To fix this on an existing counting module extension, add a `bytes memory` argument to `_countVote`. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +* `Governor`: Does not emit `VoteCast` event when params data is non-empty; instead emits `VoteCastWithParams` event. To fix this on an integration that consumes the `VoteCast` event, also fetch/monitor `VoteCastWithParams` events. ([#3043](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3043)) +* `Votes`: The internal virtual function `_getVotingUnits` was made `view` (which was accidentally missing). Any overrides should now be updated so they are `view` as well. + + +[Changes][v4.6.0] + + + +# [v4.5.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.5.0) - 2022-02-09 + + * `ERC2981`: add implementation of the royalty standard, and the respective extensions for `ERC721` and `ERC1155`. ([#3012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3012)) + * `GovernorTimelockControl`: improve the `state()` function to have it reflect cases where a proposal has been canceled directly on the timelock. ([#2977](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2977)) + * Preset contracts are now deprecated in favor of [Contracts Wizard](https://wizard.openzeppelin.com). ([#2986](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2986)) + * `Governor`: add a relay function to help recover assets sent to a governor that is not its own executor (e.g. when using a timelock). ([#2926](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2926)) + * `GovernorPreventLateQuorum`: add new module to ensure a minimum voting duration is available after the quorum is reached. ([#2973](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2973)) + * `ERC721`: improved revert reason when transferring from wrong owner. ([#2975](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2975)) + * `Votes`: Added a base contract for vote tracking with delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944)) + * `ERC721Votes`: Added an extension of ERC721 enabled with vote tracking and delegation. ([#2944](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2944)) + * `ERC2771Context`: use immutable storage to store the forwarder address, no longer an issue since Solidity >=0.8.8 allows reading immutable variables in the constructor. ([#2917](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2917)) + * `Base64`: add a library to parse bytes into base64 strings using `encode(bytes memory)` function, and provide examples to show how to use to build URL-safe `tokenURIs`. ([#2884](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2884)) + * `ERC20`: reduce allowance before triggering transfer. ([#3056](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3056)) + * `ERC20`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085)) + * `ERC20`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) + * `ERC20Burnable`: do not update allowance on `burnFrom` when allowance is `type(uint256).max`. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) + * `ERC777`: do not update allowance on `transferFrom` when allowance is `type(uint256).max`. ([#3085](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3085)) + * `ERC777`: add a `_spendAllowance` internal function. ([#3170](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3170)) + * `SignedMath`: a new signed version of the Math library with `max`, `min`, and `average`. ([#2686](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2686)) + * `SignedMath`: add a `abs(int256)` method that returns the unsigned absolute value of a signed value. ([#2984](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2984)) + * `ERC1967Upgrade`: Refactor the secure upgrade to use `ERC1822` instead of the previous rollback mechanism. This reduces code complexity and attack surface with similar security guarantees. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021)) + * `UUPSUpgradeable`: Add `ERC1822` compliance to support the updated secure upgrade mechanism. ([#3021](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3021)) + * Some more functions have been made virtual to customize them via overrides. In many cases this will not imply that other functions in the contract will automatically adapt to the overridden definitions. People who wish to override should consult the source code to understand the impact and if they need to override any additional functions to achieve the desired behavior. + +### Breaking changes + +* `ERC1967Upgrade`: The function `_upgradeToAndCallSecure` was renamed to `_upgradeToAndCallUUPS`, along with the change in security mechanism described above. +* `Address`: The Solidity pragma is increased from `^0.8.0` to `^0.8.1`. This is required by the `account.code.length` syntax that replaces inline assembly. This may require users to bump their compiler version from `0.8.0` to `0.8.1` or later. Note that other parts of the code already include stricter requirements. + + +[Changes][v4.5.0] + + + +# [v4.4.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.4.2) - 2022-01-11 + +:warning: This is a patch for a medium severity issue. For more information [visit the security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-m6w8-fq7v-ph4m). + + * `GovernorCompatibilityBravo`: Fix error in the encoding of calldata for proposals submitted through the compatibility interface with explicit signatures. ([#3100](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/#3100)) + + +[Changes][v4.4.2] + + + +# [v4.4.1](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.4.1) - 2021-12-14 + +:warning: This is a patch for a low severity vulnerability. For more information [visit the security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-9c22-pwxw-p6hx). + + * `Initializable`: change the existing `initializer` modifier and add a new `onlyInitializing` modifier to prevent reentrancy risk. ([#3006](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3006)) + +### Breaking change + +It is no longer possible to call an `initializer`-protected function from within another `initializer` function outside the context of a constructor. Projects using OpenZeppelin upgradeable proxies should continue to work as is, since in the common case the initializer is invoked in the constructor directly. If this is not the case for you, the suggested change is to use the new `onlyInitializing` modifier in the following way: + +```diff + contract A { +- function initialize() public initializer { ... } ++ function initialize() internal onlyInitializing { ... } + } + contract B is A { + function initialize() public initializer { + A.initialize(); + } + } +``` + + +[Changes][v4.4.1] + + + +# [v4.4.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.4.0) - 2021-11-25 + +Check out the first [**OpenZeppelin Community Call**](https://www.youtube.com/watch?v=ed96DWbfliQ) where the team discussed everything that is included in this release. + +And if you missed it, we recently announced an official **bug bounty program** for OpenZeppelin Contracts. [Check it out!](https://forum.openzeppelin.com/t/openzeppelin-contracts-bug-bounty-program-on-immunefi/19279) + + * `Ownable`: add an internal `_transferOwnership(address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) + * `AccessControl`: add internal `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) + * `AccessControl`: mark `_setupRole(bytes32,address)` as deprecated in favor of `_grantRole(bytes32,address)`. ([#2568](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2568)) + * `AccessControlEnumerable`: hook into `_grantRole(bytes32,address)` and `_revokeRole(bytes32,address)`. ([#2946](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2946)) + * `EIP712`: cache `address(this)` to immutable storage to avoid potential issues if a vanilla contract is used in a delegatecall context. ([#2852](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2852)) + * Add internal `_setApprovalForAll` to `ERC721` and `ERC1155`. ([#2834](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2834)) + * `Governor`: shift vote start and end by one block to better match Compound's GovernorBravo and prevent voting at the Governor level if the voting snapshot is not ready. ([#2892](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2892)) + * `GovernorCompatibilityBravo`: consider quorum an inclusive rather than exclusive minimum to match Compound's GovernorBravo. ([#2974](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2974)) + * `GovernorSettings`: a new governor module that manages voting settings updatable through governance actions. ([#2904](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2904)) + * `PaymentSplitter`: now supports ERC20 assets in addition to Ether. ([#2858](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2858)) + * `ECDSA`: add a variant of `toEthSignedMessageHash` for arbitrary length message hashing. ([#2865](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2865)) + * `MerkleProof`: add a `processProof` function that returns the rebuilt root hash given a leaf and a proof. ([#2841](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2841)) + * `VestingWallet`: new contract that handles the vesting of Ether and ERC20 tokens following a customizable vesting schedule. ([#2748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2748)) + * `Governor`: enable receiving Ether when a Timelock contract is not used. ([#2748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849)) + * `GovernorTimelockCompound`: fix ability to use Ether stored in the Timelock contract. ([#2748](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2849)) + + +[Changes][v4.4.0] + + + +# [v4.3.3](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.3.3) - 2021-11-15 + +:warning: This is a security patch. For more information visit the [security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-wmpv-c2jp-j2xg). + + * `ERC1155Supply`: Handle `totalSupply` changes by hooking into `_beforeTokenTransfer` to ensure consistency of balances and supply during `IERC1155Receiver.onERC1155Received` calls. + + +[Changes][v4.3.3] + + + +# [v4.3.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.3.2) - 2021-09-14 + +:warning: This is a security patch. For more information visit the [security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-5vp3-v4hc-gx76). + + * `UUPSUpgradeable`: Add modifiers to prevent `upgradeTo` and `upgradeToAndCall` being executed on any contract that is not the active ERC1967 proxy. This prevents these functions being called on implementation contracts or minimal ERC1167 clones, in particular. + + +[Changes][v4.3.2] + + + +# [v4.3.1](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.3.1) - 2021-08-26 + +:warning: This is a security patch. For more information visit the [security advisory](https://github.com/OpenZeppelin/openzeppelin-contracts/security/advisories/GHSA-fg47-3c2x-m2wr). + + * `TimelockController`: Add additional isOperationReady check. + + +[Changes][v4.3.1] + + + +# [v3.4.2-solc-0.7](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.4.2-solc-0.7) - 2021-08-26 + + * `TimelockController`: Add additional isOperationReady check. + + +[Changes][v3.4.2-solc-0.7] + + + +# [v3.4.2](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.4.2) - 2021-08-26 + + * `TimelockController`: Add additional isOperationReady check. + + +[Changes][v3.4.2] + + + +# [v4.3.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.3.0) - 2021-08-17 + +**Visit our blog for the full [4.3 announcement](https://blog.openzeppelin.com/openzeppelin-contracts-4-3/) as well as [Governor announcement](https://blog.openzeppelin.com/governor-smart-contract//)!** + + * `ERC2771Context`: use private variable from storage to store the forwarder address. Fixes issues where `_msgSender()` was not callable from constructors. ([#2754](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2754)) + * `EnumerableSet`: add `values()` functions that returns an array containing all values in a single call. ([#2768](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2768)) + * `Governor`: added a modular system of `Governor` contracts based on `GovernorAlpha` and `GovernorBravo`. ([#2672](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2672)) + * Add an `interfaces` folder containing solidity interfaces to final ERCs. ([#2517](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2517)) + * `ECDSA`: add `tryRecover` functions that will not throw if the signature is invalid, and will return an error flag instead. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661)) + * `SignatureChecker`: Reduce gas usage of the `isValidSignatureNow` function for the "signature by EOA" case. ([#2661](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2661)) + + +[Changes][v4.3.0] + + + +# [v4.2.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.2.0) - 2021-06-30 + +**Read the full announcement [in the blog](https://blog.openzeppelin.com/openzeppelin-contracts-4-2/)!** + +--- + + * `ERC20Votes`: add a new extension of the `ERC20` token with support for voting snapshots and delegation. ([#2632](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2632)) + * `ERC20VotesComp`: Variant of `ERC20Votes` that is compatible with Compound's `Comp` token interface but restricts supply to `uint96`. ([#2706](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2706)) + * `ERC20Wrapper`: add a new extension of the `ERC20` token which wraps an underlying token. Deposit and withdraw guarantee that the total supply is backed by a corresponding amount of underlying token. ([#2633](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2633)) + * Enumerables: Improve gas cost of removal in `EnumerableSet` and `EnumerableMap`. + * Enumerables: Improve gas cost of lookup in `EnumerableSet` and `EnumerableMap`. + * `Counter`: add a reset method. ([#2678](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2678)) + * Tokens: Wrap definitely safe subtractions in `unchecked` blocks. + * `Math`: Add a `ceilDiv` method for performing ceiling division. + * `ERC1155Supply`: add a new `ERC1155` extension that keeps track of the totalSupply of each tokenId. ([#2593](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2593)) + * `BitMaps`: add a new `BitMaps` library that provides a storage efficient datastructure for `uint256` to `bool` mapping with contiguous keys. ([#2710](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2710)) + + ### Breaking Changes + + * `ERC20FlashMint` is no longer a Draft ERC. ([#2673](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2673))) + +**How to update:** Change your import paths by removing the `draft-` prefix from `@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20FlashMint.sol`. + +> See [Releases and Stability: Drafts](https://docs.openzeppelin.com/contracts/4.x/releases-stability#drafts). + + +[Changes][v4.2.0] + + + +# [v4.1.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.1.0) - 2021-04-30 + +Read the full announcement [in the blog](https://blog.openzeppelin.com/openzeppelin-contracts-4-1/) or check out the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md#410-2021-04-29). + + * `IERC20Metadata`: add a new extended interface that includes the optional `name()`, `symbol()` and `decimals()` functions. ([#2561](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2561)) + * `ERC777`: make reception acquirement optional in `_mint`. ([#2552](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2552)) + * `ERC20Permit`: add a `_useNonce` to enable further usage of ERC712 signatures. ([#2565](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2565)) + * `ERC20FlashMint`: add an implementation of the ERC3156 extension for flash-minting ERC20 tokens. ([#2543](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2543)) + * `SignatureChecker`: add a signature verification library that supports both EOA and ERC1271 compliant contracts as signers. ([#2532](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2532)) + * `Multicall`: add abstract contract with `multicall(bytes[] calldata data)` function to bundle multiple calls together ([#2608](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2608)) + * `ECDSA`: add support for ERC2098 short-signatures. ([#2582](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2582)) + * `AccessControl`: add a `onlyRole` modifier to restrict specific function to callers bearing a specific role. ([#2609](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2609)) + * `StorageSlot`: add a library for reading and writing primitive types to specific storage slots. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542)) + * UUPS Proxies: add `UUPSUpgradeable` to implement the UUPS proxy pattern together with `EIP1967Proxy`. ([#2542](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2542)) + + +[Changes][v4.1.0] + + + +# [v4.0.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v4.0.0) - 2021-03-24 + +Read the full announcement [in the blog](https://blog.openzeppelin.com/openzeppelin-contracts-4-0/) or check out the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md#400-2021-03-23). + +## Changelog + + * Now targeting the 0.8.x line of Solidity compilers. For 0.6.x (resp 0.7.x) support, use version 3.4.0 (resp 3.4.0-solc-0.7) of OpenZeppelin. + * `Context`: making `_msgData` return `bytes calldata` instead of `bytes memory` ([#2492](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2492)) + * `ERC20`: removed the `_setDecimals` function and the storage slot associated to decimals. ([#2502](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2502)) + * `Strings`: addition of a `toHexString` function. ([#2504](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2504)) + * `EnumerableMap`: change implementation to optimize for `key → value` lookups instead of enumeration. ([#2518](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2518)) + * `GSN`: deprecate GSNv1 support in favor of upcoming support for GSNv2. ([#2521](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2521)) + * `ERC165`: remove uses of storage in the base ERC165 implementation. ERC165 based contracts now use storage-less virtual functions. Old behavior remains available in the `ERC165Storage` extension. ([#2505](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2505)) + * `Initializable`: make initializer check stricter during construction. ([#2531](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2531)) + * `ERC721`: remove enumerability of tokens from the base implementation. This feature is now provided separately through the `ERC721Enumerable` extension. ([#2511](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2511)) + * `AccessControl`: removed enumerability by default for a more lightweight contract. It is now opt-in through `AccessControlEnumerable`. ([#2512](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2512)) + * Meta Transactions: add `ERC2771Context` and a `MinimalForwarder` for meta-transactions. ([#2508](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2508)) + * Overall reorganization of the contract folder to improve clarity and discoverability. ([#2503](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2503)) + * `ERC20Capped`: optimize gas usage by enforcing the check directly in `_mint`. ([#2524](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2524)) + * Rename `UpgradeableProxy` to `ERC1967Proxy`. ([#2547](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2547)) + * `ERC777`: optimize the gas costs of the constructor. ([#2551](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2551)) + * `ERC721URIStorage`: add a new extension that implements the `_setTokenURI` behavior as it was available in 3.4.0. ([#2555](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2555)) + * `AccessControl`: added ERC165 interface detection. ([#2562](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2562)) + * `ERC1155`: make `uri` public so overloading function can call it using super. ([#2576](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2576)) + +### How to upgrade from 3.x + +Since this version has moved a few contracts to different directories, users upgrading from a previous version will need to adjust their import statements. To make this easier, the package includes a script that will migrate import statements automatically. After upgrading to the latest version of the package, run: + +``` +npx openzeppelin-contracts-migrate-imports +``` + +Make sure you're using git or another version control system to be able to recover from any potential error in our script. + + +[Changes][v4.0.0] + + + +# [v3.4.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.4.0) - 2021-02-03 + +Read the full announcement [in the blog](https://blog.openzeppelin.com/openzeppelin-contracts-4-0/) or check out the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md#340-2021-02-02). + +### Security Fixes + + * `ERC777`: fix potential reentrancy issues for custom extensions to `ERC777`. ([#2483](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483)) + +If you're using our implementation of ERC777 from version 3.3.0 or earlier, and you define a custom `_beforeTokenTransfer` function that writes to a storage variable, you may be vulnerable to a reentrancy attack. If you're affected and would like assistance please write to security@openzeppelin.com. [Read more in the pull request.](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2483) + + +[Changes][v3.4.0] + + + +# [v3.3.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.3.0) - 2020-11-27 + +Read the full announcement [in the forum](https://forum.openzeppelin.com/t/openzeppelin-contracts-3-3/4804) or check out the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md#330-2020-11-26). + + * Now supports both Solidity 0.6 and 0.7. Compiling with solc 0.7 will result in warnings. Install the `solc-0.7` tag to compile without warnings. + * `TimelockController`: added a contract to augment access control schemes with a delay. ([#2354](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2354)) + * `Address`: added `functionStaticCall`, similar to the existing `functionCall`. ([#2333](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2333)) + * `EnumerableSet`: added `Bytes32Set`, for sets of `bytes32`. ([#2395](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2395)) + +[Changes][v3.3.0] + + + +# [v3.2.1 for Solidity 0.7 (v3.2.1-solc-0.7)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.2.1-solc-0.7) - 2020-09-15 + +This is a special release for Solidity 0.7 that gets rid of a warning in `ERC777` using one of the new features of the language. ([#2327](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2327)) + +Note: The variant for Solidity 0.7 can be installed using `npm install @openzeppelin/contracts@solc-0.7`. + +[Changes][v3.2.1-solc-0.7] + + + +# [v3.2.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.2.0) - 2020-09-11 + +Welcome to a new release of OpenZeppelin Contracts! :dancers: + +The big feature in this release is that we’ve migrated our proxy contracts from OpenZeppelin SDK into the Contracts project. We hope this will make more people aware of upgrades in Ethereum, and we also think the contracts will benefit greatly from the continued scrutiny by all of you in the community. This was also a migration of the proxies from Solidity 0.5 to 0.6, which we know some users have been waiting for. + +> For Solidity 0.7 users, a reminder that we have support for the newer compiler version published on npm under the tag `solc-0.7`, the latest release being `3.2.0-solc-0.7`. We’re considering officially switching to 0.7 for the release after this one. + +There is additionally a small breaking change in `ERC20Snapshot` that may affect some of its users. If you’re one of them please take a look at [the changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md#320-2020-09-10). + +[Changes][v3.2.0] + + + +# [v3.1.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.1.0) - 2020-06-29 + + +This is the first release since [v3.0, our last major release](https://forum.openzeppelin.com/t/openzeppelin-contracts-v3-0/2695). It includes the long-awaited **ERC1155 token** and helpers for **safe calls and downcasts**, as well as a number of minor improvements. + +To install this release, run: + +```bash +npm install --save-dev @openzeppelin/contracts +``` + +## ERC1155 + +This is a new [token standard](https://eips.ethereum.org/EIPS/eip-1155) developed by the gaming industry, focusing on gas efficiency. It's key difference is that it is a _multi-token_ contract: a single ERC1155 can be used to represent an arbitrary number of tokens, which is very useful in applications that require many tokens by removing the high gas costs associated with deploying them. Check out our new [documentation page](https://docs.openzeppelin.com/contracts/3.x/erc1155) to learn more!. + +## More Replacements for `call` + +The low-level `call` primitive can be hard to use correctly and is often considered unsafe. With the addition of [`sendValue`](https://docs.openzeppelin.com/contracts/3.x/api/utils#Address-sendValue-address-payable-uint256-) in Contracts and `try-catch` in Solidity, there's only a few scenarios in which `call` is still needed, the most troublesome one being forwarding calls. + +The new [`functionCall`](https://docs.openzeppelin.com/contracts/3.x/api/utils.html#Address-functionCall-address-bytes-) helpers can forward call data to a recipient contract while imitating Solidity's function call semantics, such as bubbling up revert reasons and rejecting calls to EOAs. We expect the addition of these functions to greatly reduce the need to rely on `call`. + +## Using `SafeMath` on Small Signed Integers + +We've expanded the scope of the [`SafeCast`](https://docs.openzeppelin.com/contracts/3.x/api/utils#SafeCast) library to also include signed integer downcasting, which allows for users that need small types (such as `int8` or `int32`) to perform checked arithmetic on them by using `SafeMath`, and then downcast the result to the intended size. + +[Changes][v3.1.0] + + + +# [OpenZeppelin Contracts 3.0.1 (v3.0.1)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.0.1) - 2020-04-27 + +This is a small bugfix release, addressing an issue that allowed for some `internal` functions in ERC777 to be called with the zero address as one of their arguments. + +This was reported in [#2208](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2208), fixed in [#2212](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2212) for the v2.5 branch, and ported to the v3.0 branch in [#2213](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2213). + +[Changes][v3.0.1] + + + +# [OpenZeppelin Contracts 2.5.1 (v2.5.1)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.5.1) - 2020-04-27 + +This is a small bugfix release, addressing an issue that allowed for some `internal` functions in ERC777 to be called with the zero address as one of their arguments. + +This was reported in [#2208](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2208) and fixed in [#2212](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/2212). + +[Changes][v2.5.1] + + + +# [OpenZeppelin Contracts 3.0 (v3.0.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v3.0.0) - 2020-04-20 + +We're thrilled to finally announce the **release of OpenZeppelin Contracts v3.0** :sparkles: + +Among other things, this release features the **migration to Solidity v0.6**, as well as a **revamped access control system**, **streamlined token contracts**, and new libraries for **enumerable mappings**. + +To install this latest release, run: + +```bash +npm install --save-dev @openzeppelin/contracts +``` + +## What's New + + * All contracts were migrated to Solidity v0.6. + * `AccessControl` was designed [with help from the community](https://forum.openzeppelin.com/t/redesigning-accesscontrol-for-the-openzeppelin-contracts/2177) and has replaced `Roles` contracts (such as `MinterRole` and `PauserRole`), which were removed. + * Crowdsales were removed: we'll continue to provide support for security issues on the v2.5 release, but will not bring them over to v3.0. + * We've added **hooks**, a new feature of the library that will make extending it easier than ever. + * `ERC20` and `ERC721` were simplified and streamlined, including all optional parts of the standard by default, and simplifying some of our own custom extensions. + * Support for better `mapping` types that let you efficiently iterate over all keys using `EnumerableSet` and `EnumerableMap` + * Many, _many_ breaking changes with small improvements. We've also moved some contracts around (e.g. `Ownable` is now found under the `access` directory) and deleted some that were not being used. Head to our [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md) to see the full list. + +## Compiling v0.6 Contracts + +You can use the [**OpenZeppelin CLI**](https://docs.openzeppelin.com/cli) to compile any Solidity v0.6 contract: just update the `pragma` statement on your source code and you'll be good to go! + +```solidity +pragma solidity ^0.6.0; +``` + +Note that you will need to use the [v2.7 release of the CLI](https://forum.openzeppelin.com/t/openzeppelin-cli-2-7/2252) or newer to have Solidity v0.6 support. For detailed information about using the CLI compiler, head to its [documenation](https://docs.openzeppelin.com/cli/compiling). + +## Revamped Access Control + +One of our most widely-used contracts is [`Ownable`](https://docs.openzeppelin.com/contracts/3.x/api/access#Ownable), providing a simple authorization scheme. However, this fell short in complex systems with [multiple permissions](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/366). + +The v3.0 release introduces `AccessControl`, a one-stop-shop for all authorization needs. It lets you easily define multiple **roles with different permissions**, as well as **which accounts are allowed to grant and revoke** each role. It also boosts transparency by enabling **enumeration of all privileged accounts** in a system. + +`AccessControl` was designed with a security-first mindset, [receiving input from a wide array of users](https://forum.openzeppelin.com/t/redesigning-accesscontrol-for-the-openzeppelin-contracts/2177) and incorporating best practices in the field. Head to [our Access Control guide](https://docs.openzeppelin.com/contracts/3.x/access-control#role-based-access-control) for more information! + +## Preset Contracts + +OpenZeppelin Contracts shine when you need the building blocks to get to the right feature set, but that's not all they can do! We've added a new family of **Preset** contracts starting with ERC20 and ERC721 tokens that you can quickly deploy as-is **without having to write any Solidity code**. Check out [their documentation!](https://docs.openzeppelin.com/contracts/3.x/api/presets) + +## Migrating From OpenZeppelin Contracts v2.5 + +Other than the moved and deleted contracts mentioned above, the library API is pretty much the same as in the [v2.5 release](https://forum.openzeppelin.com/t/openzeppelin-contracts-v2-5/2155), so the migration should be straightforward. For instructions on how to update your Solidity v0.5 contracts to v0.6, refer to the [official documentation](https://solidity.readthedocs.io/en/v0.6.2/060-breaking-changes.html#how-to-update-your-code). + +If you're using the `ERC20` or `ERC721` tokens however, you'll have to remove all references to optional extensions (`ERC20Detailed`, `ERC721Enumerable`, etc.) - these have been included in the base contracts. + +The other exception to this are contracts that use the [**Gas Station Network (GSN)**](https://docs.openzeppelin.com/contracts/3.x/gsn): if you're inheriting from `GSNRecipient` or one of the other GSN contracts, you'll need to add the following snippet to your contracts: + +```solidity +function _msgSender() internal view override(Context, GSNRecipient) returns (address payable) { + return GSNRecipient._msgSender(); +} + +function _msgData() internal view override(Context, GSNRecipient) returns (bytes memory) { + return GSNRecipient._msgData(); +} +``` + +## Using Hooks + +To improve library flexibility, we're introducing **hooks**: functions that are called at specific moments during a contract's operation that you can use to _hook_ into the internals and extend as you wish. + +For example, the `_beforeTokenTransfer` hook in ERC20, ERC721 and ERC777 makes it very easy to add additional checks or actions to execute whenever tokens are transferred, minted or burned, regardless of what prompted it. + +```bash +// Tokens can only be transferred, minted or burned if the contract is not paused +contract ERC20Pausable is ERC20, Pausable { + function _beforeTokenTransfer(address from, address to, uint256 amount) + internal virtual override + { + super._beforeTokenTransfer(from, to, amount); + + require(!paused(), "ERC20Pausable: token transfer while paused"); + } +} +``` + +As an additional benefit, using hooks will allow you to side-step some of the [edge-cases product of the new `override` keyword](https://github.com/ethereum/solidity/issues/8141). + +Head over to our [brand new guide on Extending the OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/3.x/extending-contracts) to learn more! + +## What's Next + +We've started work in some exciting features for the upcoming releases, including **fixed-point arithmetic** and the **ERC1155 token standard**. To read more and find out how you can contribute, check out our [Q2 2020 roadmap](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2207)! + +[Changes][v3.0.0] + + + +# [OpenZeppelin Contracts 2.5 (v2.5.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.5.0) - 2020-02-05 + +We're very happy the announce the release of **OpenZeppelin Contracts v2.5**! + +This new release features: + - **`EnumerableSet`**: similar to Solidity's `mapping`, but that lets you retrieve all the keys! Useful for dapps that need to display a set of accounts with some property, and cannot rely on events alone. + - **`Create2`**: a simple library for using the [CREATE2 opcode](https://eips.ethereum.org/EIPS/eip-1014), allowing for deployment and pre-computation of addresses when using it. +_To learn more about all the cool things you can do with it, head to [Getting the Most out of CREATE2](https://blog.openzeppelin.com/getting-the-most-out-of-create2/)_ + - **`ERC721Metadata.baseURI`**: a neat extension for _massive_ gas savings when the token URIs share a prefix, like `https://my.cool.app/token/` + +There are also some minor improvements, such as gas optimizations for `ReentrancyGuard` and additional extensibility of `ERC777`, among others. + +_For the complete list of changes, head to our [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md)._ + +To install the new release, run: + +```bash +$ npm install @openzeppelin/contracts@latest +``` + +## New Documentation :books: + +We've also recently done some some improvements to our [documentation website](https://forum.openzeppelin.com/t/revamped-documentation-site/2056), including [new detailed guides](https://docs.openzeppelin.com/learn/) and documentation for our other tools, such as the [**Test Helpers**](https://docs.openzeppelin.com/test-helpers/0.5/), our blazing-fast [**Test Environment**](https://docs.openzeppelin.com/test-environment/0.1/) and the [**OpenZeppelin Command Line Interface**](https://docs.openzeppelin.com/cli/2.6/). Check them out for a radically better development experience! + +## Saying Goodbye to Solidity v0.5 :wave: + +December 2019 saw the [release of Solidity v0.6](https://github.com/ethereum/solidity/releases/tag/v0.6.0). This new version of the language has major improvements, and we're already underway to **release the next version of OpenZeppelin Contracts with support for Solidity v0.6**. + +However, it also includes _a lot_ of breaking changes, making it difficult to support both v0.5 and v0.6 code at the same time. For this reason, we've decided OpenZeppelin Contracts v2.5 will be the **last version supporting Solidity v0.5**. + +The exciting good news it that the next OpenZeppelin Contracts release will be v3.0, where we'll get to redesign some quirky bits of the library, improving ease of use and flexibility. Stay tuned! + +[Changes][v2.5.0] + + + +# [OpenZeppelin 2.4 (v2.4.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.4.0) - 2019-11-01 + +In 2.4 we're releasing support for the Gas Station Network for user onboarding and metatransactions :fuelpump:, new functions to safeguard your contracts against the Istanbul hard fork, and improvements to error messages. + +Read the full announcement in the [OpenZeppelin Forum](https://forum.openzeppelin.com/t/openzeppelin-contracts-v2-4/1665), and make sure to check out the details in the [changelog](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/CHANGELOG.md#240-2019-10-29)! + +Enjoy! + +[Changes][v2.4.0] + + + +# [OpenZeppelin 2.3 (v2.3.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.3.0) - 2019-06-03 + +In 2.3 we're introducing **ERC777**, **revert reasons**, and **a new documentation site**. :fireworks: Take a look and tell us what you think in [the announcement thread](https://forum.zeppelin.solutions/t/openzeppelin-2-3/787)! + +Take a look and tell us what you think! + +### ERC777 + +The long awaited sequel to ERC20. Its main additions are _transfer hooks_ and _operators_. Hooks let your contracts react to token transfers. In other words, running code when a contract receives tokens is a built-in feature: no more messing around with `approve` and `transferFrom`! + +The other special feature, operators, provides simpler and more flexible ways of delegating usage of your tokens to other people or contracts, like decentralized exchanges. + +All of this with full compatibility with ERC20! + +Start building on it and tell us what you think! We're looking for ideas for extensions, custom operators, or utilities. Share your ideas here or in a new thread. + +### Revert reasons + +Are you tired of running into cryptic errors like `VM Exception while processing transaction: revert`? All errors in OpenZeppelin now have proper error messages that will be displayed when you test your code! We've kept them succinct and to the point. Each error message is unique, so if you're having trouble figuring out exactly which `require` statement you've hit, it is easy to look up the error string in the source code, and look at the actual condition that is not being met. + +### Documentation site + +We've revamped the docs, [take a look](https://docs.openzeppelin.org)! + +It'll be super helpful to both people looking to get started in smart contract development, and veteran OpenZeppelin users who just need to quickly recall a function signature. Among other improvements, we've bundled together related concepts, added overviews for each section, and added crosslinks to other contracts and functions to make exploring the docsite a breeze! + +Everything is automatically generated from the comments in the source code, so if you spot a typo or have a suggestion, simply open an issue or PR to get it sorted out in no time! + +_Some sections still require a bit of work to get them to where we want them to be, stay tuned!_ + +### More + +Some more things are included in this release such as an implementation of ERC1820, and a fix for a bug in `PostDeliveryCrowdsale`. Take a look at the [changelog](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/CHANGELOG.md#230-2019-05-27)! +We have revamped the documentation site infrastructure and feel, [take a look](https://docs.openzeppelin.org)! It'll be super helpful to both people looking to get started in smart contract development and OpenZeppelin, and veteran users who just need to quickly recall an API. Among other improvements, we've bundled together related concepts, added overviews for each section, and added crosslinks to other contracts and functions to make exploring the docsite a breeze! + +Everything is automatically generated from the comments in the source code, so if you spot a typo or have a suggestion, simply open an issue or PR to get it sorted out in no time! + +_Some sections still require a bit of work to get them to where we want them to be, stay tuned!_ + +### More + +Some more things are included in this release such as an implementation of ERC1820, and a fix for a bug in `PostDeliveryCrowdsale`. Take a look at [the changelog](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/CHANGELOG.md#230-2019-05-27)! + +[Changes][v2.3.0] + + + +# [OpenZeppelin 2.2 (v2.2.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.2.0) - 2019-03-14 + +_No changes from the release candidate for this one, we're ironing out the kinks in the release process! :no_entry_sign: :bug:_ + +This minor release includes a way to store token balances and supply so that they can be later queried in a gas-efficient manner :bookmark:, allows safe interaction with some old, non-compliant tokens :lock:, prevents user errors when using ECDSA signatures :memo: (the magic behind metatransactions! :sparkles:), and provides multiple minor additions and improvements to the API. + +To install the release run `npm install openzeppelin-solidity@latest`. + +We would love your help by reviewing newly added contracts, their interface and documentation so that we can make names clearer, features easier to use, and the library better as a whole! Your feedback is extremely useful to us :) + +## Highlights +### New features +* `ERC20Snapshot`: this variant allows for snapshots to be created on demand, storing the current token balances and total supply so that they can be later retrieved in a gas-efficient manner and e.g. calculate dividends at a past time. ([#1617](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1617)) + * `SafeERC20`: the `ERC20` standard requires that all function calls (e.g. `transfer`, `approve`, etc.) return a boolean value indicating success. However, they are multiple widely used tokens out there that return no such value: they simply `revert` when encountering an error condition. Since Solidity v0.4.22, special code was needed to interact with this non-compliant tokens: now, all of `SafeERC20` can be used to safely call both compliant and non-compliant tokens, without the developer having to worry about it. ([#1655](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1655)) +* `TimedCrowdsale`: an internal `_extendTime(uint256 newClosingTime)` function was added (with a corresponding `TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime)` event) allowing for users to safely develop mechanisms to extend the durations of unclosed crowdsales. Note that due to it being internal, there's no out-of-the-box way to do it: this feature is opt-in and must be explicitly invoked by users. + +### Improvements +* `ECDSA`: `recover` no longer accepts malleable signatures (those using upper-range values for `s`, or 0/1 for `v`). This helps prevent multiple issues when using signatures as unique identifiers. Read more about common ECDSA issues [here](https://yondon.blog/2019/01/01/how-not-to-use-ecdsa/). ([#1622](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1622)) + * `ERC721`'s transfers are now more gas efficient due to removal of unnecessary `SafeMath` calls. ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610)) + +### Bugfixes: + * (minor) `SafeERC20`: `safeApprove` wasn't properly checking for a zero allowance when attempting to set a non-zero allowance. This bug was reported independently by [@nikeshnazareth](https://github.com/nikeshnazareth). Thanks a lot! ([#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647)) + +### Breaking changes in drafts: + * `TokenMetadata` has been renamed to `ERC20Metadata`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618)) + * The library `Counter` has been renamed to `Counters` and its API has been improved. See an example in `ERC721`, lines [17](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L17) and [204](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/3cb4a00fce1da76196ac0ac3a0ae9702b99642b5/contracts/token/ERC721/ERC721.sol#L204). ([#1610](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1610)) + + +You can also see all details of this release in our [changelog](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/CHANGELOG.md#220-2019-03-14). + +[Changes][v2.2.0] + + + +# [OpenZeppelin 2.1.3 (v2.1.3)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.1.3) - 2019-02-26 + +### Bugfix release :bug: :wrench: + +A minor issue with `SafeERC20.safeApprove` was identified and reported independently by [@nikeshnazareth](https://github.com/nikeshnazareth) (thanks once again!), this release contains the correspondig fix: [OpenZeppelin/openzeppelin-solidity#1647](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1647). + +This bug has been present since v2.0.0. Updating to this latest version is recommended, but no immediate emergency action should be required for production code using affected versions, due to the low severity of the issue. + +These independent reviews are a great way to keep our code secure and correct: we'll be making a push for a properly funded bug bounty during these next weeks to continue encouraging them. Stay tuned! + +[Changes][v2.1.3] + + + +# [OpenZeppelin 2.0.1 (v2.0.1)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.1) - 2019-02-26 + +### Bugfix release :bug: :wrench: + +This is a backport of the 2.1.3 bugfix release for the 2.0.x line, which features Solidity v0.4.25 support: if you're still using OpenZeppelin v2.0.0, you can upgrade to this version instead of migrating to v2.1 and Solidity v0.5. + +[Changes][v2.0.1] + + + +# [OpenZeppelin 2.1.2 (v2.1.2)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.1.2) - 2019-03-01 + +This release was mostly the migration from Truffle 4 to Truffle 5, which should not affect end users. + +The only user facing change here is removing the tests and tests helpers from the npm package. If you used the test helpers, you will now find them in the [`openzeppelin-test-helpers`](https://github.com/OpenZeppelin/openzeppelin-test-helpers) package. + +[Changes][v2.1.2] + + + +# [OpenZeppelin 2.1 (v2.1.1)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.1.1) - 2019-01-04 + +### 2.1 is finally out! :tada: + +The most significant change is that OpenZeppelin now **works with Solidity 0.5.0**. This new release of the compiler introduced many breaking changes, and our old contracts were no longer compatible with it. After much discussion, we've decided to _drop the Solidity compiler version out of our stability guarantees_: in an attempt to both use the best possible tools and push the industry forward, our releases will target a recent compiler version, which may change between minor releases. + +This means that installing this new OpenZeppelin version will require you to upgrade your compiler to the 0.5.x line, which can be easily done with the recently released [`truffle v5.0.0`](https://github.com/trufflesuite/truffle/releases/tag/v5.0.0). [The 2.0 release](https://github.com/OpenZeppelin/openzeppelin-solidity/releases/tag/v2.0.0) will be the last OpenZeppelin release with support for Solidity ^0.4.24, which we will still support in the form of bugfixes, if any are found. + +In general, if you're not sure whether you'll want to upgrade your compiler version, feel free to pin an OpenZeppelin version during installation: :pushpin: + +`npm install openzeppelin-solidity@2.1 --save-exact` + +If you want to know more about our rationale behind this decision, and why we discarded other possible approaches, read [here](https://github.com/OpenZeppelin/openzeppelin-solidity/issues/1498#issuecomment-449191611). + +### Highlights + * Added `WhitelistCrowdsale`, a crowdsale where only whitelisted accounts (`WhitelistedRole`) can purchase tokens. Adding or removing accounts from the whitelist is done by whitelister admins (`WhitelistAdminRole`). Similar to the pre-2.0 `WhitelistedCrowdsale`. ([#1525](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1525), [#1589](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1589)) + * `ERC20`'s `transferFrom` and `_burnFrom ` now emit `Approval` events, to represent the token's state comprehensively through events. ([#1524](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1524)) + * `SignedSafeMath` now supports signed integers (`int256`). ([#1559](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1559), [#1588](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1588)) + * `ERC20` and `ERC721` are now more gas efficient due to removed redundant `SSTORE`s and `require`s. ([#1409](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1409) and [#1549](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1549)) + +The first 2.1 release will be 2.1.1, due to a minor mishap that caused a conflict in the npm registry :man_facepalming: + +:new: See the details in our brand new [CHANGELOG](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.1.1/CHANGELOG.md#210-2019-04-01)! + +[Changes][v2.1.1] + + + +# [OpenZeppelin 2.0 (v2.0.0)](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v2.0.0) - 2018-10-21 + +**OpenZeppelin 2.0 is finally here!!!** + +The major feature in this release is that we are now commiting to a stable API. In the process of stabilizing we've also reviewed a lot of the existing API in order to ensure a more straightforward experience for users. + +## Featuring... + +### Stable API + +So far OpenZeppelin's API has sometimes changed from release to release, in backwards-incompatible ways. This has enabled us to iterate on features and design ideas, but we're at a point now where we want to commit to having a stable API and delivering reliable updates. + +You can expect the external and internal API of contracts to remain stable. We're only making an exception to this for the contracts in the `drafts/` subdirectory; this is where ERCs in Draft status, as well as more experimental contracts will go, and might have breaking changes in minor versions. We'll be documenting exactly what stability guarantees we provide in the coming weeks. + +### Granular permissions + +Features which require permissions have used the almighty `Ownable` so far. We are now moving towards a more granular system of _roles_, like the [`MinterRole`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.0.0/contracts/access/roles/MinterRole.sol). Just like `Ownable`, the creator of a contract is assigned all roles at first, but they can selectively give them out to other accounts. + +### Improved test suite + +Although this is not visible to users, we have been improving the test suite, increasing coverage to 100%, and cleaning up all of our tests, which had diverged in style. This is part of a bigger effort towards making contributing easier and involving our amazing contributors more in the entire process of building OpenZeppelin. + +### A new audit + +The awesome [LevelK](https://www.levelk.io/) team audited our 2.0.0 Release Candidate and they found some severe issues and suggested many improvements. We fixed almost all the issues and notes they reported, leaving only a few minor details for 2.1.0. Check out the [LevelK Audit - OpenZeppelin 2.0 project](https://github.com/OpenZeppelin/openzeppelin-solidity/projects/2) for all the details. + +We want to thank [@cwhinfrey](https://github.com/cwhinfrey), [@pcowgill](https://github.com/pcowgill) and [@shanefontaine](https://github.com/shanefontaine) for their very detailed reviews, high quality standards, and human support during the closing phase of this release. This audit gave us a great confidence boost on the code that we are now publishing. + +### Tons of community love + +Now hold your breath, because this release was only possible because of the contributions of many, many people from everywhere in the world, and we want to thank all of them: + +[@3sGgpQ8H](https://github.com/3sGgpQ8H), [@Aniket-Engg](https://github.com/Aniket-Engg), [@barakman](https://github.com/barakman), [@BrendanChou](https://github.com/BrendanChou), [@cardmaniac992](https://github.com/cardmaniac992), [@dougiebuckets](https://github.com/dougiebuckets), [@dwardu](https://github.com/dwardu), [@facuspagnuolo](https://github.com/facuspagnuolo), [@fulldecent](https://github.com/fulldecent), [@glesaint](https://github.com/glesaint), [@Glisch](https://github.com/Glisch), [@jacobherrington](https://github.com/jacobherrington), [@jbogacz](https://github.com/jbogacz), [@jdetychey](https://github.com/jdetychey), [@JeanoLee](https://github.com/JeanoLee), [@k06a](https://github.com/k06a), [@lamengao](https://github.com/lamengao), [@ldub](https://github.com/ldub), [@leonardoalt](https://github.com/leonardoalt), [@Miraj98](https://github.com/Miraj98), [@mswezey23](https://github.com/mswezey23), [@pw94](https://github.com/pw94), [@shishir99111](https://github.com/shishir99111), [@sohkai](https://github.com/sohkai), [@sweatyc](https://github.com/sweatyc), [@tinchoabbate](https://github.com/tinchoabbate), [@tinchou](https://github.com/tinchou), [@urvalla](https://github.com/urvalla), [@viquezclaudio](https://github.com/viquezclaudio), [@vyomshm](https://github.com/vyomshm), [@yaronvel](https://github.com/yaronvel), [@ZumZoom](https://github.com/ZumZoom). + +Also we would like to thank all the people who are constantly helping others in [our Slack channel](https://slack.openzeppelin.org/), the ones who have given us feedback about the release, and the ones helping us triage and discuss our GitHub issues. If you are reading this wanting to jump in and make your first free software contributions, but you are unsure of where and how, talk to us! We can help you getting started, and we could use the extra hands. + +With ❤️ from the maintainers team of this release. +-- [@shrugs](https://github.com/shrugs), [@nventuro](https://github.com/nventuro), [@frangio](https://github.com/frangio) and [@elopio](https://github.com/elopio) + +## Changelog + +The changelog is pretty big. We are introducing new concepts and new designs, together with many renames and restructures. If you have problems, comments or suggestions, please join [our Slack channel](https://slack.openzeppelin.org/). + +https://github.com/OpenZeppelin/openzeppelin-solidity/compare/v1.12.0...v2.0.0 + +- `Ownable` contracts have moved to role based access. ([#1291](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1291), [#1302](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1302), [#1303](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1303)) +- ERC contracts have all been renamed to follow the same convention. The interfaces are called `IERC##`, and their implementations are `ERC##`. Check out, for example, [`IERC20`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.0.0/contracts/token/ERC20/IERC20.sol) and [`ERC20`](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v2.0.0/contracts/token/ERC20/ERC20.sol). ([#1252](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1252), [#1288](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1288)) +- All state variables are now `private`, which means that derived contracts cannot access them directly, but have to use getters. This is to increase encapsulation, to be able to reason better about the code. ([#1197](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1197), [#1265](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1265), [#1267](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1267), [#1269](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1269), [#1270](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1270), [#1268](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1268), [#1281](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1281)) +- Events have been changed to be consistently in the past tense except for those which are defined by an ERC. ([#1181](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1181)) +- Separated `ERC721` into the different optional interfaces, and introduced `ERC721Full` which implements all. ([#1304](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1304)) +- Added `ERC165Query` to query support for ERC165 interfaces. ([#1086](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1086)) +- Added an experimental contract for migration between ERC20 tokens. ([#1054](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1054)) +- Added `SafeMath.mod`. ([#915](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/915)) +- Added `Math.average`. ([#1170](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1170)) +- Added `ERC721Pausable`. ([#1154](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1154)) +- Changed `SafeMath` to use `require` instead of `assert`. ([#1187](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1187), [#1120](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1120), interesting discussion!) +- Removed restriction on who can release funds in `PullPayments`, `PaymentSplitter`, `PostDeliveryCrowdsale`, `RefundableCrowdsale`. ([#1275](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1275)) +- Optimized `ReentrancyMutex` gas usage. ([#1155](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1155)) +- Made `ERC721.exists` internal. ([#1193](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1193)) +- Changed preconditions on `PaymentSplitter` constructor arguments. ([#1131](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1131)) +- Fixed `ERC721.getApproved` to be in compliance with spec. ([#1256](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1256)) +- Simplified interface of `IndividuallyCappedCrowdsale`. ([#1296](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1296)) +- Renamed `ERC20.decreaseApproval` to `decreaseAllowance`, and changed its semantics slightly to be more secure. ([#1293](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1293)) +- Renamed `MerkleProof.verifyProof` to `MerkleProof.verify`. ([#1294](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1294)) +- Renamed `ECRecovery` to `ECDSA`, and `AddressUtils` to `Address`. ([#1253](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1253)) +- Moved `ECDSA` and `MerkleProof` to a `cryptography/` subdirectory. ([#1253](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1253)) +- Moved `ReentrancyGuard`, and `Address` to a `utils/` subdirectory. ([#1253](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1253)) +- Renamed `proposals/` subdirectory to `drafts/`. ([#1271](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1271)) +- Moved `TokenVesting`, `SignatureBouncer` to `drafts/`. ([#1271](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1271)) +- Removed `ERC20Basic`, now there's only `ERC20`. ([#1125](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1125)) +- Removed `Math.min64` and `Math.max64`, left only the `uint256` variants. ([#1156](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1156)) +- Removed `Mint` and `Burn` events from `ERC20Mintable` and `ERC20Burnable`. ([#1305](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1305)) +- Removed underscores from event arguments. ([#1258](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1258)) +- Removed a few contracts that we thought were not generally secure enough: `LimitBalance`, `HasNoEther`, `HasNoTokens`, `HasNoContracts`, `NoOwner`, `Destructible`, `TokenDestructible`, `CanReclaimToken`. ([#1253](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1253), [#1254](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1254), [#1306](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1306)) +- Removed extensions of `Owable`: `Claimable`, `DelayedClaimable`, `Heritable`. ([#1274](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1274)) +- Renamed `AutoIncrementing` to `Counter` and moved it to `drafts\`. ((1307, [#1332](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1332)) +- Added events to roles on construction and when renouncing. ([#1329](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1329)) +- Separated `ERC721Mintable` into two contracts, one with metadata (token URI) and one without. ([#1365](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1365)) +- Added an ERC20 internal _transfer function. ([#1370](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1370)) +- Added an `Arrays` library. ([#1375](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1375)) +- Improved the `OwnershipTransfer` event and removed `OwnershipRenounced`. ([#1397](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1397)) +- Removed the `BreakInvariantBounty` contract because of a front-running issue. ([#1424](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1424)) +- Improved encapsulation on `ERC165` making the `_supportedInterfaces` map private. ([#1379](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1379)) +- Renamed `RefundsEscrow` event to `RefundsClosed`. ([#1418](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1418)) +- Moved `Escrow` and `RefundsEscrow` to `contracts/payment/escrow/`. ([#1430](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1430)) +- Made private the `TokenVesting` functions `_releasableAmount` and `_vestedAmount`. ([#1427](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1427)) +- Made internal the constructors of contracts that should only be used inherited from others. ([#1433](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1433), [#1439](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1439)) +- Renamed `ERC165` function `supportsInterfaces` to `_supportsAllInterfaces`. ([#1435](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1435)) +- Added the `address` to `Paused` and `Unpaused` events. ([#1410](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1410)) +- Renamed `SplitPayment` to `PaymentSplitter`, and added the events `PayeeAdded`, `PaymentReleased` and `PaymentReceived`. ([#1417](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1417)) +- Renamed the `TokenVesting` events to `TokensReleased` and `TokenVestingRevoked`. ([#1431](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1431)) +- Improved the `SafeERC20` allowance handling. ([#1407](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1407)) +- Made `getCurrentRate` from `IncreasingPriceCrowdsale` return 0 when the crowdsale is not open. ([#1442](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1442)) +- Made `tokenURI` from `ERC721Metadata` external, to match the specification. ([#1444](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1444)) +- Fixed a reentrancy issue on `FinalizableCrowdsale`. ([#1447](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1447)) +- Fixed how allowance crowdsale checks remaining tokens. ([#1449](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1449)) +- Added the nonReentrant safeguard for buyTokens in the Crowdsale contract. ([#1438](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1438)) + +[Changes][v2.0.0] + + + +# [v1.12.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.12.0) - 2018-08-11 + +And thus concludes another release cycle of OpenZeppelin! :smile: + +Among other things, we have been busy enhancing the quality and consistency of the test suite. We think this will improve the experience for future contributors. Check it out! :bookmark_tabs: :raised_hands: + +This is the last release before our planned 2.0 release, which will mark a commitment to a stable API. Keep an eye out for it! :eye: + +# Changelog + +https://github.com/OpenZeppelin/openzeppelin-solidity/compare/v1.11.0...v1.12.0 + +## Additions +- We now have a [code of conduct](https://github.com/OpenZeppelin/openzeppelin-solidity/blob/v1.12.0/CODE_OF_CONDUCT.md)! ([#1061](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1061)) +- A small library with a `Counter` datatype. ([#1023](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1023)) +- A description in the README of the different categories of contracts we have and their organization. ([#1089](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1089)) + +## Improvements +- Moved ERC165 interface IDs to interface contracts. ([#1070](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1070)) +- Moved `RBAC` contract to the `access` directory. ([#1114](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1114)) +- Fixed an inheritance order that was causing some contracts to fail linearization. ([#1128](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1128)) +- Lots of test improvements, including the removal of Babel. ([#1009](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1009), [#1050](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1050), [#1074](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1074), [#1094](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1094), [#1116](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1116), [#1112](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1112), [#1117](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1117)) +- Fix `assertRevert` test helper. ([#1123](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1123)) +- Some gas optimizations. ([#1043](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1043), [#1063](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1063), [#1030](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1030), [#1017](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1017), [#1057](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1057)) +- Removed unnecessary `payable` constructor from `Destructible`. ([#1107](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1107)) +- Documentation tidbits. ([#1035](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1035), [#1082](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1082), [#1084](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1084), [#1083](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1083), [#1060](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1060), [#1101](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1101)) +- Made code style more consistent with prefix underscore in all arguments. ([#1133](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1133)) +- Fixes for Solidity 0.5.0. ([#1080](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1080), [#1134](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1134)) +- Silenced a compilation warning in `HasNoTokens`. ([#1122](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1122)) + +[Changes][v1.12.0] + + + +# [v1.11.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.11.0) - 2018-07-13 + +We hit our 1000th issue during this release cycle! Congrats to everyone and thank you for the hard work. :smile: + +# Changelog +https://github.com/OpenZeppelin/openzeppelin-solidity/compare/v1.10.0...v1.11.0 + +## Added +- :card_file_box: `Escrow`, a new class of contracts that we used to enhance the security of `PullPayments`. ([#1014](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1014)) +- :writing_hand: `isValidSignatureAndData`, a new method of `SignatureBouncer` to validate signed function calls. ([#973](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/973)) +- :memo: Initial implementation of ERC1046. ([#933](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/933)) + +## Changed +- :volcano: Updated the ERC721 contracts to the final version of the protocol. ([#972](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/972), [#993](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/993), [#1047](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1047)) +- :shark: Updated minor things for the newer versions of Solidity. ([#951](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/951), [#1002](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1002), [#1008](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1008), [#1033](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1033)) +- :shield: Fixed unchecked token transfer in `Crowdsale`. ([#1006](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1006)) +- :seat: Moved `Whitelist` to `access` directory. ([#994](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/994)) + +## Removed +- :warning: We removed the implementation of ERC827 due to concerns about its security ([#1044](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/1044)). The code was moved to [windingtree/erc827](https://github.com/windingtree/erc827). + +[Changes][v1.11.0] + + + +# [v1.10.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.10.0) - 2018-06-05 + +The release includes the new `constructor` syntax in Solidity (goodbye warnings :wave:). + +:tada: :tada: :tada: + +# Changelog +https://github.com/OpenZeppelin/zeppelin-solidity/compare/v1.9.0...v1.10.0 +- Updated contracts for Solidity 0.4.23 including the new `constructor` syntax ([OpenZeppelin/openzeppelin-solidity#921](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/921)) +- Added `renounceOwnership` to `Ownable` ([OpenZeppelin/openzeppelin-solidity#907](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/907)) +- Added `Superuser`, an extension of `Ownable` with an emergency mechanism ([OpenZeppelin/openzeppelin-solidity#952](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/952), [OpenZeppelin/openzeppelin-solidity#978](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/978)) +- Added an `Ownable` "behavior" to test that your ownable contracts do not break the semantics ([OpenZeppelin/openzeppelin-solidity#929](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/929)) + +[Changes][v1.10.0] + + + +# [v1.9.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.9.0) - 2018-04-27 + +:warning: Beginning with this release we're renaming the npm package to **openzeppelin-solidity**. Make sure to update your dependencies! + +The release includes the new `emit` keyword in Solidity, some new functionality, and other enhancements. Thanks everyone for participating! + +:tada: :tada: :tada: + +# Changelog +https://github.com/OpenZeppelin/zeppelin-solidity/compare/v1.8.0...v1.9.0 +- :ribbon: Updated our contracts for Solidity 0.4.21, including the new `emit` keyword ([#876](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/876)) +- :fire: Added `StandardBurnableToken` with a `burnFrom` function ([#870](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/870)) +- :woman_teacher: Changed `MerkleProof` interface slightly ([#879](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/879)) +- :policewoman: Removed admin functionality from RBAC ([#836](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/836)) +- :memo: Changes to ERC827 ([#871](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/871), [#838](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/838)) +- :wrench: Cleaned up the npm package files and dependencies ([#843](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/843), [#904](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/904)) + +[Changes][v1.9.0] + + + +# [v1.8.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.8.0) - 2018-03-23 + +This release contains the full implementation of EIP721, following the last details settled in the [recently closed EIP](https://github.com/ethereum/EIPs/blob/a235dd38b9434bd3f6a8eb4d71550a2f1ef0ce24/EIPS/eip-721.md). Thanks to all the community for your contributions! 🚀 + +# Changelog +https://github.com/OpenZeppelin/zeppelin-solidity/compare/v1.7.0...v1.8.0 +- ✨ Final EIP721 implementation ([#803](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/803)) +- 🔥 Add `Transfer` event to [`BurnableToken`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.8.0/contracts/token/ERC20/BurnableToken.sol) ([#735](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/735)) +- 👨‍🏫 `ECRecovery` [`recover`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.8.0/contracts/ECRecovery.sol#L17) is now internal ([#818](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/818)) +- 💅 Documentation and tests enhancements + + +[Changes][v1.8.0] + + + +# [v1.7.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.7.0) - 2018-02-20 + +This release contains a big refactor of the `Crowdsale` contract, which allowed us to implement some really cool new crowdsale models. We also have a [shiny new documentation site](https://openzeppelin.org/api/docs/open-zeppelin.html). Thanks to all the community for the awesome contributions! :rocket: + + +# Changelog + +https://github.com/OpenZeppelin/zeppelin-solidity/compare/v1.6.0...v1.7.0 +- :warning: Big `Crowdsale` refactor, including breaking changes ([OpenZeppelin/zeppelin-solidity#744](https://github.com/OpenZeppelin/zeppelin-solidity/pull/744)) +- :new: new crowdsale models ([OpenZeppelin/zeppelin-solidity#744](https://github.com/OpenZeppelin/zeppelin-solidity/pull/744)) + - [`WhitelistedCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/validation/WhitelistedCrowdsale.sol) + - [`IndividuallyCappedCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol), + - [`PostDeliveryCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/distribution/PostDeliveryCrowdsale.sol) + - [`AllowanceCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/emission/AllowanceCrowdsale.sol) + - [`IncreasingPriceCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/price/IncreasingPriceCrowdsale.sol) + - Original `Crowdsale` contract refactored into [`Crowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/Crowdsale.sol), [`TimedCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/validation/TimedCrowdsale.sol) and [`MintedCrowdsale`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.7.0/contracts/crowdsale/emission/MintedCrowdsale.sol). +- :bow_and_arrow: Move token creation outside of `Crowdsale` contract ([OpenZeppelin/zeppelin-solidity#690](https://github.com/OpenZeppelin/zeppelin-solidity/pull/690)) +- :crown: `Heritable` improvements ([OpenZeppelin/zeppelin-solidity#702](https://github.com/OpenZeppelin/zeppelin-solidity/pull/702)) + +# Project updates: +- :blue_book: [New documentation site](https://openzeppelin.org/api/docs/open-zeppelin.html)! ([OpenZeppelin/zeppelin-solidity#750](https://github.com/OpenZeppelin/zeppelin-solidity/pull/750)) +- :octocat: Update GitHub Pull Request templates ([OpenZeppelin/zeppelin-solidity#699](https://github.com/OpenZeppelin/zeppelin-solidity/pull/699)) +- :wrench: Minor tweaks for test artifact imports ([OpenZeppelin/zeppelin-solidity#698](https://github.com/OpenZeppelin/zeppelin-solidity/pull/698)) +- :male_detective: Improve test coverage ([OpenZeppelin/zeppelin-solidity#712](https://github.com/OpenZeppelin/zeppelin-solidity/pull/712)) + + +[Changes][v1.7.0] + + + +# [v1.6.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.6.0) - 2018-01-23 + +This time we bring you a new release, which includes the much hyped ERC721 for non-fungible tokens, to create your own digital collectibles and more. 🐈 🌍 🚀 🎉 + +During this release cycle the team has been very active improving the development process itself, and we're already seeing great results in the speed with which we respond to new issues and PRs. Take a peek at the status of development at [our waffle.io board](https://waffle.io/OpenZeppelin/zeppelin-solidity). + +We'll be waiting for your contributions! + +# Changelog + +[v1.5.0...v1.6.0](https://github.com/OpenZeppelin/zeppelin-solidity/compare/v1.5.0...v1.6.0) + +- 🆕 Added [`ERC721`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.6.0/contracts/token/ERC721/ERC721Token.sol) non-fungible token implementation ([#615](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/615)) 🐈 +- 🆕 Added [`ERC827`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.6.0/contracts/token/ERC827/ERC827Token.sol) token implementation provides `transfer`, `transferFrom` and `approve` methods which additionally perform a call to the recipients ([#518](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/518)) +- 🆕 Added [`Heritable`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.6.0/contracts/lifecycle/Heritable.sol), an extension of `Ownable` with a designated heir ([#680](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/680)) +- Added [`getTokenAmount`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.6.0/contracts/crowdsale/Crowdsale.sol#L92-L95) for dynamic rate crowdsales ([#638](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/638)) +- Added the `totalSupply` function to the ERC20 interface ([#666](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/666)) + +## Project changes +- Enhanced tests and documentation +- Updated documentation for contributors ([#684](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/684)) +- Added Issue/PR templates ([#641](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/641)) +- Added linting configuration and Travis CI step ([#673](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/673)) + +[Changes][v1.6.0] + + + +# [v1.5.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.5.0) - 2017-12-22 + +A small release this time to keep the release cycle going! 🚀 + +# Changelog + +- 🆕 Added [`RBAC`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.5.0/contracts/ownership/rbac/RBAC.sol) to enable more complex access control patterns. ([#580](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/580)) +- Several enhancements to project quality. ([#581](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/581)) +- And a few small changes. 🙂 + +[Changes][v1.5.0] + + + +# [v1.4.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.4.0) - 2017-11-23 + +Thanks to all members of the community that contributed to this release! 🎉 🚀 + +# Changelog + +- 🆕 Added [`TokenVesting`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.4.0/contracts/token/TokenVesting.sol) which implements vesting of tokens. It replaces the old [`VestedToken`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.3.0/contracts/token/VestedToken.sol) with a more modular approach. ([#476](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/476)) +- 🆕 Added [`SplitPayment`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.4.0/contracts/payment/SplitPayment.sol) which implements distributing payments to multiple people proportionally to shares. ([#417](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/417)) +- 🆕 Added [`DetailedERC20`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.4.0/contracts/token/DetailedERC20.sol) which adds to a token state variables with the optional ERC20 metadata. ([#477](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/477)) +- 🆕 Added [`CappedToken`](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.4.0/contracts/token/CappedToken.sol) which is a `MintableToken` with capped supply. ([#515](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/515)) +- Made `MintableToken`'s `finishMinting` executable only once. ([#505](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/505)) +- Upgraded to Truffle 4.0.1 and Solidity 0.4.18. ([#573](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/573), [#460](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/460), [#576](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/576), [#506](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/506)) +- Removed deprecated `claim()` from `TokenTimelock`. ([#469](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/469)) + +And some additional changes to code style, tests, documentation and continuous integration. + +[Changes][v1.4.0] + + + +# [v1.3.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.3.0) - 2017-09-21 + +After a long wait, we're finally releasing version 1.3.0 of OpenZeppelin. This is a big release with a lot of small fixes, exciting new features, and enhancements to the developer experience. + +This release includes commits from 29 contributors! Huge thanks to all of you! 🎉 🎉 + +## Changelog + +- Removed `MultisigWallet` in favor of [gnosis/MultiSigWallet](https://github.com/gnosis/MultiSigWallet). ([#328](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/328)) +- Added [a directory](https://github.com/OpenZeppelin/zeppelin-solidity/blob/v1.3.0/contracts/examples) with examples. ([#333](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/333), [#342](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/342)) +- Migrated the crowdsale contracts to timestamps instead of block numbers. ([#350](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/350)) +- Removed the call to `finishMinting` in `FinalizableCrowdsale`. ([#364](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/364)) +- Made `approve` pausable in `PausableToken`. ([#448](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/448)) +- Added an `OwnershipTransferred` event. ([#424](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/424)) +- Added the `BurnableToken` contract. ([#341](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/341)) +- Added the `CanReclaimToken` contract. ([#348](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/348)) +- Added the `SafeERC20` library for interaction with ERC20 tokens. ([#413](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/413)) +- Added the `MerkleProof` library for merkle proof verification. ([#260](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/260)) +- Fixed some small issues in ERC20 compliance. ([#345](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/345), [#405](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/405), [#446](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/446)) +- Fixed a bug in `transferFrom`. ([#377](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/377)) +- Fixed `transferOwnership` to `revert` on failure instead of silently failing. ([#323](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/323)) +- Fixed a bug in `TokenTimelock`. ([#430](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/430)) +- Several enhancements to tests and documentation. +- Parallelized coverage and tests in Travis for faster test results in PRs. ([#369](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/369)) +- Removed the only production dependency (was actually a dev dependency). Now installing via `npm install --only=prod zeppelin-solidity` should install zero extra dependencies! ([#357](https://github.com/OpenZeppelin/openzeppelin-contracts/issues/357)) + + +[Changes][v1.3.0] + + + +# [v1.2.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.2.0) - 2017-07-18 + +# Changelog + +- Fix ERC20 interface and implementations to [conform to standard](https://github.com/ethereum/EIPs/pull/610). +- Rename `claim` to `release` in `TokenTimelock`. +- Bugfixes in `VestedToken`. +- Deprecated `throw` in favor of `require()`, `assert()` and `revert()`. +- Small improvements on crowdsale contracts. +- Added `ECRecovery` library. + + + + + +[Changes][v1.2.0] + + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.1.0) - 2017-07-02 + +# Changelog +- Add [Crowdsale contracts](https://github.com/OpenZeppelin/zeppelin-solidity/tree/f507a0ea29f44bebb1e3d94fcc97ea5808915dab/contracts/crowdsale). +- Add a [TokenTimelock contract](https://github.com/OpenZeppelin/zeppelin-solidity/blob/f507a0ea29f44bebb1e3d94fcc97ea5808915dab/contracts/token/TokenTimelock.sol). +- Add coveralls and move npm to yarn for TravisCI. +- Upgrade to Truffle version 3.2.2 and Solidity version 0.4.11 +- Other minor refactors and fixes. +- Extract some functions of `SafeMath` into `Math`. +- Remove all checks for short address attack (see: [OpenZeppelin/zeppelin-solidity#261](https://github.com/OpenZeppelin/zeppelin-solidity/issues/261)) + + + +[Changes][v1.1.0] + + + +# [v1.0.6](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.0.6) - 2017-05-29 + +# Changelog +- Add external audit report to repo. +- Minor improvements to `Destructible`. +- Revert to usig npm as preferred installation method, as ethpm is still immature. +- Add solidity-coverage +- Add natspec documentation +- Add revokability and burnablity options to `VestedToken` and general refactor and optimizations +## Security +- Add fix for the `approve()` mitigation. +- Protect `transferFrom` against short hand attack. +- Fix attack on `VestedToken#grantVestedTokens()` + +Thanks to [@misteraverin](https://github.com/misteraverin), [@miohtama](https://github.com/miohtama), [@izqui](https://github.com/izqui), [@cgewecke](https://github.com/cgewecke), [@maurelian](https://github.com/maurelian), [@JGcarv](https://github.com/JGcarv) and [@DavidKnott](https://github.com/DavidKnott) for your contributions to this release! + +[Changes][v1.0.6] + + + +# [v1.0.5](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.0.5) - 2017-05-09 + +# Changelog: +- Added new `TokenDestructible` lifecycle helper for contracts that want to transfer owned tokens when destroyed. +- Decouple transferable logic from `VestedToken` as `LimitedTransferToken`. +- Added new ownership helpers `HasNoEther`, `HasNoContracts`, `NoOwner`. +- Added `ReentrancyGuard` to prevent contract from calling itself, directly or indirectly. +- Several refactors and small fixes. +- New `MintableToken` token with minting functions. +- New `PausableToken` token with pausable transfers (it's a `Pausable` instance) +- Make SafeMath a library. +- External audit security fixes. (Audit link will be published soon). + +Thanks to [@Recmo](https://github.com/Recmo), [@izqui](https://github.com/izqui), [@demibrener](https://github.com/demibrener), [@frangio](https://github.com/frangio), [@roderik](https://github.com/roderik), [@jdetychey](https://github.com/jdetychey), [@DavidKnott](https://github.com/DavidKnott), [@lastperson](https://github.com/lastperson), [@tatiesmars](https://github.com/tatiesmars), [@AugustoL](https://github.com/AugustoL), [@ORBAT](https://github.com/ORBAT) for your contributions! This release wouldn't be the same without your work. + + + +[Changes][v1.0.5] + + + +# [v1.0.4](https://github.com/OpenZeppelin/openzeppelin-contracts/releases/tag/v1.0.4) - 2017-03-09 + +- Add integration and publish to [ethpm](https://www.ethpm.com/). + +[Changes][v1.0.4] + + +[v5.4.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v5.3.0...v5.4.0 +[v5.3.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v5.2.0...v5.3.0 +[v5.2.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v5.1.0...v5.2.0 +[v5.1.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v5.0.2...v5.1.0 +[v5.0.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.6...v5.0.2 +[v4.9.6]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.5...v4.9.6 +[v4.9.5]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v5.0.1...v4.9.5 +[v5.0.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.4...v5.0.1 +[v4.9.4]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v5.0.0...v4.9.4 +[v5.0.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.3...v5.0.0 +[v4.9.3]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.2...v4.9.3 +[v4.9.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.1...v4.9.2 +[v4.9.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.9.0...v4.9.1 +[v4.9.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.8.3...v4.9.0 +[v4.8.3]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.8.2...v4.8.3 +[v4.8.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.8.1...v4.8.2 +[v4.8.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.8.0...v4.8.1 +[v4.8.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.7.3...v4.8.0 +[v4.7.3]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.7.2...v4.7.3 +[v4.7.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.7.1...v4.7.2 +[v4.7.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.7.0...v4.7.1 +[v4.7.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.6.0...v4.7.0 +[v4.6.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.5.0...v4.6.0 +[v4.5.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.4.2...v4.5.0 +[v4.4.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.4.1...v4.4.2 +[v4.4.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.4.0...v4.4.1 +[v4.4.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.3.3...v4.4.0 +[v4.3.3]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.3.2...v4.3.3 +[v4.3.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.3.1...v4.3.2 +[v4.3.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.4.2-solc-0.7...v4.3.1 +[v3.4.2-solc-0.7]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.4.2...v3.4.2-solc-0.7 +[v3.4.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.3.0...v3.4.2 +[v4.3.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.2.0...v4.3.0 +[v4.2.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.1.0...v4.2.0 +[v4.1.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v4.0.0...v4.1.0 +[v4.0.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.4.0...v4.0.0 +[v3.4.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.3.0...v3.4.0 +[v3.3.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.2.1-solc-0.7...v3.3.0 +[v3.2.1-solc-0.7]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.2.0...v3.2.1-solc-0.7 +[v3.2.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.1.0...v3.2.0 +[v3.1.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.0.1...v3.1.0 +[v3.0.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.5.1...v3.0.1 +[v2.5.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v3.0.0...v2.5.1 +[v3.0.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.5.0...v3.0.0 +[v2.5.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.4.0...v2.5.0 +[v2.4.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.3.0...v2.4.0 +[v2.3.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.2.0...v2.3.0 +[v2.2.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.1.3...v2.2.0 +[v2.1.3]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.0.1...v2.1.3 +[v2.0.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.1.2...v2.0.1 +[v2.1.2]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.1.1...v2.1.2 +[v2.1.1]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v2.0.0...v2.1.1 +[v2.0.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.12.0...v2.0.0 +[v1.12.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.11.0...v1.12.0 +[v1.11.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.10.0...v1.11.0 +[v1.10.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.9.0...v1.10.0 +[v1.9.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.8.0...v1.9.0 +[v1.8.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.7.0...v1.8.0 +[v1.7.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.6.0...v1.7.0 +[v1.6.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.5.0...v1.6.0 +[v1.5.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.4.0...v1.5.0 +[v1.4.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.3.0...v1.4.0 +[v1.3.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.1.0...v1.2.0 +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.0.6...v1.1.0 +[v1.0.6]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.0.5...v1.0.6 +[v1.0.5]: https://github.com/OpenZeppelin/openzeppelin-contracts/compare/v1.0.4...v1.0.5 +[v1.0.4]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v1.0.4 diff --git a/docs/content/contracts/5.x/eoa-delegation.mdx b/docs/content/contracts/5.x/eoa-delegation.mdx new file mode 100644 index 00000000..cc114518 --- /dev/null +++ b/docs/content/contracts/5.x/eoa-delegation.mdx @@ -0,0 +1,147 @@ +--- +title: EOA Delegation +--- + +[EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) introduces a new transaction type (`0x4`) that grants [Externally Owned Accounts (EOAs)](https://ethereum.org/en/developers/docs/accounts/) the ability to delegate execution to an smart contract. This is particularly useful to enable traditional EVM accounts to: + +* Batch multiple operations in a single transaction (e.g. [`approve`](/contracts/5.x/api/token/ERC20#IERC20-approve-address-uint256-) + [`transfer`](/contracts/5.x/api/token/ERC20#IERC20-transfer-address-uint256-), 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 [ERC-7702](https://eips.ethereum.org/EIPS/eip-7702). This allows you to use your EOA’s private key to sign and execute operations with custom execution logic. Combined with [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) infrastructure, users can achieve gas sponsoring through [paymasters](https://docs.openzeppelin.com/community-contracts/paymasters). + +## Delegating execution + +EIP-7702 enables EOAs to delegate their execution capabilities to smart contracts, effectively bridging the gap between traditional and [Smart Accounts](./accounts). The [`SignerERC7702`](/contracts/5.x/api/utils/cryptography) 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. + +./examples/account/MyAccountERC7702.sol + + +Users can delegate to an instance of [`ERC-7821`](/contracts/5.x/api/account#ERC7821) 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 [viem](https://viem.sh/): + +```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", +}); +``` + + +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. + +```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, + }) + ); + +// Print receipt +console.log(userOpReceipt); +``` + +To remove the delegation and restore your EOA to its original state, you can send a `SET_CODE_TX_TYPE` transaction with an authorization tuple that points to the zero address (`0x0000000000000000000000000000000000000000`). This will clear the account’s code and reset its code hash to the empty hash, however, be aware that it will not automatically clean the EOA storage. + +When changing an account’s delegation, ensure the newly delegated code is purposely designed and tested as an upgrade to the old one. To ensure safe migration between delegate contracts, namespaced storage that avoid accidental collisions following ERC-7201. + + +Updating the delegation designator may render your EOA unusable due to potential storage collisions. We recommend following similar practices to those of [writing upgradeable smart contracts](https://docs.openzeppelin.com/upgrades-plugins/writing-upgradeable). + + +## 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 [`Account`](/contracts/5.x/api/account#Account) contract with an [`SignerERC7702`](/contracts/5.x/api/utils/cryptography#SignerERC7702) to accomplish ERC-4337 compliance out of the box. + +### Sending a UserOp + +Once your EOA is delegated to an ERC-4337 compatible account, you can send user operations through the entry point contract. The user operation includes all the necessary fields for execution, including gas limits, fees, and the actual call data to execute. The entry point will validate the operation and execute it in the context of your delegated account. + +Similar to how [sending a UserOp](./accounts#batched-execution) is achieved for factory accounts, here’s how you can construct a UserOp for an EOA who’s delegated to an [`Account`](/contracts/5.x/api/account#Account) contract. + +```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); +``` + + +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. + diff --git a/docs/content/contracts/5.x/erc1155.mdx b/docs/content/contracts/5.x/erc1155.mdx new file mode 100644 index 00000000..e5df92b8 --- /dev/null +++ b/docs/content/contracts/5.x/erc1155.mdx @@ -0,0 +1,113 @@ +--- +title: ERC-1155 +--- + +ERC-1155 is a novel token standard that aims to take the best from previous standards to create a [**fungibility-agnostic**](./tokens#different-kinds-of-tokens) and **gas-efficient** [token contract](./tokens#but-first-coffee-a-primer-on-token-contracts). + + +ERC-1155 draws ideas from all of [ERC-20](./erc20), [ERC-721](./erc721), and [ERC-777](https://eips.ethereum.org/EIPS/eip-777). If you’re unfamiliar with those standards, head to their guides before moving on. + + +## 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 [`balanceOf`](/contracts/5.x/api/token/ERC1155#IERC1155-balanceOf-address-uint256-) 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 [`balanceOf`](/contracts/5.x/api/token/ERC721#IERC721-balanceOf-address-) 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 + +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, [`balanceOfBatch`](/contracts/5.x/api/token/ERC1155#IERC1155-balanceOfBatch-address---uint256---) and [`safeBatchTransferFrom`](/contracts/5.x/api/token/ERC1155#IERC1155-safeBatchTransferFrom-address-address-uint256---uint256---bytes-), 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 [`_mintBatch`](/contracts/5.x/api/token/ERC1155#ERC1155-_mintBatch-address-uint256---uint256---bytes-). + +## 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. + + +For an overview of minting mechanisms, check out [Creating ERC-20 Supply](./erc20-supply). + + +Here’s what a contract for tokenized items might look like: + +./examples/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 [`ERC1155`](/contracts/5.x/api/token/ERC1155#ERC1155) contract includes the optional extension [`IERC1155MetadataURI`](/contracts/5.x/api/token/ERC1155#IERC1155MetadataURI). That’s where the [`uri`](/contracts/5.x/api/token/ERC1155#IERC1155MetadataURI-uri-uint256-) 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: +```javascript +> gameItems.balanceOf(deployerAddress,3) +1000000000 +``` + +We can transfer items to player accounts: +```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: +```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: + +```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: + +```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 [ERC-1155 Metadata URI JSON Schema](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1155.md#erc-1155-metadata-uri-json-schema). + + +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! + + + +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 [`Base64`](./utilities#base64) Data URI with the JSON schema encoded. You could also leverage IPFS to store the URI information, but these techniques are out of the scope of this overview guide + + +## Sending Tokens to Contracts + +A key difference when using [`safeTransferFrom`](/contracts/5.x/api/token/ERC1155#IERC1155-safeTransferFrom-address-address-uint256-uint256-bytes-) is that token transfers to other contracts may revert with the following custom error: + +```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, [the Golem contract currently holds over 350k `GNT` tokens](https://etherscan.io/token/0xa74476443119A942dE498590Fe1f2454d7D4aC0d?a=0xa74476443119A942dE498590Fe1f2454d7D4aC0d), 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 [`ERC1155Holder`](/contracts/5.x/api/token/ERC1155#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: + +./examples/token/ERC1155/MyERC115HolderContract.sol + +We can also implement more complex scenarios using the [`onERC1155Received`](/contracts/5.x/api/token/ERC1155#IERC1155Receiver-onERC1155Received-address-address-uint256-uint256-bytes-) and [`onERC1155BatchReceived`](/contracts/5.x/api/token/ERC1155#IERC1155Receiver-onERC1155BatchReceived-address-address-uint256---uint256---bytes-) functions. diff --git a/docs/content/contracts/5.x/erc20-supply.mdx b/docs/content/contracts/5.x/erc20-supply.mdx new file mode 100644 index 00000000..7b676067 --- /dev/null +++ b/docs/content/contracts/5.x/erc20-supply.mdx @@ -0,0 +1,64 @@ +--- +title: 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 [`ERC20`](/contracts/5.x/api/token/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 + +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: + +```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 [`_mint`](/contracts/5.x/api/token/ERC20#ERC20-_mint-address-uint256-) function that will do exactly this: + +```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 Miners + +The internal [`_mint`](/contracts/5.x/api/token/ERC20#ERC20-_mint-address-uint256-) 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 miners that produce Ethereum blocks. In Solidity, we can access the address of the current block’s miner in the global variable `block.coinbase`. 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! + +```solidity +contract ERC20WithMinerReward is ERC20 { + constructor() ERC20("Reward", "RWD") {} + + function mintMinerReward() public { + _mint(block.coinbase, 1000); + } +} +``` + +As we can see, `_mint` makes it super easy to do this correctly. + +## 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 [`_update`](/contracts/5.x/api/token/ERC20#ERC20-_update-address-address-uint256-) function. + +Adding to the supply mechanism from the previous section, we can use this function to mint a miner reward for every token transfer that is included in the blockchain. + +./examples/ERC20WithAutoMinerReward.sol + +## 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. diff --git a/docs/content/contracts/5.x/erc20.mdx b/docs/content/contracts/5.x/erc20.mdx new file mode 100644 index 00000000..5f0355a4 --- /dev/null +++ b/docs/content/contracts/5.x/erc20.mdx @@ -0,0 +1,66 @@ +--- +title: ERC-20 +--- + +An ERC-20 token contract keeps track of [_fungible_ tokens](./tokens#different-kinds-of-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 [`API reference`](/contracts/5.x/api/token/ERC20) you’ll find detailed information on their properties and usage. + +## 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. + +./examples/token/ERC20/GLDToken.sol + +Our contracts are often used via [inheritance](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance), and here we’re reusing [`ERC20`](/contracts/5.x/api/token/ERC20#erc20) for both the basic standard implementation and the [`name`](/contracts/5.x/api/token/ERC20#ERC20-name--), [`symbol`](/contracts/5.x/api/token/ERC20#ERC20-symbol--), and [`decimals`](/contracts/5.x/api/token/ERC20#ERC20-decimals--) optional extensions. Additionally, we’re creating an `initialSupply` of tokens, which will be assigned to the address that deploys the contract. + + +For a more complete discussion of ERC-20 supply mechanisms, see [Creating ERC-20 Supply](./erc20-supply). + + +That’s it! Once deployed, we will be able to query the deployer’s balance: + +```javascript +> GLDToken.balanceOf(deployerAddress) +1000000000000000000000 +``` + +We can also [transfer](/contracts/5.x/api/token/ERC20#IERC20-transfer-address-uint256-) these tokens to other accounts: + +```javascript +> GLDToken.transfer(otherAddress, 300000000000000000000) +> GLDToken.balanceOf(otherAddress) +300000000000000000000 +> GLDToken.balanceOf(deployerAddress) +700000000000000000000 +``` + +## 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, [`ERC20`](/contracts/5.x/api/token/ERC20#ERC20) provides a [`decimals`](/contracts/5.x/api/token/ERC20#ERC20-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)`. + + +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)); +``` diff --git a/docs/content/contracts/5.x/erc4626.mdx b/docs/content/contracts/5.x/erc4626.mdx new file mode 100644 index 00000000..25128093 --- /dev/null +++ b/docs/content/contracts/5.x/erc4626.mdx @@ -0,0 +1,168 @@ +--- +title: ERC-4626 +--- + +[ERC-4626](https://eips.ethereum.org/EIPS/eip-4626) is an extension of [ERC-20](./erc20) 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. + +## 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. + +![Exchange rates in linear scale](/erc4626-rate-linear.png) + +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. + +![Exchange rates in logarithmic scale](/erc4626-rate-loglog.png) + +In such a representation, widely different rates can be clearly visible in the same graph. This wouldn’t be the case in linear scale. + +![More exchange rates in logarithmic scale](/erc4626-rate-loglogext.png) + +### 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. + +![Depositing assets](/erc4626-deposit.png) + +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%. + +![Minting shares](/erc4626-mint.png) + +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. + +![Inflation attack without protection](/erc4626-attack.png) + +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: + +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | Assets | Shares | Rate | +| --- | --- | --- | --- | +| initial | $0$ | $0$ | - | +| after attacker's deposit | $a_0$ | $a_0$ | $1$ | +| after attacker's donation | $a_0 + a_1$ | $a_0$ | $\fraca_0 + a_1a_0$ | + +This means a deposit of $u$ will give $\fracu \times a_0a_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 + +```math +\frac{u \times a_0}{a_0+a_1} < 1 \iff u < 1 + \frac{a_1}{a_0} +``` + +Using $a_0 = 1$ and $a_1 = u$ is enough. So the attacker only needs $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 $\fracun$, the user needs to suffer rounding of a similar fraction, which means the user must receive at most $n$ shares. This results in: + +```math +\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 $n$ times less powerful (in how much it is stealing) and costs $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 [YieldBox](https://github.com/boringcrypto/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: + +* $\delta$ the vault offset +* $a_0$ the attacker deposit +* $a_1$ the attacker donation +* $u$ the user deposit + +| | Assets | Shares | Rate | +| --- | --- | --- | --- | +| initial | $1$ | $10^\delta$ | $10^\delta$ | +| after attacker's deposit | $1+a_0$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | +| after attacker's donation | $1+a_0+a_1$ | $10^\delta \times (1+a_0)$ | $10^\delta$ | + +One important thing to note is that the attacker only owns a fraction $\fraca_01 + a_0$ of the shares, so when doing the donation, he will only be able to recover that fraction $\fraca_1 \times a_01 + a_0$ of the donation. The remaining $\fraca_11+a_0$ are captured by the vault. + +```math +\mathit{loss} = \frac{a_1}{1+a_0} +``` + +When the user deposits $u$, he receives + +```math +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 + +```math +10^\delta \times u \times \frac{1+a_0}{1+a_0+a_1} < 1 +``` + +```math +\iff 10^\delta \times u < \frac{1+a_0+a_1}{1+a_0} +``` + +```math +\iff 10^\delta \times u < 1 + \frac{a_1}{1+a_0} +``` + +```math +\iff 10^\delta \times u \le \mathit{loss} +``` + +* If the offset is 0, the attacker 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. + +![Inflation attack without offset=3](/erc4626-attack-3a.png) +$\delta = 3$, $a_0 = 1$, $a_1 = 10^5$ + +![Inflation attack without offset=3 and an attacker deposit that limits its losses](/erc4626-attack-3b.png) +$\delta = 3$, $a_0 = 100$, $a_1 = 10^5$ + +![Inflation attack without offset=6](/erc4626-attack-6.png) +$\delta = 6$, $a_0 = 1$, $a_1 = 10^5$ + +## 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: + +./examples/ERC4626Fees.sol diff --git a/docs/content/contracts/5.x/erc6909.mdx b/docs/content/contracts/5.x/erc6909.mdx new file mode 100644 index 00000000..e1a09a12 --- /dev/null +++ b/docs/content/contracts/5.x/erc6909.mdx @@ -0,0 +1,48 @@ +--- +title: 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 is to decrease gas costs and complexity--this is mainly accomplished by removing batching and callbacks. + + +To understand the inspiration for a multi token standard, see the [multi token standard](./erc1155#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: + +1. The removal of batch operations. +2. The removal of transfer callbacks. +3. 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 by minted to the deployer of the contract, which we can later transfer to players. We’ll also use the [`ERC6909Metadata`](/contracts/5.x/api/token/ERC6909#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. + + +For an overview of minting mechanisms, check out [Creating ERC-20 Supply](./erc20-supply). + + +Here’s what a contract for tokenized items might look like: + +./examples/token/ERC6909/ERC6909GameItems.sol + +Note that there is no content URI functionality in the base implementation, but the [`ERC6909ContentURI`](/contracts/5.x/api/token/ERC6909#ERC6909ContentURI) extension adds it. Additionally, the base implementation does not track total supplies, but the [`ERC6909TokenSupply`](/contracts/5.x/api/token/ERC6909#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: +```javascript +> gameItems.balanceOf(deployerAddress, 3) +1000000000 +``` + +We can transfer items to player accounts: +```javascript +> gameItems.transfer(playerAddress, 2, 1) +> gameItems.balanceOf(playerAddress, 2) +1 +> gameItems.balanceOf(deployerAddress, 2) +0 +``` diff --git a/docs/content/contracts/5.x/erc721.mdx b/docs/content/contracts/5.x/erc721.mdx new file mode 100644 index 00000000..474830b3 --- /dev/null +++ b/docs/content/contracts/5.x/erc721.mdx @@ -0,0 +1,58 @@ +--- +title: ERC-721 +--- + +We’ve discussed how you can make a _fungible_ token using [ERC-20](./erc20), 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 [_non-fungible_ tokens](./tokens#different-kinds-of-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 [API Reference](/contracts/5.x/api/token/ERC721) 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 any account can call `awardItem` to mint items. To restrict what accounts can mint items we can add [Access Control](./access-control). + +Here’s what a contract for tokenized items might look like: + +./examples/token/ERC721/GameItem.sol + +The [`ERC721URIStorage`](/contracts/5.x/api/token/ERC721#ERC721URIStorage) contract is an implementation of ERC-721 that includes the metadata standard extensions ([`IERC721Metadata`](/contracts/5.x/api/token/ERC721#IERC721Metadata)) as well as a mechanism for per-token metadata. That’s where the [`_setTokenURI`](/contracts/5.x/api/token/ERC721#ERC721-_setTokenURI-uint256-string-) 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: + +```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: + +```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: + +```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 [ERC-721 specification](https://eips.ethereum.org/EIPS/eip-721). + + +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! + + + +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 [`Base64`](./utilities#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. + diff --git a/docs/content/contracts/5.x/extending-contracts.mdx b/docs/content/contracts/5.x/extending-contracts.mdx new file mode 100644 index 00000000..4667f4d0 --- /dev/null +++ b/docs/content/contracts/5.x/extending-contracts.mdx @@ -0,0 +1,64 @@ +--- +title: Extending Contracts +--- + +Most of the OpenZeppelin Contracts are expected to be used via [inheritance](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance): you will _inherit_ from them when writing your own contracts. + +This is the commonly found `is` syntax, like in `contract MyToken is ERC20`. + + +Unlike ``contract``s, Solidity ``library``s are not inherited from and instead rely on the [`using for`](https://solidity.readthedocs.io/en/latest/contracts.html#using-for) syntax. + +OpenZeppelin Contracts has some ``library``s: most are in the [Utils](/contracts/5.x/api/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 [`AccessControl`](/contracts/5.x/api/access#AccessControl) so that [`revokeRole`](/contracts/5.x/api/access#AccessControl-revokeRole-bytes32-address-) can no longer be called. This can be achieved using overrides: + +```solidity +// contracts/AccessControlModified.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +contract AccessControlModified is AccessControl { + error AccessControlNonRevocable(); + + // Override the revokeRole function + function revokeRole(bytes32, address) public pure override { + revert AccessControlNonRevocable(); + } +} +``` + +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. + + +For more information on how overrides work, head over to the [official Solidity documentation](https://solidity.readthedocs.io/en/latest/contracts.html#index-17). + + +Here is a modified version of [`AccessControl`](/contracts/5.x/api/access#AccessControl) where [`revokeRole`](/contracts/5.x/api/access#AccessControl-revokeRole-bytes32-address-) cannot be used to revoke the `DEFAULT_ADMIN_ROLE`: + +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. + + +The same rule is implemented and extended in [`AccessControlDefaultAdminRules`](/contracts/5.x/api/access#AccessControlDefaultAdminRules), an extension that also adds enforced security measures for the `DEFAULT_ADMIN_ROLE`. + + +## 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. diff --git a/docs/content/contracts/5.x/faq.mdx b/docs/content/contracts/5.x/faq.mdx new file mode 100644 index 00000000..7858ccd0 --- /dev/null +++ b/docs/content/contracts/5.x/faq.mdx @@ -0,0 +1,15 @@ +--- +title: 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. diff --git a/docs/content/contracts/5.x/governance.mdx b/docs/content/contracts/5.x/governance.mdx new file mode 100644 index 00000000..9fc180c1 --- /dev/null +++ b/docs/content/contracts/5.x/governance.mdx @@ -0,0 +1,240 @@ +--- +title: 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. + + +Find detailed contract documentation at [Governance API](/contracts/5.x/api/governance). + + +## 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 `proposalId`s 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 `proposalId`s. + +### 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 + +[Tally](https://www.tally.xyz) 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 [IERC6372](/contracts/5.x/api/interfaces#IERC6372), visualize voting power and advocates, navigate proposals, and cast votes. For proposal creation in particular, projects can also use [Defender Transaction Proposals](/defender/module/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. + +./examples/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. + +./examples/governance/MyTokenWrapped.sol + + +The only other source of voting power available in OpenZeppelin Contracts currently is [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes). ERC-721 tokens that don’t provide this functionality can be wrapped into a voting tokens using a combination of [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes) and [`ERC721Wrapper`](/contracts/5.x/api/token/ERC721#ERC721Wrapper). + + + +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 [IERC6372](/contracts/5.x/api/interfaces#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. + +./examples/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. + + +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). +* 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. + +## 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(, )`, and with 0 ETH attached. + +Generally a proposal will be created with the help of an interface such as Tally or [Defender Proposals](/defender/module/transaction-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. + +![Voting in Tally](/tally-vote.png) + +### 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. + +![Administration Panel in Tally](/tally-exec.png) + +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.utils.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 ran 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 [`ERC20Votes`](/contracts/5.x/api/token/ERC20#ERC20Votes) and [`ERC721Votes`](/contracts/5.x/api/token/ERC721#ERC721Votes)) rely on [IERC6372](/contracts/5.x/api/interfaces#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. + +./examples/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 +pragma solidity ^0.8.20; + +import {Governor} from "@openzeppelin/contracts/governance/Governor.sol"; +import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/compatibility/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. diff --git a/docs/content/contracts/5.x/index.mdx b/docs/content/contracts/5.x/index.mdx new file mode 100644 index 00000000..e81553b1 --- /dev/null +++ b/docs/content/contracts/5.x/index.mdx @@ -0,0 +1,73 @@ +--- +title: Overview +--- + +**A library for secure smart contract development.** Build on a solid foundation of community-vetted code. + +* Implementations of standards like [ERC20](/contracts/5.x/erc20) and [ERC721](/contracts/5.x/erc721). +* Flexible [role-based permissioning](/contracts/5.x/access-control) scheme. +* Reusable [Solidity components](/contracts/5.x/utilities) to build custom contracts and complex decentralized systems. + + +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](/contracts/5.x/backwards-compatibility). + + +## Overview + +### Installation + +#### Hardhat (npm) + +```console +$ npm install @openzeppelin/contracts +``` + +#### Foundry (git) + + +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. + + + +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 + +Once installed, you can use the contracts in the library by importing them: + +./examples/MyNFT.sol + + +If you’re new to smart contract development, head to [Developing Smart Contracts](/contracts/5.x/learn/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 + +Please report any security issues you find via our [bug bounty program on Immunefi](https://www.immunefi.com/bounty/openzeppelin) or directly to security@openzeppelin.org. + +The [Security Center](https://contracts.openzeppelin.com/security) contains more details about the secure development process. + +## Learn More + +The guides in the sidebar will teach about different concepts, and how to use the related contracts that OpenZeppelin Contracts provides: + +* [Access Control](/contracts/5.x/access-control): decide who can perform each of the actions on your system. +* [Tokens](/contracts/5.x/tokens): create tradable assets or collectibles, like the well known [ERC20](/contracts/5.x/api/token/ERC20) and [ERC721](/contracts/5.x/api/token/ERC721) standards. +* [Utilities](/contracts/5.x/utilities): generic useful tools, including non-overflowing math, signature verification, and trustless paying systems. + +The [full API](/contracts/5.x/api/token/ERC20) 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](https://forum.openzeppelin.com). + +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. + +* [The Hitchhiker’s Guide to Smart Contracts in Ethereum](https://blog.openzeppelin.com/the-hitchhikers-guide-to-smart-contracts-in-ethereum-848f08001f05) will help you get an overview of the various tools available for smart contract development, and help you set up your environment. +* [A Gentle Introduction to Ethereum Programming, Part 1](https://blog.openzeppelin.com/a-gentle-introduction-to-ethereum-programming-part-1-783cc7796094) provides very useful information on an introductory level, including many basic concepts from the Ethereum platform. +* For a more in-depth dive, you may read the guide [Designing the architecture for your Ethereum application](https://blog.openzeppelin.com/designing-the-architecture-for-your-ethereum-application-9cec086f8317), which discusses how to better structure your application and its relationship to the real world. diff --git a/docs/content/contracts/5.x/learn/building-a-dapp.mdx b/docs/content/contracts/5.x/learn/building-a-dapp.mdx new file mode 100644 index 00000000..fa618c15 --- /dev/null +++ b/docs/content/contracts/5.x/learn/building-a-dapp.mdx @@ -0,0 +1,170 @@ +--- +title: Building a dapp +--- + + +This article is no longer maintained. Read [here](https://forum.openzeppelin.com/t/doubling-down-in-security/2712) for more info. + + +Decentralized Applications (aka dapps or ÐApps) are web applications backed by Ethereum smart contracts. Instead of using a centralized server or database, these applications rely on the blockchain as a consensus and coordination layer. This leads to potentially unstoppable applications: anyone can spin up a copy of the frontend, and freely connect it to the public Ethereum network. + +In this guide, we will see how we can use OpenZeppelin libraries such as Network JS and the [hot-loader](https://github.com/OpenZeppelin/solidity-loader) to build your dapp, as well as how the Starter Kits bind all pieces of a dapp together: + +* [Connecting to the Network](#connecting-to-the-network) +* [Hot-loading your Contracts](#hot-loading-contracts) +* [Kickstarting a Dapp using Starter Kits](#kickstarting-your-dapp) + +If you are in a rush, just skip over to [the last section](#kickstarting-your-dapp)! + +## Connecting to the Network + +When building a dapp, the first step is to connect to the Ethereum network. This usually involves the following steps: + +1. If the user is running Metamask, use the injected web3 provider to access the network, and monitor for network or account changes +2. If not, fall back to a public Infura node + +To avoid having to code this logic on every dapp, we built [**OpenZeppelin Network JS**] to take care of it. It provides a `useWeb3` [React hook](https://reactjs.org/docs/hooks-intro.html) for getting a `web3Context` based on the logic above. This `web3Context` object provides access to a connected `web3` instance, network information, the user accounts, a function to [request account access](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1102.md), and more. + +The following snippet is a small React app that connects to the network using Metamask (falling back to Infura if not found), prompts the user for account access, and displays the user balance: + +```js +import React, useCallback, useState, useEffect from 'react'; +import useWeb3 from '@openzeppelin/network/react'; + +function App() + // Get web3 context via @openzeppelin/network/react hook + const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${PROJECT_ID`); + const lib: web3, networkId, accounts, providerName = web3Context; + + // Methods for requesting accounts access + const requestAuth = (web3Context) => web3Context.requestAuth(); + const requestAccess = useCallback(() => requestAuth(web3Context), []); + + // Querying account balance + const [balance, setBalance] = useState(0); + const getBalance = useCallback(async () => + setBalance( + accounts && accounts.length > 0 + ? (await web3.eth.getBalance(accounts[0])) + : 'Unknown') + , [accounts, web3.eth, web3.utils]); + useEffect(() => + getBalance(); + , [accounts, getBalance, networkId]); + + // Show all information to the user + return ( +
+
Network: networkId || 'No connection'
+
Your address: accounts ? accounts[0] : 'Unknown'
+ + accounts && accounts.length ? ( +
Accounts access granted
+ ) : !!networkId && providerName !== 'infura' ? ( + + ) : ( +
No accounts access
+ )} + div> + +} +``` + +In the snippet above, with just the first two lines we get access to the Ethereum network via Metamask and falling back to Infura, as well as information on the network, provider, and even user accounts - all of this through the `useWeb3` hook provided by Network JS. + + +You can easily [enable meta-transaction support](./sending-gasless-transactions) in your dapp when using Network JS by [adding a `gsn` option] to the React hook. + + +To learn more, head to our step-by-step tutorial on [Building a Dapp from Scratch] using the Networks library. + +## Hot-loading Contracts + +Now that we have a working connection to the Ethereum network, we can start interacting with the contracts we have coded and [deployed using the OpenZeppelin CLI](./deploying-and-interacting). + +While we _could_ create a regular `web3` contract instance [from ABI and address], we will instead leverage [contract upgrades](./upgrading-smart-contracts) to hot-load our contracts. + +This means that once we deploy and load a contract in our running React dapp, the app will ***automatically pick up any changes to the Solidity code and upgrade the contract*** during development. The [**OpenZeppelin Hot Loader**](https://github.com/OpenZeppelin/solidity-loader) will watch all your Solidity contracts for changes, and automatically recompile and upgrade the contract on the local development network - keeping the same address and state, and just changing the code to the new version you wrote. + + +The Hot Loader is a [webpack](https://webpack.js.org/) plugin. It will not work if you are using a different bundler for your application. + + +To set up the hot-loader, you need to install `@openzeppelin/solidity-loader` and `json-loader`, and add the following [rule](https://webpack.js.org/configuration/module/#rule) to your webpack configuration: + +```js +test: /\.sol$/, +use: [ + loader: 'json-loader' , + + loader: '@openzeppelin/solidity-loader', + options: { + network: 'development', + disabled: false + , + }, +], +``` + + +If you are using `create-react-app`, you may need to either eject the application to modify the webpack configuration, or use [`react-app-rewired`](https://github.com/timarney/react-app-rewired). + + +Then, just load the contract Solidity file from your client code, which will go through the Hot Loader. Whenever you modify your contract source, the loader will automatically upgrade the instance to the latest code for you. + +```js +const networkId, lib: web3 = useWeb3Network('http://127.0.0.1:8545'); + +const Box = require('../../contracts/Box.sol'); + +const [box, setBox] = useState(undefined); +if (!box && Box && Box.networks && networkId) + const deployedNetwork = Box.networks[networkId.toString()]; + if (deployedNetwork) { + setBox(new web3.eth.Contract(Box.abi, deployedNetwork.address)); + +} +``` + +For detailed setup instructions, follow our step-by-step tutorial on [Enabling the Hot Loader in a React web application](https://forum.openzeppelin.com/t/building-an-openzeppelin-dapp-with-solidity-hot-loader/1843). + +## Kickstarting your Dapp + +As you have seen so far, setting up a dapp involves a fair amount of boilerplate and configuration, including creating an OpenZeppelin CLI project, initializing a new webpack client app, configuring network access and loading your contracts. + +To kickstart this process, we have built the [**OpenZeppelin Starter Kits**]. Starter Kits are preconfigured dapps with several OpenZeppelin libraries, Infura connections, and [Rimble UI components](https://github.com/ConsenSys/rimble-ui), ready to start developing right away. + +You can start a new project from a starter kit using the `oz unpack` CLI command: + +```console +$ oz unpack starter +✓ Kit downloaded and unpacked +The kit is ready to use. + +Quick Start +Run your local blockchain: +> ganache-cli --deterministic +Initialize the OpenZeppelin project: +> openzeppelin init app +Go to the client directory: +> cd client +Run the React app: +> npm run start +Continue in your browser! +More at https://github.com/OpenZeppelin/starter-kit/tree/stable +``` + +This will unpack a preconfigured React dapp, with a network connection set up to both Metamask and the local node. Each box in the dapp shows the information from each connection, each obtained from a different `web3Context`: network ID, provider, accounts, and balance. On the Metamask side, you need to request access to the user accounts before obtaining them from the plugin. + +![OpenZeppelin Starter Kit](/StarterKit.png) + +Having this initial setup, you can now modify it to start building your own application on top of it - as you would do in a vanilla [`create-react-app` setup](https://create-react-app.dev/). + +If you want to learn more about using Starter Kits, `unpack` the [`tutorial`] kit instead of `starter`: it will guide you through the process of deploying and interacting with contracts from a dapp. You can also check out the [list of all starter kits] available to unpack. + +## Next steps + +You have taken the first steps towards building a decentralized web-based front-end for your smart contracts. Your next tasks should be: + +* [Writing Automated Tests](./writing-automated-tests) +* [Connecting to Public Test Networks](./connecting-to-public-test-networks) diff --git a/docs/content/contracts/5.x/learn/connecting-to-public-test-networks.mdx b/docs/content/contracts/5.x/learn/connecting-to-public-test-networks.mdx new file mode 100644 index 00000000..9d082bb3 --- /dev/null +++ b/docs/content/contracts/5.x/learn/connecting-to-public-test-networks.mdx @@ -0,0 +1,165 @@ +--- +title: Connecting to public test networks +--- + +After you have [written your contracts](./developing-smart-contracts), and [tried them out locally](./deploying-and-interacting) and [tested them thoroughly](./writing-automated-tests), it’s time to move to a persistent public testing environment, where you and your beta users can start interacting with your application. + +We will use **public testing networks** (aka _testnets_) for this, which are networks that operate similar to the main Ethereum network, but where Ether has no value and is free to acquire - making them ideal for testing your contracts at no cost. + +In this guide, we will use our beloved [`Box` contract](./developing-smart-contracts#first-contract), and deploy it to a testnet, while learning: + +* [What test networks are available](#available-testnets) +* [How to set up your project for working on a testnet](#connecting-a-project-to-a-public-network) +* [How to deploy and interact with your testnet contract instances](#working-on-a-testnet) + +Remember that deploying to a public test network is a necessary step when developing an Ethereum project. They provide a safe environment for testing that closely mimics the main network - you don’t want to take out your project for a test drive in a network where mistakes will cost you and your users money! + +## Available testnets + +There are a number of test networks available for you to choose, each with their own characteristics. The recommended network for testing decentralized applications and smart contracts is Sepolia. (id=11155111) + + +Each network is identified by a numeric ID. Local networks usually have a large random value, while id=1 is reserved for the main Ethereum network. + + +## Connecting a project to a public network + +To connect our project to a public testnet, we will need to: + +1. [Get hold of a testnet node](#accessing-a-testnet-node) +2. [Create a new account](#creating-a-new-account) +3. [Update our configuration file](#configuring-the-network) +4. [Fund our testing account](#funding-the-testnet-account) + +### Accessing a testnet node + +While you can spin up your own [Ethereum nodes](https://ethereum.org/en/developers/docs/nodes-and-clients/run-a-node/) connected to a testnet, the easiest way to access a testnet is via a public node service such as [Alchemy](https://alchemy.com/) or [Infura](https://infura.io). Alchemy and Infura provide access to public nodes for all testnets and the main network, via both free and paid plans. + + +We say a node is _public_ when it can be accessed by the general public, and manages no accounts. This means that it can reply to queries and relay signed transactions, but cannot sign transactions on its own. + + +In this guide we will use Alchemy, though you can use [Infura](https://infura.io/), or another public node provider of your choice. + +Head over to [Alchemy](https://dashboard.alchemyapi.io/signup?referral=53fcee38-b894-4d5f-bd65-885d241f8d29) (includes referral code), sign up, and jot down your assigned API key - we will use it later to connect to the network. + +### Creating a new account + +To send transactions in a testnet, you will need a new Ethereum account. There are many ways to do this: here we will use the `mnemonics` package, which will output a fresh mnemonic (a set of 12 words) we will use to derive our accounts: + +```console +$ npx mnemonics +drama film snack motion ... +``` + + +Make sure to keep your mnemonic secure. Do not commit secrets to version control. Even if it is just for testing purposes, there are still malicious users out there who will wreak havoc on your testnet deployment for fun! + + +### Configuring the network + +Since we are using public nodes, we will need to sign all our transactions locally. We will configure the network with our mnemonic and an Alchemy endpoint. + + +This part assumes you have already set up a project. If you haven’t, head over to the guide on [Setting up a Solidity project](./developing-smart-contracts#setting-up-a-project). + + +We need to update our configuration file with a new network connection to the testnet. Here we will use Sepolia, but you can use whichever you want: +```diff +// hardhat.config.js ++ const { alchemyApiKey, mnemonic } = require('./secrets.json'); +... + module.exports = { ++ networks: { ++ sepolia: { ++ url: `https://eth-sepolia.g.alchemy.com/v2/${alchemyApiKey}`, ++ accounts: { mnemonic: mnemonic }, ++ }, ++ }, +... +}; +``` + + +See the Hardhat [networks configuration](https://hardhat.org/config/#json-rpc-based-networks) documentation for information on configuration options. +Note in the first line that we are loading the project id and mnemonic from a `secrets.json` file, which should look like the following, but using your own values. Make sure to `.gitignore` it to ensure you don’t commit secrets to version control! + + +```json + + "mnemonic": "drama film snack motion ...", + "alchemyApiKey": "JPV2..." + +``` + + +Instead of a `secrets.json` file, you can use whatever secret-management solution you like for your project. A popular and simple option is to use [`dotenv`](https://github.com/motdotla/dotenv) for injecting secrets as environment variables. + + +We can now test out that this configuration is working by listing the accounts we have available for the Sepolia network. Remember that yours will be different, as they depend on the mnemonic you used. + +```console +$ npx hardhat console --network sepolia +Welcome to Node.js v20.17.0. +Type ".help" for more information. +> accounts = (await ethers.getSigners()).map(signer => signer.address) +[ + '0x6B1c3A2f2160a7Cb2ebc7Fc861b8dB71476C30E7', + '0xC1310ade58A75E6d4fCb8238f9559188Ea3808f9', +... +] +``` +We can also test the connection to the node, by querying our account balance. +```console +> (await ethers.provider.getBalance(accounts[0])).toString() +'0' +``` + +Empty! This points to our next task: getting testnet funds so that we can send transactions. + +### Funding the testnet account + +Most public testnets have a faucet: a site that will provide you with a small amount of test Ether for free. If you are on Sepolia, head to [Alchemy’s free Sepolia faucet](https://www.alchemy.com/faucets/ethereum-sepolia), [Infura’s free Sepolia faucet](https://www.infura.io/faucet), or [Google’s free Sepolia faucet](https://cloud.google.com/application/web3/faucet/ethereum/sepolia) to get free testETH. + +Armed with a funded account, let’s deploy our contracts to the testnet! + +## Working on a testnet + +With a project configured to work on a public testnet, we can now finally [deploy our `Box` contract](./deploying-and-interacting#deploying-a-smart-contract). The command here is exactly the same as if you were on your [local development network](./deploying-and-interacting#setting-up-a-local-blockchain), though it will take a few seconds to run as new blocks are mined. + +```console +$ npx hardhat run --network sepolia scripts/deploy.js +Deploying Box... +Box deployed to: 0x1b99CCaCea0e4046db618770dEF72180F8138641 +``` + +That’s it! Your `Box` contract instance will be forever stored in the testnet, and publicly accessible to anyone. + +You can see your contract on a block explorer such as [Etherscan](https://etherscan.io/). Remember to access the explorer on the testnet where you deployed your contract, such as [sepolia.etherscan.io](https://sepolia.etherscan.io) for Sepolia. + + +You can check out the contract we deployed in the example above, along with all transactions sent to it, [here](https://sepolia.etherscan.io/address/0x1b99CCaCea0e4046db618770dEF72180F8138641). + + +You can also interact with your instance as you regularly would, either using the [console](./deploying-and-interacting#interacting-from-the-console), or [programmatically](./deploying-and-interacting#interacting-programmatically). + +```console +$ npx hardhat console --network sepolia +Welcome to Node.js v20.17.0. +Type ".help" for more information. +> const Box = await ethers.getContractFactory('Box'); +undefined +> const box = await Box.attach('0x1b99CCaCea0e4046db618770dEF72180F8138641'); +undefined +> await box.store(42); +{ + hash: '0x330e331d30ee83f96552d82b7fdfa6156f9f97d549a612eeef7283d18b31d107', +... +> (await box.retrieve()).toString() +'42' +``` +Keep in mind that every transaction will cost some gas, so you will eventually need to top up your account with more funds. + +## Next steps + +After thoroughly testing your application on a public testnet, you are ready for the last step on the development journey: [deploying your application in production](./preparing-for-mainnet). diff --git a/docs/content/contracts/5.x/learn/deploying-and-interacting.mdx b/docs/content/contracts/5.x/learn/deploying-and-interacting.mdx new file mode 100644 index 00000000..815f2b26 --- /dev/null +++ b/docs/content/contracts/5.x/learn/deploying-and-interacting.mdx @@ -0,0 +1,299 @@ +--- +title: Deploying and interacting with smart contracts +--- + +Unlike most software, smart contracts don't run on your computer or somebody's server: they live on the Ethereum network itself. This means that interacting with them is a bit different from more traditional applications. + +This guide will cover all you need to know to get you started using your contracts, including: + +* [Setting up a Local Blockchain](#setting-up-a-local-blockchain) +* [Deploying a Smart Contract](#deploying-a-smart-contract) +* [Interacting from the Console](#interacting-from-the-console) +* [Interacting Programmatically](#interacting-programmatically) + +## Setting up a Local Blockchain + +Before we begin, we first need an environment where we can deploy our contracts. The Ethereum blockchain (often called "mainnet", for "main network") requires spending real money to use it, in the form of Ether (its native currency). This makes it a poor choice when trying out new ideas or tools. + +To solve this, a number of "testnets" (for "test networks") exist: these include the Sepolia and Holesky blockchains. They work very similarly to mainnet, with one difference: you can get Ether for these networks for free, so that using them doesn't cost you a single cent. However, you will still need to deal with private key management, blocktimes of 12 seconds or more, and actually getting this free Ether. + +During development, it is a better idea to instead use a _local_ blockchain. It runs on your machine, requires no Internet access, provides you with all the Ether that you need, and mines blocks instantly. These reasons also make local blockchains a great fit for [automated tests](./writing-automated-tests#setting-up-a-testing-environment). + + +If you want to learn how to deploy and use contracts on a _public_ blockchain, like the Ethereum testnets, head to our [Connecting to Public Test Networks](./connecting-to-public-test-networks) guide. + + +Hardhat comes with a local blockchain built-in, the [Hardhat Network](https://hardhat.org/hardhat-network/). + +Upon startup, Hardhat Network will create a set of unlocked accounts and give them Ether. + +```bash +npx hardhat node +``` + +Hardhat Network will print out its address, `http://127.0.0.1:8545`, along with a list of available accounts and their private keys. + +Keep in mind that every time you run Hardhat Network, it will create a brand new local blockchain - the state of previous runs is ***not*** preserved. This is fine for short-lived experiments, but it means that you will need to have a window open running Hardhat Network for the duration of these guides. + + +Hardhat will always spin up an instance of ***Hardhat Network*** when no network is specified and there is no default network configured or the default network is set to `hardhat`. + + + +You can also run an actual Ethereum node in _[development mode](https://geth.ethereum.org/getting-started/dev-mode)_. These are a bit more complex to set up, and not as flexible for testing and development, but are more representative of the real network. + + +## Deploying a Smart Contract + +In the [Developing Smart Contracts guide](./developing-smart-contracts) we set up our development environment. + +If you don't already have this setup, please [create](./setting-up-a-node-project#creating-a-project) and [setup](./developing-smart-contracts#setting-up-a-project) the project and then [create](./developing-smart-contracts#first-contract) and [compile](./developing-smart-contracts#compiling-solidity) our Box smart contract. + +With our project setup complete we're now ready to deploy a contract. We'll be deploying `Box`, from the [Developing Smart Contracts](./developing-smart-contracts#first-contract) guide. Make sure you have a copy of [Box](./developing-smart-contracts#first-contract) in `contracts/Box.sol`. + +Hardhat uses either [declarative deployments](https://hardhat.org/hardhat-runner/docs/guides/deploying) or [scripts](https://hardhat.org/hardhat-runner/docs/advanced/scripts#writing-scripts-with-hardhat) to deploy contracts. + +We will create a script to deploy our Box contract. We will save this file as `scripts/deploy.js`. + +```js +async function main() { + // We get the contract to deploy + const Box = await ethers.getContractFactory('Box'); + console.log('Deploying Box...'); + const box = await Box.deploy(); + await box.waitForDeployment(); + console.log('Box deployed to:', await box.getAddress()); +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); +``` + +We use [ethers](https://github.com/ethers-io/ethers.js) in our script, so we need to install it and the [@nomicfoundation/hardhat-ethers plugin](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-ethers). + +```bash +npm install --save-dev @nomicfoundation/hardhat-ethers ethers +``` + +We need to add in our [configuration](https://hardhat.org/config/) that we are using the `@nomicfoundation/hardhat-ethers` plugin. + +```js +require("@nomicfoundation/hardhat-ethers"); + +... +module.exports = { +... +}; +``` + +Using the `run` command, we can deploy the `Box` contract to the local network ([Hardhat Network](#setting-up-a-local-blockchain)): + +```bash +npx hardhat run --network localhost scripts/deploy.js +# Deploying Box... +# Box deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3 +``` + + +Hardhat doesn't keep track of your deployed contracts. We displayed the deployed address in our script (in our example, `0x5FbDB2315678afecb367f032d93F642f64180aa3`). This will be useful when interacting with them programmatically. + + +All done! On a real network this process would've taken a couple of seconds, but it is near instant on local blockchains. + + +If you got a connection error, make sure you are running a [local blockchain](#setting-up-a-local-blockchain) in another terminal. + + + +Remember that local blockchains ***do not*** persist their state throughout multiple runs! If you close your local blockchain process, you'll have to re-deploy your contracts. + + +## Interacting from the Console + +With our `Box` contract [deployed](#deploying-a-smart-contract), we can start using it right away. + +We will use the [Hardhat console](https://hardhat.org/guides/hardhat-console.html) to interact with our deployed `Box` contract on our localhost network. + + +We need to specify the address of our `Box` contract we displayed in our deploy script. + + + +It's important that we explicitly set the network for Hardhat to connect our console session to. If we don't, Hardhat will default to using a new ephemeral network, which our Box contract wouldn't be deployed to. + + +```bash +npx hardhat console --network localhost +# Welcome to Node.js v20.17.0. +# Type ".help" for more information. +> const Box = await ethers.getContractFactory('Box'); +# undefined +> const box = Box.attach('0x5FbDB2315678afecb367f032d93F642f64180aa3') +# undefined +``` + +### Sending transactions +`Box`'s first function, `store`, receives an integer value and stores it in the contract storage. Because this function _modifies_ the blockchain state, we need to _send a transaction_ to the contract to execute it. + +We will send a transaction to call the `store` function with a numeric value: +```bash +> await box.store(42) + +# hash: '0x3d86c5c2c8a9f31bedb5859efa22d2d39a5ea049255628727207bc2856cce0d3', +#... +``` + +### Querying state + +`Box`'s other function is called `retrieve`, and it returns the integer value stored in the contract. This is a _query_ of blockchain state, so we don't need to send a transaction: +```bash +> await box.retrieve() +# 42n +``` + +Because queries only read state and don't send a transaction, there is no transaction hash to report. This also means that using queries doesn't cost any Ether, and can be used for free on any network. + + +Our `Box` contract returns `uint256` which is too large a number for JavaScript so instead we get returned a big number object. We can display the big number as a string using `(await box.retrieve()).toString()`. + + +```bash +> (await box.retrieve()).toString() +# '42' +``` + + +To learn more about using the console, check out the [Hardhat documentation](https://hardhat.org/guides/hardhat-console.html). + + +## Interacting programmatically + +The console is useful for prototyping and running one-off queries or transactions. However, eventually you will want to interact with your contracts from your own code. + +In this section, we'll see how to interact with our contracts from JavaScript, and use [Hardhat to run our script](https://hardhat.org/guides/scripts.html) with our Hardhat configuration. + + +Keep in mind that there are many other JavaScript libraries available, and you can use whichever you like the most. Once a contract is deployed, you can interact with it through any library! + + +### Setup + +Let's start coding in a new `scripts/index.js` file, where we'll be writing our JavaScript code, beginning with some boilerplate, including for [writing async code](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). + +```js +// scripts/index.js +async function main() { + // Our code will go here +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); +``` + +We can test our setup by asking the local node something, such as the list of enabled accounts: + +```js +// Retrieve accounts from the local node +const accounts = (await ethers.getSigners()).map(signer => signer.address); +console.log(accounts); +``` + + +We won't be repeating the boilerplate code on every snippet, but make sure to always code _inside_ the `main` function we defined above! + + +Run the code above using `hardhat run`, and check that you are getting a list of available accounts in response. + +```bash +npx hardhat run --network localhost ./scripts/index.js +# [ +# '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', +# '0x70997970C51812dc3A010C7d01b50e0d17dc79C8', +#... +#] +``` + +These accounts should match the ones displayed when you started the [local blockchain](#setting-up-a-local-blockchain) earlier. Now that we have our first code snippet for getting data out of a blockchain, let's start working with our contract. Remember we are adding our code _inside_ the `main` function we defined above. + +### Getting a contract instance + +In order to interact with the [`Box`](#box-contract) contract we deployed, we'll use an [ethers contract instance](https://docs.ethers.org/v6/api/contract/). + +An ethers contract instance is a JavaScript object that represents our contract on the blockchain, which we can use to interact with our contract. To attach it to our deployed contract we need to provide the contract address. + +```js +// Set up an ethers contract, representing our deployed Box instance +const address = '0x5FbDB2315678afecb367f032d93F642f64180aa3'; +const Box = await ethers.getContractFactory('Box'); +const box = Box.attach(address); +``` + + +Make sure to replace the `address` with the one you got when deploying the contract, which may be different to the one shown here. + + +We can now use this JavaScript object to interact with our contract. + +### Calling the contract + +Let's start by displaying the current value of the `Box` contract. + +We'll need to call the read only `retrieve()` public method of the contract, and [await](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await) the response: + +```js +// Call the retrieve() function of the deployed Box contract +const value = await box.retrieve(); +console.log('Box value is', value.toString()); +``` +This snippet is equivalent to the [query](#querying-state) we ran earlier from the console. Now, make sure everything is running smoothly by running the script again and checking the printed value: + +```bash +npx hardhat run --network localhost ./scripts/index.js +# Box value is 42 +``` + +If you restarted your local blockchain at any point, this script may fail. Restarting clears all local blockchain state, so the `Box` contract instance won't be at the expected address. + +If this happens, simply [start the local blockchain](#setting-up-a-local-blockchain) and [redeploy](#deploying-a-smart-contract) the `Box` contract. + + +### Sending a transaction +We'll now send a transaction to `store` a new value in our Box. + +Let's store a value of `23` in our `Box`, and then use the code we had written before to display the updated value: + +```js +// Send a transaction to store() a new value in the Box +await box.store(23); + +// Call the retrieve() function of the deployed Box contract +const value = await box.retrieve(); +console.log('Box value is', value.toString()); +``` + + +In a real-world application, you may want to [estimate the gas](https://docs.ethers.org/v6/api/contract/#BaseContractMethod-estimateGas) of your transactions, and check a [gas price oracle](https://etherscan.io/gastracker) to know the optimal values to use on every transaction. + + +We can now run the snippet, and check that the box's value is updated! + +```bash +npx hardhat run --network localhost ./scripts/index.js +# Box value is 23 +``` + +## Next steps + +Now that you know how to set up a local blockchain, deploy contracts and interact with them both manually and programmatically, you will need to learn about testing environments, public test networks and going to production: + +* [Writing Automated Tests](./writing-automated-tests) +* [Connecting to Public Test Networks](./connecting-to-public-test-networks) +* [Preparing for Mainnet](./preparing-for-mainnet) diff --git a/docs/content/contracts/5.x/learn/developing-smart-contracts.mdx b/docs/content/contracts/5.x/learn/developing-smart-contracts.mdx new file mode 100644 index 00000000..b65f88b1 --- /dev/null +++ b/docs/content/contracts/5.x/learn/developing-smart-contracts.mdx @@ -0,0 +1,239 @@ +--- +title: Developing smart contracts +--- + +Welcome to the exciting world of smart contract development! This guide will let you get started writing Solidity contracts by going over the following: + +* [Setting up a Solidity Project](#setting-up-a-project) +* [Compiling Solidity Source Code](#compiling-solidity) +* [Adding More Contracts](#adding-more-contracts) +* [Using OpenZeppelin Contracts](#using-openzeppelin-contracts) + +## About Solidity + +We won’t cover language concepts such as syntax or keywords in this guide. For that, you’ll want to check out the following curated content, which feature great learning resources for both newcomers and experienced developers: + +* For a general overview of how Ethereum and smart contracts work, the official website hosts a [Learn about Ethereum](https://ethereum.org/learn/) section with lots of beginner-friendly content. +* If you’re new to the language, the [official Solidity documentation](https://solidity.readthedocs.io/en/latest/introduction-to-smart-contracts.html) is a good resource to have handy. Take a look at their [security recommendations](https://solidity.readthedocs.io/en/latest/security-considerations.html), which nicely go over the differences between blockchains and traditional software platforms. +* Consensys' [best practices](https://consensys.github.io/smart-contract-best-practices/) are quite extensive, and include both [proven patterns](https://consensys.github.io/smart-contract-best-practices/development-recommendations/) to learn from and [known pitfalls](https://consensys.github.io/smart-contract-best-practices/attacks/) to avoid. +* The [Ethernaut](https://ethernaut.openzeppelin.com/) web-based game will have you look for subtle vulnerabilities in smart contracts as you advance through levels of increasing difficulty. + +With that out of the way, let’s get started! + +## Setting up a Project + +The first step after [creating a project](./setting-up-a-node-project#creating-a-project) is to install a development tool. + +The most popular development frameworks for Ethereum are [Hardhat](https://hardhat.org/) and [Foundry](https://github.com/foundry-rs/foundry). Each has their strengths and it is useful to be comfortable using all of them. + +In these guides we will show how to develop, test and deploy smart contracts using Hardhat, and we cover its most common use with [ethers.js](https://docs.ethers.io/). + +To get started with Hardhat we will install it in our [project directory](./setting-up-a-node-project#creating-a-project). + +```bash +npm install --save-dev hardhat +``` + +Once installed, we can run `npx hardhat`. This will create a Hardhat config file (`hardhat.config.js`) in our project directory. + +```bash +npx hardhat +# 888 888 888 888 888 +# 888 888 888 888 888 +# 888 888 888 888 888 +# 8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888 +# 888 888 "88b 888P" d88" 888 888 "88b "88b 888 +# 888 888 .d888888 888 888 888 888 888 .d888888 888 +# 888 888 888 888 888 Y88b 888 888 888 888 888 Y88b. +# 888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888 +# +# 👷 Welcome to Hardhat v2.22.12 👷‍ +# +# ✔ What do you want to do? · Create an empty hardhat.config.js +# Config file created +``` + +## First contract + +We store our Solidity source files (`.sol`) in a `contracts` directory. This is equivalent to the `src` directory you may be familiar with from other languages. + +We can now write our first simple smart contract, called `Box`: it will let people store a value that can be later retrieved. + +We will save this file as `contracts/Box.sol`. Each `.sol` file should have the code for a single contract, and be named after it. + +```solidity +pragma solidity ^0.8.0; + +contract Box { + uint256 private _value; + + // Emitted when the stored value changes + event ValueChanged(uint256 value); + + // Stores a new value in the contract + function store(uint256 value) public { + _value = value; + emit ValueChanged(value); + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return _value; + } +} +``` + +## Compiling Solidity + +The Ethereum Virtual Machine (EVM) cannot execute Solidity code directly: we first need to compile it into EVM bytecode. + +Our `Box.sol` contract uses Solidity 0.8 so we need to first [configure Hardhat to use an appropriate solc version](https://hardhat.org/config/#solidity-configuration). + +We specify a Solidity 0.8 solc version in our `hardhat.config.js`. + +```js + +/** + * @type import('hardhat/config').HardhatUserConfig + */ + module.exports = + solidity: "0.8.24", +; +``` + +Compiling can then be achieved by running a single compile command: + + +If you’re unfamiliar with the `npx` command, check out our [Node project setup guide](./setting-up-a-node-project#using-npx). + + +```bash +npx hardhat compile +# Compiled 1 Solidity file successfully (evm target: paris). +``` + +The [`compile`](https://hardhat.org/guides/compile-contracts.html#compiling-your-contracts) built-in task will automatically look for all contracts in the `contracts` directory, and compile them using the Solidity compiler using the configuration in [`hardhat.config.js`](https://hardhat.org/config/#solidity-configuration). + +You will notice an `artifacts` directory was created: it holds the compiled artifacts (bytecode and metadata), which are .json files. It’s a good idea to add this directory to your `.gitignore`. + +## Adding more contracts + +As your project grows, you will begin to create more contracts that interact with each other: each one should be stored in its own `.sol` file. + +To see how this looks, let’s add a simple access control system to our `Box` contract: we will store an administrator address in a contract called `Auth`, and only let `Box` be used by those accounts that `Auth` allows. + +Because the compiler will pick up all files in the `contracts` directory and subdirectories, you are free to organize your code as you see fit. Here, we’ll store the `Auth` contract in an `access-control` subdirectory: + + +```solidity +pragma solidity ^0.8.0; + +contract Auth { + address private _administrator; + + constructor(address deployer) { + // Make the deployer of the contract the administrator + _administrator = deployer; + } + + function isAdministrator(address user) public view returns (bool) { + return user == _administrator; + } +} +``` + +To use this contract from `Box` we use an `import` statement, referring to `Auth` by its relative path: + +```solidity +pragma solidity ^0.8.0; + +import "./access-control/Auth.sol"; + +contract Box { + uint256 private _value; + Auth private _auth; + + event ValueChanged(uint256 value); + + constructor() { + _auth = new Auth(msg.sender); + } + + function store(uint256 value) public { + // Require that the caller is registered as an administrator in Auth + require(_auth.isAdministrator(msg.sender), "Unauthorized"); + + _value = value; + emit ValueChanged(value); + } + + function retrieve() public view returns (uint256) { + return _value; + } +} +``` + +Separating concerns across multiple contracts is a great way to keep each one simple, and is generally a good practice. + +However, this is not the only way to split your code into modules. You can also use _inheritance_ for encapsulation and code reuse in Solidity, as we’ll see next. + +## Using OpenZeppelin Contracts + +Reusable modules and libraries are the cornerstone of great software. [**OpenZeppelin Contracts**](/contracts) contains lots of useful building blocks for smart contracts to build on. And you can rest easy when building on them: they’ve been the subject of multiple audits, with their security and correctness battle-tested. + +### About inheritance + +Many of the contracts in the library are not standalone, that is, you’re not expected to deploy them as-is. Instead, you will use them as a starting point to build your own contracts by adding features to them. Solidity provides _multiple inheritance_ as a mechanism to achieve this: take a look at the [Solidity documentation](https://solidity.readthedocs.io/en/latest/contracts.html#inheritance) for more details. + +For example, the [`Ownable`](../api/access#Ownable) contract marks the deployer account as the contract’s owner, and provides a modifier called `onlyOwner`. When applied to a function, `onlyOwner` will cause all function calls that do not originate from the owner account to revert. Functions to [transfer](/contracts/5.x/api/access#Ownable-transferOwnership-address-) and [renounce](/contracts/5.x/api/access#Ownable-renounceOwnership--) ownership are also available. + +When used this way, inheritance becomes a powerful mechanism that allows for modularization, without forcing you to deploy and manage multiple contracts. + +### Importing OpenZeppelin Contracts + +The latest published release of the OpenZeppelin Contracts library can be downloaded by running: + +```bash +npm install @openzeppelin/contracts +``` + + +You should always use the library from these published releases: copy-pasting library source code into your project is a dangerous practice that makes it very easy to introduce security vulnerabilities in your contracts. + + +To use one of the OpenZeppelin Contracts, `import` it by prefixing its path with `@openzeppelin/contracts`. For example, in order to replace our own [`Auth`](#auth-contract) contract, we will import `@openzeppelin/contracts/access/Ownable.sol` to add access control to `Box`: + + +```solidity +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/access/Ownable.sol"; + +contract Box is Ownable { + uint256 private _value; + + event ValueChanged(uint256 value); + + constructor() Ownable(msg.sender) {} + + // The onlyOwner modifier restricts who can call the store function + function store(uint256 value) public onlyOwner { + _value = value; + emit ValueChanged(value); + } + + function retrieve() public view returns (uint256) { + return _value; + } +} +``` + +The [OpenZeppelin Contracts documentation](/contracts) is a great place to learn about developing secure smart contract systems. It features both guides and a detailed API reference: see for example the [Access Control](../access-control) guide to know more about the `Ownable` contract used in the code sample above. + +## Next steps + +Writing and compiling Solidity contracts are but the first steps in the journey to having your decentralized application running on the Ethereum network. Once you are comfortable with this setup, you’ll want to move on to more advanced tasks: + +* [Deploying and Interacting](./deploying-and-interacting) +* [Writing Automated Tests](./writing-automated-tests) +* [Connecting to Public Test Networks](./connecting-to-public-test-networks) diff --git a/docs/content/contracts/5.x/learn/index.mdx b/docs/content/contracts/5.x/learn/index.mdx new file mode 100644 index 00000000..1d9e5249 --- /dev/null +++ b/docs/content/contracts/5.x/learn/index.mdx @@ -0,0 +1,15 @@ +--- +title: Learn +--- + +Comprehensive guides for every step of your development journey. + +* [Setting up a Node project](/contracts/5.x/learn/setting-up-a-node-project) - Get your Node development environment set up for using OpenZeppelin tools +* [Developing smart contracts](/contracts/5.x/learn/developing-smart-contracts) - Learn the basics of writing Solidity contracts with OpenZeppelin +* [Deploying and interacting with smart contracts](/contracts/5.x/learn/deploying-and-interacting) - Deploy contracts to local and test networks and interact with them +* [Writing automated smart contract tests](/contracts/5.x/learn/writing-automated-tests) - Write comprehensive tests to verify your contracts work as intended +* [Upgrading smart contracts](/contracts/5.x/learn/upgrading-smart-contracts) - Modify your contract code while preserving state and address using OpenZeppelin Upgrades +* [Connecting to public test networks](/contracts/5.x/learn/connecting-to-public-test-networks) - Move from local development to persistent test environments +* [Preparing for mainnet](/contracts/5.x/learn/preparing-for-mainnet) - Security considerations and best practices for production deployment +* [Building a dapp](/contracts/5.x/learn/building-a-dapp) - Create decentralized web applications with OpenZeppelin Network JS and hot-loading +* [Sending gasless transactions](/contracts/5.x/learn/sending-gasless-transactions) - Enable meta-transactions using the Gas Station Network for better user onboarding diff --git a/docs/content/contracts/5.x/learn/preparing-for-mainnet.mdx b/docs/content/contracts/5.x/learn/preparing-for-mainnet.mdx new file mode 100644 index 00000000..7cff2350 --- /dev/null +++ b/docs/content/contracts/5.x/learn/preparing-for-mainnet.mdx @@ -0,0 +1,104 @@ +--- +title: Preparing for mainnet +--- + +After [running your project on a testnet](./connecting-to-public-test-networks) for some time without issues, you will want to deploy it to the main Ethereum network (aka _mainnet_). However, the planning for going to mainnet should begin much earlier than your planned release date. + +In this guide, we will go through Ethereum-specific considerations for taking your project to production, such as: + +* [Auditing and Security](#auditing-and-security) +* [Verifying Source Code](#verifying-your-source-code) +* [Managing Keys Securely](#key-management) +* [Handling Project Governance](#project-governance) + +Remember that, while managing your contracts in a testnet and in mainnet is technically the same, there are important differences when on mainnet, since your project now manages real value for your users. + +## Auditing and security + +While security affects all of software development, security in smart contracts is particularly important. Anyone can send a transaction directly to your contracts with any payload, and all your contract code and state is publicly accessible. To make matters worse, in the event you are hacked, there is no recourse to reclaim the stolen funds - they are gone for good in a decentralized network. + +With this in mind, security should be a primary concern at all stages of development. This means that ***security is not something that you sprinkle on your project a week before you release***, but a guiding principle starting day one of your project. + +Review [smart contract security best practices](https://consensys.github.io/smart-contract-best-practices/) as you begin coding, join the [security discussions in our forum](https://forum.openzeppelin.com/c/security/25), and make sure to go through our [quality checklist](https://blog.openzeppelin.com/follow-this-quality-checklist-before-an-audit-8cc6a0e44845/) to ensure your project is healthy. + +Once you are done, it’s a good time to request an audit with one or more auditing firms. You can [request an audit](https://openzeppelin.com/security-audits/) from the OpenZeppelin Research Team - we are an experienced team with a [long track record](https://blog.openzeppelin.com/security-audits/). + +Remember that audits do not ensure the absence of bugs, but having several experienced security researchers go through your code certainly helps. + +## Verifying your source code + +Right after you deploy your contracts to mainnet, you should ***verify their source code***. This process involves submitting the Solidity code to a third-party, such as [Etherscan](https://etherscan.io/) or [Sourcify](https://sourcify.dev/), who will compile it and _verify_ that it matches the deployed assembly. This allows any user to view your contract code such as in a block explorer, and know that it corresponds to the assembly actually running at that address. + +You can verify your contracts manually on the [Etherscan](https://etherscan.io/verifyContract) website. + +You can also use [hardhat-verify plugin](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify). + +To do this, install the plugin: +```console +npm install --save-dev @nomicfoundation/hardhat-verify +``` + +Update your hardhat configuration: +```js +const etherscanApiKey, projectId, mnemonic = require('./secrets.json'); +require("@nomicfoundation/hardhat-verify"); +... +module.exports = { + networks: { + mainnet: { ... } + }, + etherscan:{ + apiKey: etherscanApiKey + } +}; +``` +Finally run the `verify` task, passing the address of the contract, the network where it’s deployed, and the constructor arguments that were used to deploy it (if any): + +```console +npx hardhat verify --network mainnet DEPLOYED_CONTRACT_ADDRESS "Constructor argument 1" +``` + +You will need an [Etherscan API key](https://etherscan.io/apis) to use their service. + + + +When you deploy an upgradeable contract the contract that the user interacts with will be just a proxy, and the actual logic will be in the implementation contract. Etherscan does have [support for correctly showing OpenZeppelin proxies and their implementations](https://medium.com/etherscan-blog/and-finally-proxy-contract-support-on-etherscan-693e3da0714b), but other explorers may not. + + +## Key management + +When working on mainnet you need to take special care to secure your private keys. The accounts you use to deploy and interact with your contracts will hold real Ether, which has real value and is a tempting target for hackers. Take every precaution to protect your keys, and consider using a [hardware wallet](https://ethereum.org/en/security/#use-hardware-wallet) if necessary. + + +Unlike on a testnet, you cannot get mainnet Ether from a faucet. You will need to head to an exchange to trade in other coins or fiat to get real Ether for deploying your contracts. + + +Additionally, you may define certain accounts to have special privileges in your system - and you should take extra care to secure them. + +### Admin accounts + +An _admin_ (short for _administrator_) account is one that has special privileges in your system. For example, an admin may have the power to [pause](/contracts/5.x/api/utils#Pausable) a contract. If such an account were to fall in the hands of a malicious user, they could wreak havoc in your system. + +A good option for securing admin accounts is to use a special contract, such as a multisig, instead of a regular externally owned account. A _multisig_ is a contract that can execute any action, _as long as a predefined number of trusted members agree upon it_. [Safe](https://safe.global/wallet) is a good multisig to use. + +### Upgrades admin + +A special administrator account in an [OpenZeppelin Upgrades Plugins](/upgrades-plugins) project is the account with the power to [_upgrade_](./upgrading-smart-contracts) other contracts. This defaults to the externally owned account used to deploy the contracts: while this is good enough for a local or testnet deployment, in mainnet you need to better secure your contracts. An attacker who gets hold of your upgrade admin account can change any contract in your system! + +With this in mind, it is a good idea to ***change the ownership of the ProxyAdmin*** after deployment - for example, to a multisig. To do this, you can use `admin.transferProxyAdminOwnership` to transfer ownership of our `ProxyAdmin` contract. + +When you need to upgrade your contracts, we can use `prepareUpgrade` to validate and deploy a new implementation contract ready to be used when our proxy is updated. + +## Project governance + +It can be argued that admin accounts reflect that a project is not actually _decentralized_. After all, if an account can single-handedly change any contract in the system, we are not exactly creating a trustless environment. + +Here is where _governance_ comes in. In many cases there will be some operations in your project that require special privileges, from fine-tuning system parameters, to running full contract upgrades. You will need to choose how those actions will be decided upon: whether it is by a [small group](https://safe.global/wallet) of trusted developers, or by [public voting](../governance) of all project stakeholders. + +There is no right answer here. Which governance scheme you pick for your project will largely depend on what you are building and who your community is. + +## Next steps + +Congratulations! You have reached the end of the development journey, from writing your first contract to deploying to production. But work is far from over. Now you have to start collecting live user feedback, adding new features to your system (made possible via contract upgrades!), monitoring your application, and ultimately scaling your project. + +On this site, you have at your disposal detailed guides and reference for all the projects in the OpenZeppelin platform, so you can pick whatever you need to build your Ethereum application. Happy coding! diff --git a/docs/content/contracts/5.x/learn/sending-gasless-transactions.mdx b/docs/content/contracts/5.x/learn/sending-gasless-transactions.mdx new file mode 100644 index 00000000..8cacd5bd --- /dev/null +++ b/docs/content/contracts/5.x/learn/sending-gasless-transactions.mdx @@ -0,0 +1,354 @@ +--- +title: Sending gasless transactions +--- + + +This guide is now deprecated, as it uses GSNv1, which is no longer supported. Consider using GSNv2 from the [OpenGSN](https://opengsn.org/) team for a decentralized solution. + + + +This article is no longer maintained. Read [here](https://forum.openzeppelin.com/t/doubling-down-in-security/2712) for more info. + + +Anyone who sends an Ethereum transaction needs to have Ether to pay for its gas fees. This forces new users to purchase Ether (which can be a daunting task) before they can start using a dapp. This is a major hurdle in user onboarding. + +In this guide, we will explore the concept of gasless (also called _meta_) transactions, where **the user does not need to pay for their gas fees**. We will also introduce the [Gas Station Network](https://gasstation.network), a decentralized solution to this problem, as well as the OpenZeppelin libraries that allow you to leverage it in your dapps: + +* [Learn what meta-transactions are and why they matter](#what-is-a-meta-transaction?) +* [The Gas Station Network as a decentralized meta-transaction solution](#the-gas-station-network) +* [Build a GSN-powered DApp from scratch](#building-a-gsn-powered-dapp) +* [Fast-forward with the GSN Starter Kit](#the-gsn-starter-kit) + +## What is a Meta-transaction? + +All Ethereum transactions use gas, and the sender of each transaction must have enough Ether to pay for the gas spent. Even though these gas costs are low for basic transactions (a couple of cents), getting Ether is no easy task: dApp users often need to go through Know Your Customer and Anti Money-Laundering processes (KYC & AML), which not only takes time but often involves sending a selfie holding their passport over the Internet (!). On top of that, they also need to provide financial information to be able to purchase Ether through an exchange. Only the most hardcore users will put up with this hassle, and dApp adoption greatly suffers when Ether is required. We can do better. + +***Enter meta-transactions***. This is a fancy name for a simple idea: a third-party (called a _relayer_) can send another user’s transactions and pay themselves for the gas cost. In this scheme, users sign messages (not transactions) containing information about a transaction they would like to execute. Relayers are then responsible for signing valid Ethereum transactions with this information and sending them to the network, paying for the gas cost. A [base contract] preserves the identity of the user that originally requested the transaction. In this way, users can interact directly with smart contracts without needing to have a wallet or own Ether. + +This means that, in order to support meta transactions in your application, you need to keep a _relayer_ process running - or leverage a decentralized relayer network. + +## The Gas Station Network + +The [Gas Station Network](https://gasstation.network) (GSN) is a decentralized network of _relayers_. It allows you to build dapps where you pay for your users transactions, so they do not need to hold Ether to pay for gas, easing their onboarding process. + + +The GSN was originally conceived and designed by [TabooKey](https://medium.com/tabookey/1-800-ethereum-gas-stations-network-for-toll-free-transactions-4bbfc03a0a56), and it has grown to encompass many companies in the Ethereum space looking to work together to solve the problem of onboarding users to Ethereum applications. + + +However, relayers in the GSN are not running a charity: they’re running a business. The reason why they’ll gladly pay for your users' gas costs is because they will in turn charge your contract, the _recipient_. That way relayers get their money back, plus a bit extra as a fee for their services. + +This may sound strange at first, but paying for user onboarding is a very common business practice. Lots of money is spent on advertising, free trials, new user discounts, etc., all with the [goal of user acquisition](https://en.wikipedia.org/wiki/Customer_acquisition_cost). Compared to those, the cost of a couple of Ethereum transactions is actually very small. + +Additionally, you can leverage the GSN in scenarios where your users pay you off-chain in advance (e.g. via credit card), with each GSN-call deducting from their balance on your system. The possibilities are endless! + +Furthermore, the GSN is set up in such a way where it’s in the relayers' best interest to serve your requests, and there are measures in place to penalize them if they misbehave. All of this happens automatically, so you can safely start using their services worry-free. + + +You can learn more about how the GSN works in [Interacting With `RelayHub`]. + + +## Building a GSN-powered DApp + +Time to build a dapp leveraging the GSN and push it to a testnet. In this section, we will use: + +* The `create-react-app` package to bootstrap a React application, along with [**OpenZeppelin Network JS**] to easily set up a web3 object with GSN support +* [**OpenZeppelin GSN Helpers**] to emulate the GSN in your local ganache instance +* The [`@openzeppelin/contracts-ethereum-package`](https://github.com/OpenZeppelin/openzeppelin-contracts-ethereum-package) smart contract library to get GSN +* The [**OpenZeppelin CLI**] to manage and deploy our contracts + + +It might feel like there are many moving pieces here, but each component has a well-defined role in building this application. That said, if you are new to the OpenZeppelin platform, it may help to look into the [OpenZeppelin Contracts GSN guide] and the [Building a Dapp](./building-a-dapp) tutorial before you continue reading. + + +We will create a simple contract that just counts transactions sent to it, but will tie it into the GSN so that users will not have to pay for the gas when sending these transactions. Let’s get started! + +### Setting up the Environment + +We will begin by creating a new npm project and installing all dependencies, including [Ganache](https://www.trufflesuite.com/ganache) (which we’ll use to [run a local network](./deploying-and-interacting#setting-up-a-local-blockchain)): + +```console +$ mkdir gsn-dapp && cd gsn-dapp +$ npm init -y +$ npm install @openzeppelin/network +$ npm install --save-dev @openzeppelin/gsn-helpers @openzeppelin/contracts-ethereum-package @openzeppelin/upgrades @openzeppelin/cli ganache-cli +``` + +Use the CLI to set up a new project and follow the prompts so we can write our first contract. + +```console +$ npx oz init +``` + + +Check out [Getting Started with the OpenZeppelin CLI](./deploying-and-interacting#interacting-from-the-console) if you’re unfamiliar with it. + + +### Creating our Contract + +We will write our vanilla `Counter` contract in the newly created `contracts` folder. + +```solidity +// contracts/Counter.sol +pragma solidity ^0.5.0; + +contract Counter + uint256 public value; + + function increase() public { + value += 1; + +} +``` + +This is simple enough. Now, let’s modify it to add GSN support. This requires extending from the `GSNRecipient` contract and implementing the `acceptRelayedCall` method. This method must return whether we accept or reject to pay for a user transaction. For the sake of simplicity, we will be paying for all transactions sent to this contract. + + +For most (d)apps, it is probably not a good idea to have such a generous policy since any malicious user could easily drain your contract’s funds. Check out our [guide on GSN payment strategies] for different approaches to this problem. + + +```solidity +// contracts/Counter.sol +pragma solidity ^0.5.0; + +import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol"; + +contract Counter is GSNRecipient + uint256 public value; + + function increase() public { + value += 1; + + + function acceptRelayedCall( + address relay, + address from, + bytes calldata encodedFunction, + uint256 transactionFee, + uint256 gasPrice, + uint256 gasLimit, + uint256 nonce, + bytes calldata approvalData, + uint256 maxPossibleCharge + ) external view returns (uint256, bytes memory) + return _approveRelayedCall(); + + + // We won't do any pre or post processing, so leave _preRelayedCall and _postRelayedCall empty + function _preRelayedCall(bytes memory context) internal returns (bytes32) + + + function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal + +} +``` + +Start ganache on a separate terminal by running `npx ganache-cli`. Then, create an instance of our new contract using the OpenZeppelin CLI with `npx oz create` and follow the prompts, including choosing to call a function to initialize the instance. + +Be sure to take note of the address of your instance, which is returned at the end of this process! + + +It is important that you remember to call the `initialize()` function when creating the contract, as this will get your contract ready to be used in the GSN. + + +```console +$ openzeppelin create +✓ Compiled contracts with solc 0.5.9 (commit.e560f70d) +? Pick a contract to instantiate Counter +? Pick a network development +All contracts are up to date +? Call a function to initialize the instance after creating it? Yes +? Select which function * initialize() +✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601 +``` + +Great! Now, if we deployed this contract to mainnet or the goerli testnet, we would be almost ready to start sending gasless transactions to it, since the GSN is already set up on both of those networks. However, since we are on a local ganache, we’ll need to set it up ourselves. + +### Deploying a Local GSN for Development + +The GSN is composed of a central `RelayHub` contract that coordinates all relayed transactions, as well as multiple decentralized relayers. The relayers are processes that receive requests to relay a transaction via an HTTP interface and send them to the network via the `RelayHub`. + +With ganache running, you can start a relayer in a new terminal using the following command from the [**OpenZeppelin GSN Helpers**]: + +```console +$ npx oz-gsn run-relayer +Deploying singleton RelayHub instance +RelayHub deployed at 0xd216153c06e857cd7f72665e0af1d7d82172f494 +Starting relayer + -Url http://localhost:8090 +... +RelayHttpServer starting. version: 0.4.0 +... +Relay funded. Balance: 4999305160000000000 +``` + + +Under the hood, this command takes care of several steps to have a local relayer up and running. First, it will download a relayer binary for your platform and start it. It will then deploy the `RelayHub` contract to your local ganache, registering the relayer on the hub, and funding it so it can relay transactions. You can run these steps individually by using other `oz-gsn commands` or even [directly from your JavaScript code]. + + +The last step will be to _fund_ our `Counter` contract. GSN relayers require recipient contracts to have funds since they will then charge the cost of the relayed transaction (plus a fee!) to it. We will again use the `oz-gsn` set of commands to do this: + +```console +$ npx oz-gsn fund-recipient --recipient 0xCfEB869F69431e42cdB54A4F4f105C19C080A601 +``` + +Make sure to replace the recipient address with the address of your `Counter` contract instance! + + +Cool! Now that we have our GSN-powered contract and a local GSN to try it out, let’s build a small (d)app. + +### Creating the Dapp + +We will create our (d)app using the `create-react-app` package, which bootstraps a simple client-side application using React. + +```console +$ npx create-react-app client +``` + +First, create a symlink so we can access our compiled contract `.json` files. From inside the `client/src` directory, run: +```console +$ ln -ns ../../build +``` + +This will allow our front end to reach our contract artifacts. + +Then, replace `client/src/App.js` with the following code. This will use [**OpenZeppelin Network JS**] to create a new provider connected to the local network. It will use a key generated on the spot to sign all transactions on behalf of the user and will use the GSN to relay them to the network. This allows your users to start interacting with your (d)app right away, even if they do not have MetaMask installed, an Ethereum account, or any Ether at all. + +```jsx +// client/src/App.js +import React, useState, useEffect, useCallback from "react"; +import useWeb3Network from "@openzeppelin/network/react"; + +const PROVIDER_URL = "http://127.0.0.1:8545"; + +function App() + // get GSN web3 + const context = useWeb3Network(PROVIDER_URL, { + gsn: { dev: true + }); + + const accounts, lib = context; + + // load Counter json artifact + const counterJSON = require("./build/contracts/Counter.json"); + + // load Counter Instance + const [counterInstance, setCounterInstance] = useState(undefined); + + if ( + !counterInstance && + context && + context.networkId + ) + const deployedNetwork = counterJSON.networks[context.networkId.toString()]; + const instance = new context.lib.eth.Contract(counterJSON.abi, deployedNetwork.address); + setCounterInstance(instance); + + + const [count, setCount] = useState(0); + + const getCount = useCallback(async () => + if (counterInstance) { + // Get the value from the contract to prove it worked. + const response = await counterInstance.methods.value().call(); + // Update state with the result. + setCount(response); + + }, [counterInstance]); + + useEffect(() => + getCount(); + , [counterInstance, getCount]); + + const increase = async () => + await counterInstance.methods.increase().send({ from: accounts[0] ); + getCount(); + }; + + return ( +
+

Counter counterInstance

+ lib && !counterInstance && ( + +
Contract Instance or network not loaded.
+
+ ) + lib && counterInstance && ( + +
+
Counter Value:
+
{count
+
+
Counter Actions
+ +
+ )} +
+ ); +} + +export default App; + +``` + + +You can pass a `dev: true` flag to the `gsn` options when setting up the provider. This will use the [GSNDevProvider] instead of the regular GSN provider. This is a provider set up specifically for testing or development, and it _does not require a relayer to be running_ to work. This can make development easier, but it will feel less like the actual GSN experience. If you want to use an actual relayer, you can run `npx oz-gsn run-relayer` locally (see the [Preparing a Testing Environment] for more info). + + +Great! We can now fire up our application running `npm start` from within the `client` folder. Remember to keep both your ganache and relayer up and running. You should be able to send transactions to your `Counter` contract without having to use MetaMask or have any ETH at all! + +### Moving to a Testnet + +It is not very impressive to send a local transaction in your ganache network, where you already have a bunch of fully-funded accounts. To witness the GSN at its full potential, let’s move our application to the goerli testnet. If you later want to go onto mainnet, the instructions are the same. + +You will need to create a new entry in the `networks.js` file, with a goerli account that has been funded. For detailed instructions on how to do this, check out [Deploying to Public Tests Network](./connecting-to-public-test-networks). + +We can now deploy our `Counter` contract to goerli: + +```console +$ openzeppelin create +✓ Compiled contracts with solc 0.5.9 (commit.e560f70d) +? Pick a contract to instantiate: Counter +? Pick a network: goerli +✓ Added contract Counter +✓ Contract Counter deployed +? Call a function to initialize the instance after creating it?: Yes +? Select which function * initialize() +✓ Setting everything up to create contract instances +✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601 +``` + +The next step will be to instruct our (d)app to connect to a goerli node instead of the local network. Change the `PROVIDER_URL` in your `App.js` to, for example, an Infura goerli endpoint. + +We will now be using a real GSN provider rather than our developer environment, so you may want to also provide a [configuration object], which will give you more control over things such as the gas price you are willing to pay. For production (d)apps, you will want to configure this to your requirements. + +```javascript +import useWeb3Network, useEphemeralKey from "@openzeppelin/network/react"; + +// inside App.js#App() +const context = useWeb3Network('https://goerli.infura.io/v3/' + INFURA_API_TOKEN, + gsn: { signKey: useEphemeralKey() +}); +``` + +We are almost there! If you try to use your (d)app now, you will notice that you are not able to send any transactions. This is because your `Counter` contract has not been funded on this network yet. Instead of using the `oz-gsn fund-recipient` command we used earlier, we will now use the [online gsn-tool](https://gsn.openzeppelin.com) by pasting in the address of your instance. To do this, the web interface requires that you use MetaMask on the goerli Network, which will allow you to deposit funds into your contract. + +![OpenZeppelin GSN Dapp Tool](/GSNDappTool.png) + +That’s it! We can now start sending transactions to our `Counter` contract on the goerli network from our browser without even having MetaMask installed. + +## The GSN Starter Kit + +[Starter Kits] are pre-configured project templates to bootstrap dapp development. One of them, the [GSN Starter Kit], is a ready-to-use dapp connected to the GSN, with a similar setup as the one we built from scratch in the previous section. + +If you are building a new dapp and want to include meta-transaction support, you can run `oz unpack gsn` to jumpstart your development and start with a GSN-enabled box! + +## Next steps + +To learn more about the GSN, head over to the following resources: + +* To learn how to use OpenZeppelin Contracts to **build a GSN-capable contract**, head to the [GSN basics guide]. +* If you want to learn how to use OpenZeppelin Contracts' **pre-made accept and charge strategies**, go to the [GSN Strategies guide]. +* If instead you wish to know more about how to **use GSN from your application**, head to the [OpenZeppelin GSN Provider guides]. +* For information on how to **test GSN-enabled contracts**, go to the [OpenZeppelin GSN Helpers documentation]. diff --git a/docs/content/contracts/5.x/learn/setting-up-a-node-project.mdx b/docs/content/contracts/5.x/learn/setting-up-a-node-project.mdx new file mode 100644 index 00000000..5d5d1b0a --- /dev/null +++ b/docs/content/contracts/5.x/learn/setting-up-a-node-project.mdx @@ -0,0 +1,86 @@ +--- +title: Setting up a Node project +--- + +New software industries often start out with every project sharing the same technology stack. The Ethereum ecosystem is no exception, and the language of choice is [JavaScript](https://en.wikipedia.org/wiki/JavaScript). Many Ethereum libraries, including OpenZeppelin software, are written in JavaScript or one of its variants. + +JavaScript code is traditionally run on a web browser as part of a website, but it can be also executed as a standalone process using [Node](https://nodejs.org). + +This guide will help you get your Node development environment set up, which you’ll need to use the different OpenZeppelin tools and other third party products. + + +If you are already familiar with Node, npm and Git, feel free to skip this guide! + + +## Installing Node + +There are multiple ways to get Node on your machine: you can get it either via a [package manager](https://nodejs.org/en/download/package-manager/) or by downloading [the installer](https://nodejs.org/en/download/prebuilt-installer) directly. + + +If you are running Windows consider using [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/nodejs/setup-on-wsl2) as much of the ecosystem is written for Linux. + + +Once you’re done, run `node --version` on a terminal to check your installation: any [active or maintenance version](https://nodejs.org/en/about/previous-releases) should be compatible with most Ethereum software. + +```console +$ node --version +v20.17.0 +``` + +## Creating a project + +JavaScript software is often bundled in _packages_, which are distributed via the [npm registry](https://www.npmjs.com/). A package is simply a directory that contains a file called `package.json`, describing the package’s name, version, content, and others. When you build your own project, you will be creating a package, even if you don’t plan to distribute it. + +All Node installations include a command-line client for the npm registry, which you’ll use while developing your own projects. To start a new project, create a directory for it: + +```console +$ mkdir learn && cd learn +``` + +Then we can initialize it: + +```console +$ npm init -y +``` + +Simple as that! Your newly created `package.json` file will evolve as your project grows, such as when installing dependencies with `npm install`. + + +JavaScript and npm are some of the most used software tools in the world: if you’re ever in doubt, you’ll find plenty of information about them online. + + +### Using `npx` + +There are two broads type of packages stored in the npm registry: _libraries_ and _executables_. Installed libraries are used like any other piece of JavaScript code, but executables are special. + +A third binary was included when installing node: [`npx`](https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner). This is used to run executables installed locally in your project. + +Whilst [Hardhat](https://hardhat.org/) can be installed globally we recommend installing locally in each project so that you can control the version on a project by project basis. + +For clarity we’ll display the full command in our guides including `npx` so we don’t get errors due to the binary not being in the system path: + +```console +$ hardhat init +hardhat: command not found +$ npx hardhat init +👷 Welcome to Hardhat v2.22.12 👷‍ +? What do you want to do? … +``` + + +Make sure you are inside your project’s directory when running `npx`! Otherwise, it will download the full executable again just to run that command, which most of the time is _not_ what you want. + + +## Tracking with Version Control + +Before you get coding, you should add [version control software](https://en.wikipedia.org/wiki/Version_control) to your project to track changes. + +By far, the most used tool is [Git](https://git-scm.com), often in conjunction with [GitHub](https://github.com) for hosting purposes. Indeed, you will find the full source code and history of all OpenZeppelin software in our [GitHub repository](https://github.com/OpenZeppelin). + + +If you’ve never used Git before, a good starting place is the [Git Handbook](https://guides.github.com/introduction/git-handbook/). + + + +Don’t commit secrets such as mnemonics, private keys and API keys to version control! Make sure you [`.gitignore`](https://git-scm.com/docs/gitignore) files with secrets. + diff --git a/docs/content/contracts/5.x/learn/upgrading-smart-contracts.mdx b/docs/content/contracts/5.x/learn/upgrading-smart-contracts.mdx new file mode 100644 index 00000000..2edc268b --- /dev/null +++ b/docs/content/contracts/5.x/learn/upgrading-smart-contracts.mdx @@ -0,0 +1,337 @@ +--- +title: Upgrading smart contracts +--- + +Smart contracts deployed using [OpenZeppelin Upgrades Plugins](/upgrades-plugins) can be ***upgraded*** to modify their code, while preserving their address, state, and balance. This allows you to iteratively add new features to your project, or fix any bugs you may find [in production](./preparing-for-mainnet). + +Throughout this guide, we will learn: + +* [Why upgrades are important](#what's-in-an-upgrade) +* [Upgrade our Box using the Upgrades Plugins](#upgrading-using-the-upgrades-plugins) +* [Learn how upgrades work under the hood](#how-upgrades-work) +* [Learn how to write upgradeable contracts](#limitations-of-contract-upgrades) + +## What's in an upgrade + +Smart contracts in Ethereum are immutable by default. Once you create them there is no way to alter them, effectively acting as an unbreakable contract among participants. + +However, for some scenarios, it is desirable to be able to modify them. Think of a traditional contract between two parties: if they both agreed to change it, they would be able to do so. On Ethereum, they may desire to alter a smart contract to fix a bug they found (which might even lead to a hacker stealing their funds!), to add additional features, or simply to change the rules enforced by it. + +Here’s what you’d need to do to fix a bug in a contract you cannot upgrade: + +1. Deploy a new version of the contract +2. Manually migrate all state from the old one contract to the new one (which can be very expensive in terms of gas fees!) +3. Update all contracts that interacted with the old contract to use the address of the new one +4. Reach out to all your users and convince them to start using the new deployment (and handle both contracts being used simultaneously, as users are slow to migrate) + +To avoid going through this mess, we have built contract upgrades directly into our plugins. This allows us to **change the contract code, while preserving the state, balance, and address**. Let’s see it in action. + +## Upgrading using the Upgrades Plugins + +Whenever you deploy a new contract using `deployProxy` in the [OpenZeppelin Upgrades Plugins](/upgrades-plugins), that contract instance can be ***upgraded*** later. By default, only the address that originally deployed the contract has the rights to upgrade it. + +`deployProxy` will create the following transactions: + +1. Deploy the implementation contract (our `Box` contract) +2. Deploy the proxy contract and run any initializer function. + * The proxy deployment automatically deploys a `ProxyAdmin` contract (the admin for our proxy) in the scenario below. + +Let’s see how it works, by deploying an upgradeable version of our `Box` contract, using the same setup as when [we deployed earlier](./deploying-and-interacting#deploying-a-smart-contract): + +```solidity +pragma solidity ^0.8.0; + +contract Box { + uint256 private _value; + + // Emitted when the stored value changes + event ValueChanged(uint256 value); + + // Stores a new value in the contract + function store(uint256 value) public { + _value = value; + emit ValueChanged(value); + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return _value; + } +} +``` + +We first need to install the Upgrades Plugin. + +Install the [Hardhat Upgrades](/upgrades-plugins/hardhat-upgrades) plugin. +```bash +npm install --save-dev @openzeppelin/hardhat-upgrades +``` + +We then need to configure Hardhat to use our `@openzeppelin/hardhat-upgrades` plugin. To do this add the plugin in your `hardhat.config.js` file as follows. + +```js +... +require("@nomicfoundation/hardhat-ethers"); +require('@openzeppelin/hardhat-upgrades'); +... +module.exports = { +... +}; +``` + +In order to upgrade a contract like `Box` we need to first deploy it as an upgradeable contract, which is a different deployment procedure than we’ve seen so far. We will initialize our Box contract by calling `store` with the value 42. + +With Hardhat, we use [scripts](https://hardhat.org/hardhat-runner/docs/advanced/scripts#writing-scripts-with-hardhat) to deploy upgradeable contracts. + +We will create a script to deploy our upgradeable Box contract using [`deployProxy`](/upgrades-plugins/api-hardhat-upgrades#deployproxy). We will save this file as `scripts/deploy_upgradeable_box.js`. + +```js +const { ethers, upgrades } = require('hardhat'); + +async function main() { + const Box = await ethers.getContractFactory('Box'); + console.log('Deploying Box...'); + const box = await upgrades.deployProxy(Box, [42], { initializer: 'store' }); + await box.waitForDeployment(); + console.log('Box deployed to:', await box.getAddress()); +} + +main(); +``` + +We can then deploy our upgradeable contract. + +Using the `run` command, we can deploy the `Box` contract to the `development` network. + +```console +$ npx hardhat run --network localhost scripts/deploy_upgradeable_box.js +Deploying Box... +Box deployed to: 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 +``` + +We can then interact with our `Box` contract to `retrieve` the value that we stored during initialization. + +We will use the [Hardhat console](https://hardhat.org/guides/hardhat-console.html) to interact with our upgraded `Box` contract. + +We need to specify the address of our proxy contract from when we deployed our `Box` contract. + +```console +$ npx hardhat console --network localhost +Welcome to Node.js v20.17.0. +Type ".help" for more information. +> const Box = await ethers.getContractFactory('Box'); +undefined +> const box = await Box.attach('0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0'); +undefined +> (await box.retrieve()).toString(); +'42' +``` + +For the sake of the example, let’s say we want to add a new feature: a function that increments the `value` stored in a new version of `Box`. + +```solidity +pragma solidity ^0.8.0; + +contract BoxV2 { + // ... code from Box.sol + + // Increments the stored value by 1 + function increment() public { + _value = _value + 1; + emit ValueChanged(_value); + } +} +``` + +After creating the Solidity file, we can now upgrade the instance we had deployed earlier using the `upgradeProxy` function. + +`upgradeProxy` will create the following transactions: + +1. Deploy the implementation contract (our `BoxV2` contract) +2. Call the `ProxyAdmin` to update the proxy contract to use the new implementation. + +We will create a script to upgrade our `Box` contract to use `BoxV2` using [`upgradeProxy`](/upgrades-plugins/api-hardhat-upgrades#upgradeproxy). We will save this file as `scripts/upgrade_box.js`. +We need to specify the address of our proxy contract from when we deployed our `Box` contract. + +```js +const { ethers, upgrades } = require('hardhat'); + +async function main() { + const BoxV2 = await ethers.getContractFactory('BoxV2'); + console.log('Upgrading Box...'); + await upgrades.upgradeProxy('0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0', BoxV2); + console.log('Box upgraded'); +} + +main(); +``` + +We can then deploy our upgradeable contract. + +Using the `run` command, we can upgrade the `Box` contract on the `development` network. + +```console +$ npx hardhat run --network localhost scripts/upgrade_box.js +Compiled 1 Solidity file successfully (evm target: paris). +Upgrading Box... +Box upgraded +``` + +Done! Our `Box` instance has been upgraded to the latest version of the code, **while keeping its state and the same address as before**. We didn’t need to deploy a new one at a new address, nor manually copy the `value` from the old `Box` to the new one. + +Let’s try it out by invoking the new `increment` function, and checking the `value` afterwards: + +We need to specify the address of our proxy contract from when we deployed our `Box` contract. + +```console +$ npx hardhat console --network localhost +Welcome to Node.js v20.17.0. +Type ".help" for more information. +> const BoxV2 = await ethers.getContractFactory('BoxV2'); +undefined +> const box = await BoxV2.attach('0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0'); +undefined +> await box.increment(); +... +> (await box.retrieve()).toString(); +'43' +``` + +That’s it! Notice how the `value` of the `Box` was preserved throughout the upgrade, as well as its address. And this process is the same regardless of whether you are working on a local blockchain, a testnet, or the main network. + +Let’s see how the [OpenZeppelin Upgrades Plugins](/upgrades-plugins) accomplish this. + +## How upgrades work + +_This section will be more theory-heavy than others: feel free to skip over it and return later if you are curious._ + +When you create a new upgradeable contract instance, the [OpenZeppelin Upgrades Plugins](/upgrades-plugins) actually deploys three contracts: + +1. The contract you have written, which is known as the _implementation contract_ containing the _logic_. +2. A _proxy_ to the _implementation contract_, which is the contract that you actually interact with. +3. A _ProxyAdmin_ to be the admin of the _proxy_. + +Here, the _proxy_ is a simple contract that just _delegates_ all calls to an implementation contract. A _delegate call_ is similar to a regular call, except that all code is executed in the context of the caller, not of the callee. Because of this, a `transfer` in the implementation contract’s code will actually transfer the proxy’s balance, and any reads or writes to the contract storage will read or write from the proxy’s own storage. + +This allows us to ***decouple*** a contract’s state and code: the proxy holds the state, while the implementation contract provides the code. And it also allows us to ***change*** the code by just having the proxy delegate to a different implementation contract. + +An upgrade then involves the following steps: + +1. Deploy the new implementation contract. +2. Send a transaction to the proxy that updates its implementation address to the new one. + + +You can have multiple proxies using the same implementation contract, so you can save gas using this pattern if you plan to deploy multiple copies of the same contract. + + +Any user of the smart contract always interacts with the proxy, **which never changes its address**. This allows you to roll out an upgrade or fix a bug without requesting your users to change anything on their end - they just keep interacting with the same address as always. + + +If you want to learn more about how OpenZeppelin proxies work, check out [Proxies](/upgrades-plugins/proxies). + + +## Limitations of contract upgrades + +While any smart contract can be made upgradeable, some restrictions of the Solidity language need to be worked around. These come up when writing both the initial version of contract and the version we’ll upgrade it to. + +### Initialization + +Upgradeable contracts cannot have a `constructor`. To help you run initialization code, [**OpenZeppelin Contracts**](/contracts) provides the [`Initializable`](/contracts/5.x/api/proxy#Initializable) base contract that allows you to tag a method as [`initializer`](/contracts/5.x/api/proxy#Initializable-initializer--), ensuring it can be run only once. + +As an example, let’s write a new version of the `Box` contract with an initializer, storing the address of an `admin` who will be the only one allowed to change its contents. + +```solidity +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract AdminBox is Initializable { + uint256 private _value; + address private _admin; + + // Emitted when the stored value changes + event ValueChanged(uint256 value); + + function initialize(address admin) public initializer { + _admin = admin; + } + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() initializer {} + + // Stores a new value in the contract + function store(uint256 value) public { + require(msg.sender == _admin, "AdminBox: not admin"); + _value = value; + emit ValueChanged(value); + } + + // Reads the last stored value + function retrieve() public view returns (uint256) { + return _value; + } +} +``` + +When deploying this contract, we will need to specify the `initializer` function name (only when the name is not the default of `initialize`) and provide the admin address that we want to use. + +```js +const { ethers, upgrades } = require('hardhat'); + +async function main() { + const AdminBox = await ethers.getContractFactory('AdminBox'); + console.log('Deploying AdminBox...'); + const adminBox = await upgrades.deployProxy(AdminBox, ['0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E'], { initializer: 'initialize' }); + await adminBox.waitForDeployment(); + console.log('AdminBox deployed to:', await adminBox.getAddress()); +} + +main(); +``` + +For all practical purposes, the initializer acts as a constructor. However, keep in mind that since it’s a regular function, you will need to manually call the initializers of all base contracts (if any). + +You may have noticed that we included a constructor as well as an initializer. This constructor serves the purpose of leaving the implementation contract in an initialized state, which is a mitigation against certain potential attacks. + +To learn more about this and other caveats when writing upgradeable contracts, check out our [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable) guide. + +### Upgrading + +Due to technical limitations, when you upgrade a contract to a new version you cannot change the ***storage layout*** of that contract. + +This means that, if you have already declared a state variable in your contract, you cannot remove it, change its type, or declare another variable before it. In our `Box` example, it means that we can only add new state variables _after_ `value`. + +```solidity +contract Box { + uint256 private _value; + + // We can safely add a new variable after the ones we had declared + address private _owner; + + // ... +} +``` + +Fortunately, this limitation only affects state variables. You can change the contract’s functions and events as you wish. + + +If you accidentally mess up with your contract’s storage layout, the Upgrades Plugins will warn you when you try to upgrade. + + +To learn more about this limitation, head over to the [Modifying Your Contracts](/upgrades-plugins/writing-upgradeable#modifying-your-contracts) guide. + +## Testing + +To test upgradeable contracts we should create unit tests for the implementation contract, along with creating higher level tests for testing interaction via the proxy. We can use `deployProxy` in our tests just like we do when we deploy. + +When we want to upgrade, we should create unit tests for the new implementation contract, along with creating higher level tests for testing interaction via the proxy after we upgrade using `upgradeProxy`, checking that state is maintained across upgrades. + +## Possible issues + +While learning how to upgrade contract you might find yourself in a situation of conflicting contracts on the local environment. +To solve this consider using the follow steps: +Stop the node ctrl+C which was ran with `npx hardhat node`. Execute a clean: `npx hardhat clean`. + +## Next steps + +Now that you know how to upgrade your smart contracts, and can iteratively develop your project, it’s time to take your project to [testnet](./connecting-to-public-test-networks) and to [production](./preparing-for-mainnet)! You can rest with the confidence that, should a bug appear, you have the tools to modify your contract and change it. diff --git a/docs/content/contracts/5.x/learn/webauthn-smart-accounts.mdx b/docs/content/contracts/5.x/learn/webauthn-smart-accounts.mdx new file mode 100644 index 00000000..aa8e747e --- /dev/null +++ b/docs/content/contracts/5.x/learn/webauthn-smart-accounts.mdx @@ -0,0 +1,1942 @@ +--- +title: WebAuthn Smart Accounts +--- + +import { Step, Steps } from 'fumadocs-ui/components/steps'; + +Account abstraction is becoming a vital tool to advance onchain technology for everyday users, making it easier to create and use a wallet without needing to handle a private key. One of the most popular forms of this is through [Passkeys](https://www.webauthn.me/passkeys), which use the WebAuthn standard to create cryptographic [Authentication Assertions](https://www.w3.org/TR/webauthn-2/#sctn-verifying-assertion) across multiple devices and ecosystems. + +OpenZeppelin's `WebAuthn.sol` contract enables smart accounts to verify these WebAuthn assertions onchain. This creates a powerful and secure user experience that leverages biometrics and industry-standard cryptography for wallet interactions. + +In this tutorial we'll show you how you can build fullstack application that allows users to create smart accounts with WebAuthn passkeys and conduct an example user operation like minting an NFT. + +## Prerequisites + +Before we get started make sure you have the following installed + +- [`Node.js`](https://nodejs.org/en/download) +- [`pnpm`](https://pnpm.io/installation) +- [`Foundry`](https://getfoundry.sh/introduction/installation) + +Once you have confirmed those are all installed, let's make sure we have a wallet setup with Foundry. If you already have one setup and funded with testnet eth, you can skip this part. + +### Wallet Setup + +To make a new wallet run the following command: + +```bash title="Shell" +cast wallet new -p ~/.foundry/keystores sepolia +``` + +This will prompt you for a password and then create a new keypair and encrypt the private key locally, which is much safer than working with plain text private keys. The public address should be printed when you create the wallet but you can retrieve it at any time with the following command: + +```bash title="Shell" +cast wallet address --account sepolia +``` + + + Make sure to only use this wallet for testnet funds! + + + +### Project Structure + +For context our final project will look something like this + +``` +. +└── contracts // Smart contracts +└── server // Secure server environment +└── shared // Shared addresses and ABIs +└── client // Web UI +``` + +Let's make an empty directory that will store all of this and then `cd` into it. + +```bash title="Shell" +mkdir webauthn-tutorial +cd webauthn-tutorial +``` + +With the initial structure setup we can move on to initializing the different projects. + +## Contracts + +While inside `webauthn-tutorial` run the command below to setup a new foundry project for our contracts, then move into it. + +```bash title="Shell" +forge init contracts +cd contracts +``` + +Inside the `contracts` project go ahead and delete the default `Counter` files like so: + +```bash title="Shell" +rm src/* test/* script/* +``` + +### Setup + +Next we'll install the OpenZeppelin contracts library which will include everything else we need to setup a WebAuthn account and factory. + +```bash title="Shell" +foundry install OpenZeppelin/openzeppelin-contracts@v5.5.0-rc.0 +``` + +For our contracts we need to make three files inside of `src`: +- `AccountWebAuthn.sol` - Account implementation using WebAuthn signatures +- `AccountFactory.sol` - Account factory +- `MyNFT.sol` - Example NFT contract for testing User Operations + +Inside `AccountWebAuthn.sol` paste in the following code: + +```solidity title="AccountWebAuthn.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 {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.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 {SignerWebAuthn} from "@openzeppelin/contracts/utils/cryptography/signers/SignerWebAuthn.sol"; +import {SignerP256} from "@openzeppelin/contracts/utils/cryptography/signers/SignerP256.sol"; + + +contract AccountWebAuthn is + Initializable, + Account, + EIP712, + ERC7739, + ERC7821, + SignerWebAuthn, + ERC721Holder, + ERC1155Holder +{ + constructor() + EIP712("AccountWebAuthn", "1") + SignerP256( + 0x6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296, + 0x4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5 + ) + {} + + function initializeWebAuthn(bytes32 qx, bytes32 qy) public initializer { + _setSigner(qx, qy); // Set the P256 public key + } + + /** + * @dev Override to allow EntryPoint to execute transactions + */ + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view override returns (bool) { + return + caller == address(entryPoint()) || + super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} +``` + +Let's break down the key components of our WebAuthn account implementation: + +Our contract inherits from `Account.sol`, which provides the core ERC-4337 functionality, along with `EIP712.sol` for typed data signatures. We also include `ERC721Holder.sol` and `ERC1155Holder.sol` to enable the account to receive NFTs and multi-tokens. The `ERC7739.sol` extension enables readable typed signatures that prevent replay attacks, while `ERC7821.sol` provides the minimal batch executor interface for transaction batching. + +The account uses a factory pattern with minimal proxies (like `Clones.sol`) for gas-efficient deployment. Since each account is deployed as a proxy, we use an initializer function instead of a constructor to set up the account's state after deployment. + +WebAuthn verification relies on `P256.sol` elliptic curve operations, which require a valid public key during contract construction. To work around this factory pattern constraint, we provide a dummy public key in the constructor and set the real WebAuthn public key through the `initializeWebAuthn` function. Once initialized, the signer cannot be changed. + +Now paste the following code into `AccountFactory.sol`: + +```solidity title="AccountFactory.sol" +// contracts/AccountFactory.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @dev A factory contract to create accounts on demand. + */ +contract AccountFactory { + using Clones for address; + using Address for address; + + address private immutable _impl; + + constructor(address impl_) { + require(impl_.code.length > 0); + _impl = impl_; + } + + /// @dev Predict the address of the account + function predictAddress(bytes calldata callData) public view returns (address) { + return _impl.predictDeterministicAddress(keccak256(callData), address(this)); + } + + /// @dev Create clone accounts on demand + function cloneAndInitialize(bytes calldata callData) public returns (address) { + address predicted = predictAddress(callData); + if (predicted.code.length == 0) { + _impl.cloneDeterministic(keccak256(callData)); + predicted.functionCall(callData); + } + return predicted; + } +} +``` + +The factory will take an implementation address which it will use for creating new accounts. There are two public functions; one is to `predictAddress` so we could fund the account before creating if we wanted to, and the second is `cloneAndInitialize` which will create the new account from our implementation address. + +Finally we'll add the code for `MyNFT.sol`: + +```solidity title="MyNFT.sol" +// SPDX-License-Identifier: MIT +// Compatible with OpenZeppelin Contracts ^5.4.0 +pragma solidity ^0.8.27; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyNFT is ERC721, Ownable { + uint256 private _nextTokenId; + + constructor(address initialOwner) + ERC721("MyNFT", "MYNFT") + Ownable(initialOwner) + {} + + function safeMint(address to) public returns (uint256) { + uint256 tokenId = _nextTokenId++; + _safeMint(to, tokenId); + return tokenId; + } +} +``` + +This is a really simple NFT contract that has minting enabled, with the small exception that we've removed the `onlyOwner` modifier from the `safeMint` function to make it simpler for our smart account to interact with it. + +### Deployment + +With all of our contracts put together we can make a new file under the `script` directory called `Deploy.s.sol` and put the following code inside: + +```solidity title="Deploy.s.sol" +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import "forge-std/Script.sol"; +import "../src/AccountWebAuthn.sol"; +import "../src/AccountFactory.sol"; +import "../src/MyNFT.sol"; + +contract Deploy is Script { + function run() external { + uint256 deployerPrivateKey; + + // Use Anvil's first default account if no private key is provided + // Anvil account #0: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + if (vm.envOr("PRIVATE_KEY", uint256(0)) == 0) { + deployerPrivateKey = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80; + console.log("Using Anvil default account"); + } else { + deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + console.log("Using provided private key"); + } + + vm.startBroadcast(deployerPrivateKey); + + // Deploy AccountWebAuthn implementation + AccountWebAuthn accountImpl = new AccountWebAuthn(); + console.log("AccountWebAuthn implementation deployed at:", address(accountImpl)); + + // Deploy AccountFactory with the implementation address + AccountFactory accountFactory = new AccountFactory(address(accountImpl)); + console.log("AccountFactory deployed at:", address(accountFactory)); + + // Deploy test NFT + MyNFT nftContract = new MyNFT(vm.addr(deployerPrivateKey)); + console.log("AccountWebAuthn implementation deployed at:", address(nftContract)); + + console.log("Deployed by:", vm.addr(deployerPrivateKey)); + + vm.stopBroadcast(); + } +} +``` + +This deployment script will do the following: +- Setup the broadcast with our `PRIVATE_KEY` +- Deploy the `AccountWebAuthn` implementation contract +- Deploy the `AccountFactory` and pass in the recently deployed `AccountWebAuthn` implementation address +- Deploy the `MyNFT` contract with our deployer address as the owner + +Before we can run this script we need to create a `.env` file with the following content: + +```bash +export RPC_URL=https://sepolia.drpc.org +export PRIVATE_KEY=$(cast wallet private-key --account sepolia) +``` + +Thanks to `cast` we can use our wallet private key without keeping it in plain text and instead make it a shell environment variable that can be accessed by Foundry. We're using a public RPC url here but you may want to use one from Alchemy or your DRPC account that won't have rate limits. With that we can go ahead and run the deployment: + +```bash title="Shell" +source .env +forge script script/Deploy.s.sol:Deploy --rpc-url $RPC_URL --broadcast --verify +``` + +This should deploy all three contracts and print the addresses for each in the terminal, as well as save them to `broadcast`. + +### Setup Shared Directory + +By compiling and deploying these contracts we've created the ABI's and Addresses we need across the other pieces of our app. To make it easier to access these constants, let's make make a new directory called `shared`. + +```bash title="Shell" +cd .. # Move out of contracts +mkdir shared +cd shared +``` + +Inside the folder create two files and put in the following content: + +```bash title="Shell" +touch index.ts +``` + +```typescript title="index.ts" +export * from "./EntrypointV08"; +export const FACTORY_ADDRESS = "0xf403e5e9230a233dde99d1c6adffa9d1e81dbd98"; +export const NFT_ADDRESS = "0x1936494b8444aF8585873F478dc826C6Ab76582e"; +export const ENTRYPOINT_ADDRESS = "0x4337084d9e255ff0702461cf8895ce9e3b5ff108"; +export { abi as accountWebAuthnAbi } from "../contracts/out/AccountWebAuthn.sol/AccountWebAuthn.json"; +export { abi as accountFactoryAbi } from "../contracts/out/AccountFactory.sol/AccountFactory.json"; +export { abi as myNftAbi } from "../contracts/out/MyNFT.sol/MyNFT.json"; +``` + +One of the pieces we need to use Account Abstraction is the Entrypoint contract. This is a contract that has the same address across every chain and allows us to submit user operations and have them conducted to our smart accounts. You can create a new file inside `shared` called `EntrypointV08.ts` and paste in the contents below: + +```typescript title="EntrypointV08.ts" +export const entryPointAbi = [ + { inputs: [], stateMutability: "nonpayable", type: "constructor" }, + { + inputs: [ + { internalType: "bool", name: "success", type: "bool" }, + { internalType: "bytes", name: "ret", type: "bytes" }, + ], + name: "DelegateAndRevert", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "opIndex", type: "uint256" }, + { internalType: "string", name: "reason", type: "string" }, + ], + name: "FailedOp", + type: "error", + }, + { + inputs: [ + { internalType: "uint256", name: "opIndex", type: "uint256" }, + { internalType: "string", name: "reason", type: "string" }, + { internalType: "bytes", name: "inner", type: "bytes" }, + ], + name: "FailedOpWithRevert", + type: "error", + }, + { inputs: [], name: "InvalidShortString", type: "error" }, + { + inputs: [{ internalType: "bytes", name: "returnData", type: "bytes" }], + name: "PostOpReverted", + type: "error", + }, + { inputs: [], name: "ReentrancyGuardReentrantCall", type: "error" }, + { + inputs: [{ internalType: "address", name: "sender", type: "address" }], + name: "SenderAddressResult", + type: "error", + }, + { + inputs: [{ internalType: "address", name: "aggregator", type: "address" }], + name: "SignatureValidationFailed", + type: "error", + }, + { + inputs: [{ internalType: "string", name: "str", type: "string" }], + name: "StringTooLong", + type: "error", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "factory", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "paymaster", + type: "address", + }, + ], + name: "AccountDeployed", + type: "event", + }, + { anonymous: false, inputs: [], name: "BeforeExecution", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "totalDeposit", + type: "uint256", + }, + ], + name: "Deposited", + type: "event", + }, + { anonymous: false, inputs: [], name: "EIP712DomainChanged", type: "event" }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "revertReason", + type: "bytes", + }, + ], + name: "PostOpRevertReason", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "aggregator", + type: "address", + }, + ], + name: "SignatureAggregatorChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "totalStaked", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "unstakeDelaySec", + type: "uint256", + }, + ], + name: "StakeLocked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "withdrawTime", + type: "uint256", + }, + ], + name: "StakeUnlocked", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "withdrawAddress", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "StakeWithdrawn", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "paymaster", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { indexed: false, internalType: "bool", name: "success", type: "bool" }, + { + indexed: false, + internalType: "uint256", + name: "actualGasCost", + type: "uint256", + }, + { + indexed: false, + internalType: "uint256", + name: "actualGasUsed", + type: "uint256", + }, + ], + name: "UserOperationEvent", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + ], + name: "UserOperationPrefundTooLow", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "bytes32", + name: "userOpHash", + type: "bytes32", + }, + { + indexed: true, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "nonce", + type: "uint256", + }, + { + indexed: false, + internalType: "bytes", + name: "revertReason", + type: "bytes", + }, + ], + name: "UserOperationRevertReason", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "account", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "withdrawAddress", + type: "address", + }, + { + indexed: false, + internalType: "uint256", + name: "amount", + type: "uint256", + }, + ], + name: "Withdrawn", + type: "event", + }, + { + inputs: [ + { internalType: "uint32", name: "unstakeDelaySec", type: "uint32" }, + ], + name: "addStake", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "balanceOf", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "target", type: "address" }, + { internalType: "bytes", name: "data", type: "bytes" }, + ], + name: "delegateAndRevert", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "depositTo", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "eip712Domain", + outputs: [ + { internalType: "bytes1", name: "fields", type: "bytes1" }, + { internalType: "string", name: "name", type: "string" }, + { internalType: "string", name: "version", type: "string" }, + { internalType: "uint256", name: "chainId", type: "uint256" }, + { internalType: "address", name: "verifyingContract", type: "address" }, + { internalType: "bytes32", name: "salt", type: "bytes32" }, + { internalType: "uint256[]", name: "extensions", type: "uint256[]" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "address", name: "account", type: "address" }], + name: "getDepositInfo", + outputs: [ + { + components: [ + { internalType: "uint256", name: "deposit", type: "uint256" }, + { internalType: "bool", name: "staked", type: "bool" }, + { internalType: "uint112", name: "stake", type: "uint112" }, + { internalType: "uint32", name: "unstakeDelaySec", type: "uint32" }, + { internalType: "uint48", name: "withdrawTime", type: "uint48" }, + ], + internalType: "struct IStakeManager.DepositInfo", + name: "info", + type: "tuple", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getDomainSeparatorV4", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint192", name: "key", type: "uint192" }, + ], + name: "getNonce", + outputs: [{ internalType: "uint256", name: "nonce", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "getPackedUserOpTypeHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "pure", + type: "function", + }, + { + inputs: [{ internalType: "bytes", name: "initCode", type: "bytes" }], + name: "getSenderAddress", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "bytes", name: "initCode", type: "bytes" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "bytes32", name: "gasFees", type: "bytes32" }, + { internalType: "bytes", name: "paymasterAndData", type: "bytes" }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct PackedUserOperation", + name: "userOp", + type: "tuple", + }, + ], + name: "getUserOpHash", + outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + components: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "bytes", name: "initCode", type: "bytes" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "bytes32", name: "gasFees", type: "bytes32" }, + { + internalType: "bytes", + name: "paymasterAndData", + type: "bytes", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct PackedUserOperation[]", + name: "userOps", + type: "tuple[]", + }, + { + internalType: "contract IAggregator", + name: "aggregator", + type: "address", + }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct IEntryPoint.UserOpsPerAggregator[]", + name: "opsPerAggregator", + type: "tuple[]", + }, + { internalType: "address payable", name: "beneficiary", type: "address" }, + ], + name: "handleAggregatedOps", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { internalType: "bytes", name: "initCode", type: "bytes" }, + { internalType: "bytes", name: "callData", type: "bytes" }, + { + internalType: "bytes32", + name: "accountGasLimits", + type: "bytes32", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "bytes32", name: "gasFees", type: "bytes32" }, + { internalType: "bytes", name: "paymasterAndData", type: "bytes" }, + { internalType: "bytes", name: "signature", type: "bytes" }, + ], + internalType: "struct PackedUserOperation[]", + name: "ops", + type: "tuple[]", + }, + { internalType: "address payable", name: "beneficiary", type: "address" }, + ], + name: "handleOps", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [{ internalType: "uint192", name: "key", type: "uint192" }], + name: "incrementNonce", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "bytes", name: "callData", type: "bytes" }, + { + components: [ + { + components: [ + { internalType: "address", name: "sender", type: "address" }, + { internalType: "uint256", name: "nonce", type: "uint256" }, + { + internalType: "uint256", + name: "verificationGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "callGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "paymasterVerificationGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "paymasterPostOpGasLimit", + type: "uint256", + }, + { + internalType: "uint256", + name: "preVerificationGas", + type: "uint256", + }, + { internalType: "address", name: "paymaster", type: "address" }, + { + internalType: "uint256", + name: "maxFeePerGas", + type: "uint256", + }, + { + internalType: "uint256", + name: "maxPriorityFeePerGas", + type: "uint256", + }, + ], + internalType: "struct EntryPoint.MemoryUserOp", + name: "mUserOp", + type: "tuple", + }, + { internalType: "bytes32", name: "userOpHash", type: "bytes32" }, + { internalType: "uint256", name: "prefund", type: "uint256" }, + { internalType: "uint256", name: "contextOffset", type: "uint256" }, + { internalType: "uint256", name: "preOpGas", type: "uint256" }, + ], + internalType: "struct EntryPoint.UserOpInfo", + name: "opInfo", + type: "tuple", + }, + { internalType: "bytes", name: "context", type: "bytes" }, + ], + name: "innerHandleOp", + outputs: [ + { internalType: "uint256", name: "actualGasCost", type: "uint256" }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { internalType: "address", name: "", type: "address" }, + { internalType: "uint192", name: "", type: "uint192" }, + ], + name: "nonceSequenceNumber", + outputs: [{ internalType: "uint256", name: "", type: "uint256" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "senderCreator", + outputs: [ + { internalType: "contract ISenderCreator", name: "", type: "address" }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [{ internalType: "bytes4", name: "interfaceId", type: "bytes4" }], + name: "supportsInterface", + outputs: [{ internalType: "bool", name: "", type: "bool" }], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "unlockStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address", + }, + ], + name: "withdrawStake", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address payable", + name: "withdrawAddress", + type: "address", + }, + { internalType: "uint256", name: "withdrawAmount", type: "uint256" }, + ], + name: "withdrawTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { stateMutability: "payable", type: "receive" }, +] as const; +``` + +With that our contracts are all set to go! + +## Client and Server + +In our smart account app we want to have the following flow: +- User clicks on UI button to create an account +- User is prompted to create a passkey +- Passkey is used to create and fund smart account on on our server by our server wallet interacting with the account factory +- Client prepares operation to mint an NFT from the NFT contract, then prompts the user to sign the transaction with their passkey +- Signature and operation info is sent to the server to be processed through the Entrypoint contract by our server wallet +- Server sends a response back to the client with the transaction info + +In a real world application you might use a bundler instead of a server like we are to process the transactions, but it's helpful to see how it all works end-to-end. With that said we need to setup the client and server repos inside our main project directory. + +### Setup Client + +Make sure you are in the root directory and run the following command + +``` +pnpm create vite@latest client +``` + +Go ahead and select the `React` and `Typescript` options, and the defaults that follow. Then move into that client directory and install our other dependencies. + +```bash title="Shell" +cd client +pnpm install viem ox +``` + +While we are here go ahead and create a new file called `utils.ts` inside the `src` directory and put in the following code: + +```typescript title="utils.ts" +export function serializeBigInts(obj: any): any { + if (typeof obj === "bigint") { + return obj.toString(); + } + if (Array.isArray(obj)) { + return obj.map(serializeBigInts); + } + if (obj !== null && typeof obj === "object") { + return Object.fromEntries( + Object.entries(obj).map(([key, value]) => [key, serializeBigInts(value)]), + ); + } + return obj; +} +``` + +This will be a helper function to help process `BigInt` types that can't be serialized by JSON when we send data to our server. + +### Setup Server + +Move back out into the main root directory of the tutorial and then run the following command to create a [Hono]() app: + +``` +pnpm create hono@latest server +``` + +Select the `cloudflare-worker` option from the templates, then move into the project and install the other dependencies. + +```bash title="Shell" +pnpm install viem +pnpm install -D @types/node +``` + +This server is going to use our same foundry wallet from before to handle transactions that need to be processed on the backend. Let's make another `.env` file with the following contents: + +```bash +export CLOUDFLARE_INCLUDE_PROCESS_ENV=true +export RPC_URL=https://sepolia.drpc.org +export PRIVATE_KEY=$(cast wallet private-key --account sepolia) +``` + + + It is highly recommended to use an RPC URL that will be performant and not rate limited. Make a free one at DRPC.org or Alchemy! + + +One last thing we need to do is edit the `server/wrangler.jsonc` file by uncommenting the ` "compatibility_flags"` field like so: + +```jsonc +{ + "$schema": "node_modules/wrangler/config-schema.json", + "name": "server", + "main": "src/index.ts", + "compatibility_date": "2025-10-10", + "compatibility_flags": ["nodejs_compat"] +} +``` + +### Client & Server Code + +Now it's time to start putting code into our client app and server to start the flow we want to achieve. Go ahead and open the `client/src/App.tsx` file and put in the following code: + +```tsx title="App.tsx" +import { useState } from "react"; +import "./App.css"; +import { WebAuthnP256 } from "ox"; +import { + encodeAbiParameters, + createPublicClient, + http, + encodeFunctionData, + encodePacked, + type Hex, +} from "viem"; +import { sepolia } from "viem/chains"; +import { + ENTRYPOINT_ADDRESS, + NFT_ADDRESS, + entryPointAbi, + myNftAbi, + accountWebAuthnAbi, +} from "../../shared"; +import type { PackedUserOperation } from "viem/account-abstraction"; +import { serializeBigInts } from "./utils"; + +const SERVER_URL = "http://localhost:8787"; + +const publicClient = createPublicClient({ + transport: http(), + chain: sepolia, +}); + +function App() { + const [isLoading, setIsLoading] = useState(false); + const [statusMessage, setStatusMessage] = useState(""); + const [accountAddress, setAccountAddress] = useState(null); + const [mintTxHash, setMintTxHash] = useState(null); + + async function createAccount() { + try { + setIsLoading(true); + setStatusMessage("Creating WebAuthn credential..."); + + // Create WebAuthn credential + const credential = await WebAuthnP256.createCredential({ + name: "wallet-user", + }); + + // Convert BigInt values to hex strings for serialization (with proper padding) + const publicKey = { + prefix: credential.publicKey.prefix, + x: `0x${credential.publicKey.x.toString(16).padStart(64, "0")}`, + y: `0x${credential.publicKey.y.toString(16).padStart(64, "0")}`, + }; + + setStatusMessage("Deploying WebAuthn account..."); + + // Send credential to server for account deployment + const response = await fetch(`${SERVER_URL}/account/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + credentialId: credential.id, + publicKey, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Failed to create account"); + } + + const result = await response.json(); + + const deployedAddress = result.accountAddress; + setAccountAddress(deployedAddress); + + setStatusMessage("Account deployed! Preparing NFT mint transaction..."); + + const nonce = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getNonce", + args: [deployedAddress, 0n], + }); + + const incrementCallData = encodeFunctionData({ + abi: myNftAbi, + functionName: "safeMint", + args: [deployedAddress], + }); + + const mode = encodePacked( + ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"], + [ + "0x01", + "0x00", + "0x00000000", + "0x00000000", + "0x00000000000000000000000000000000000000000000", + ], + ); + + // Encode execution data as array of (address, uint256, bytes)[] + const executionData = encodeAbiParameters( + [ + { + type: "tuple[]", + components: [ + { type: "address" }, + { type: "uint256" }, + { type: "bytes" }, + ], + }, + ], + [[[NFT_ADDRESS, 0n, incrementCallData]]], + ); + + // Encode the execute call on the account using ERC7821 format + const callData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "execute", + args: [mode, executionData], + }); + + const feeData = await publicClient.estimateFeesPerGas(); + + const userOp: PackedUserOperation = { + sender: deployedAddress, + nonce, // Already a BigInt from readContract + initCode: "0x", + callData, + accountGasLimits: encodePacked( + ["uint128", "uint128"], + [ + 1_000_000n, // verificationGasLimit (high for P256 verification) + 300_000n, // callGasLimit + ], + ), + preVerificationGas: 100_000n, + gasFees: encodePacked( + ["uint128", "uint128"], + [ + feeData.maxPriorityFeePerGas, // maxPriorityFeePerGas (1 gwei) + feeData.maxFeePerGas, // maxFeePerGas (2 gwei) + ], + ), + paymasterAndData: "0x", + signature: "0x" as Hex, // Placeholder, will be replaced + }; + + const userOpHash = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getUserOpHash", + args: [userOp], + }); + + setStatusMessage("Signing transaction with WebAuthn..."); + + const { signature, metadata } = await WebAuthnP256.sign({ + challenge: userOpHash, + credentialId: credential.id, + }); + + // Encode the signature in the format expected by OpenZeppelin SignerWebAuthn + // The contract expects an ABI-encoded WebAuthnAuth struct: + // struct WebAuthnAuth { + // bytes32 r; + // bytes32 s; + // uint256 challengeIndex; + // uint256 typeIndex; + // bytes authenticatorData; + // string clientDataJSON; + // } + + // Prepare signature components + const rHex = `0x${signature.r.toString(16).padStart(64, "0")}` as Hex; + const sHex = `0x${signature.s.toString(16).padStart(64, "0")}` as Hex; + + setStatusMessage("Submitting UserOperation to mint NFT..."); + + const mintRequest = await fetch(`${SERVER_URL}/account/mint`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + rHex, + sHex, + metadata, + userOp: serializeBigInts(userOp), + nonce: nonce.toString(), + }), + }); + + const mintResponse = await mintRequest.json(); + console.log(mintResponse); + + if (mintResponse.hash) { + setMintTxHash(mintResponse.hash); + } + + setStatusMessage("Success! NFT minted to your account."); + setIsLoading(false); + } catch (err) { + console.error("Error creating account:", err); + setStatusMessage( + `Error: ${err instanceof Error ? err.message : "Unknown error occurred"}`, + ); + setIsLoading(false); + } + } + + return ( + <> +

WebAuthn Account Abstraction

+
+ + {statusMessage && ( +
+ {isLoading &&
} +

{statusMessage}

+
+ )} + {accountAddress && ( +
+

Account Details

+
+ Address: + {accountAddress} +
+ {mintTxHash && ( +
+ NFT Mint Transaction: + + View on Etherscan ↗ + +
+ )} +
+ )} +
+ + ); +} +export default App; +``` + +Now inside `server/src/index.ts` paste in the code below: + +```ts title="index.ts" +import { Hono } from "hono"; +import { cors } from "hono/cors"; +import { logger } from "hono/logger"; +import { + type Hex, + encodeFunctionData, + createPublicClient, + createWalletClient, + http, + encodeAbiParameters, +} from "viem"; +import { + accountWebAuthnAbi, + FACTORY_ADDRESS, + accountFactoryAbi, + ENTRYPOINT_ADDRESS, + entryPointAbi, +} from "../../shared"; +import { privateKeyToAccount } from "viem/accounts"; +import { sepolia } from "viem/chains"; + +type Bindings = { + RPC_URL: string; + PRIVATE_KEY: string; +}; + +const app = new Hono<{ Bindings: Bindings }>(); + +app.use(cors()); +app.use(logger()); + +app.get("/", (c) => { + return c.text("Hello Hono!"); +}); + +app.post("/account/create", async (c) => { + try { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + const { + credentialId, // Can be used to store accounts for future logins + publicKey, + } = await c.req.json(); + + // Extract qx and qy from the public key (ensure proper padding) + const qx = publicKey.x as Hex; + const qy = publicKey.y as Hex; + + // Encode the initialization call + const initCallData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "initializeWebAuthn", + args: [qx, qy], + }); + + // Predict account address + const [predictedAddress] = await publicClient.readContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "predictAddress", + args: [initCallData], + }); + + // Deploy the account + const hash = await walletClient.writeContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "cloneAndInitialize", + args: [initCallData], + }); + + // Wait for transaction + await publicClient.waitForTransactionReceipt({ hash }); + + // Fund the account with 0.005 ETH from deployer wallet + const fundHash = await walletClient.sendTransaction({ + to: predictedAddress, + value: 5000000000000000n, // 0.005 ETH in wei + }); + + // Wait for funding transaction + await publicClient.waitForTransactionReceipt({ hash: fundHash }); + + return c.json({ + success: true, + accountAddress: predictedAddress, + transactionHash: hash, + fundingTransactionHash: fundHash, + publicKey: { qx, qy }, + }); + } catch (error) { + return c.json({ error: ` Failed to create account: ${error} ` }, 500); + } +}); + +app.post("/account/mint", async (c) => { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + try { + const { + metadata, + rHex, + sHex, + userOp, + nonce: serializedNonce, + } = await c.req.json(); + + const challengeIndex = BigInt(metadata.challengeIndex); + const typeIndex = BigInt(metadata.typeIndex); + const authenticatorDataHex = metadata.authenticatorData; + const clientDataJSON = metadata.clientDataJSON; + const nonce = BigInt(serializedNonce); + + const encodedSignature = encodeAbiParameters( + [ + { name: "r", type: "bytes32" }, + { name: "s", type: "bytes32" }, + { name: "challengeIndex", type: "uint256" }, + { name: "typeIndex", type: "uint256" }, + { name: "authenticatorData", type: "bytes" }, + { name: "clientDataJSON", type: "string" }, + ], + [ + rHex, + sHex, + challengeIndex, + typeIndex, + authenticatorDataHex, + clientDataJSON, + ], + ); + + const fullUserOp = { + ...userOp, + nonce, + preVerificationGas: BigInt(userOp.preVerificationGas), + signature: encodedSignature, + }; + + const { request } = await publicClient.simulateContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "handleOps", + args: [[fullUserOp], walletClient.account.address], + account: walletClient.account, + }); + + const hash = await walletClient.writeContract(request); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: hash, + }); + + if (receipt.status === "reverted") { + return c.json({ error: ` Failed to Mint: Reverted ` }, 500); + } + + return c.json({ + status: "success", + hash, + }); + } catch (error) { + return c.json({ error: ` Failed to Mint: ${error} ` }, 500); + } +}); + +export default app; +``` + +Now that's a fair bit of code, so let's do a breakdown of each step and what's happening. + +### Breakdown + + + + +#### Create and Fund Account + +The flow starts in the client code where the user clicks on the button and it kicks off `createAccount()`. + +```tsx +async function createAccount() { + try { + setIsLoading(true); + setStatusMessage("Creating WebAuthn credential..."); + + // Create WebAuthn credential + const credential = await WebAuthnP256.createCredential({ + name: "wallet-user", + }); + + // Convert BigInt values to hex strings for serialization (with proper padding) + const publicKey = { + prefix: credential.publicKey.prefix, + x: `0x${credential.publicKey.x.toString(16).padStart(64, "0")}`, + y: `0x${credential.publicKey.y.toString(16).padStart(64, "0")}`, + }; + + setStatusMessage("Deploying WebAuthn account..."); + + // Send credential to server for account deployment + const response = await fetch(`${SERVER_URL}/account/create`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + credentialId: credential.id, + publicKey, + }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.error || "Failed to create account"); + } + + const result = await response.json(); + + // Rest of function +``` + +The first thing we need is a WebAuthn credential, and thankfully `ox` makes this really easy to do with `WebAuthnP256.createCredential()`. Once we have the credential we need to take the public key coordinates and serlialize the BigInt values into hex strings. Then we send a request to our server to `/account/create` with a JSON body of that `publicKey` as well as a `credentialId`. + +On the server the incoming requst is handled with this endpoint: + +```ts +app.post("/account/create", async (c) => { + try { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + const { + credentialId, // Can be used to store accounts for future logins + publicKey, + } = await c.req.json(); + + // Extract qx and qy from the public key (ensure proper padding) + const qx = publicKey.x as Hex; + const qy = publicKey.y as Hex; + + // Encode the initialization call + const initCallData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "initializeWebAuthn", + args: [qx, qy], + }); + + // Predict account address + const [predictedAddress] = await publicClient.readContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "predictAddress", + args: [initCallData], + }); + + // Deploy the account + const hash = await walletClient.writeContract({ + address: FACTORY_ADDRESS, + abi: accountFactoryAbi, + functionName: "cloneAndInitialize", + args: [initCallData], + }); + + // Wait for transaction + await publicClient.waitForTransactionReceipt({ hash }); + + // Fund the account with 0.005 ETH from deployer wallet + const fundHash = await walletClient.sendTransaction({ + to: predictedAddress, + value: 5000000000000000n, // 0.005 ETH in wei + }); + + // Wait for funding transaction + await publicClient.waitForTransactionReceipt({ hash: fundHash }); + + return c.json({ + success: true, + accountAddress: predictedAddress, + transactionHash: hash, + fundingTransactionHash: fundHash, + publicKey: { qx, qy }, + }); + } catch (error) { + return c.json({ error: ` Failed to create account: ${error} ` }, 500); + } +}); +``` + +In this endpoint the server parses the JSON body to grab the serialized coordinates of the public key, then we put together what we need to do in order to predict our address. We don't necessarily need this in our flow, but it's good to know how it works. The predicted address is calculated by creating `initCallData` from `initializeWebAuthn` function and the coordiantes we got from the client. + +To actually deploy our smart account we'll take the same `initCallData` as the argument, then call `cloneAndInitialize` from the factory. This will return the smart account address, which we can then fund with our server wallet. Finally we can return the result of our process to the client. + + + + +#### Prepare and Sign User Operation + +Back in our client we now can start prepping a User Operation. + +```tsx + // createAccount() continuted + + const nonce = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getNonce", + args: [deployedAddress, 0n], + }); + + const mintCallData = encodeFunctionData({ + abi: myNftAbi, + functionName: "safeMint", + args: [deployedAddress], + }); + + const mode = encodePacked( + ["bytes1", "bytes1", "bytes4", "bytes4", "bytes22"], + [ + "0x01", + "0x00", + "0x00000000", + "0x00000000", + "0x00000000000000000000000000000000000000000000", + ], + ); + + // Encode execution data as array of (address, uint256, bytes)[] + const executionData = encodeAbiParameters( + [ + { + type: "tuple[]", + components: [ + { type: "address" }, + { type: "uint256" }, + { type: "bytes" }, + ], + }, + ], + [[[NFT_ADDRESS, 0n, mintCallData]]], + ); + + // Encode the execute call on the account using ERC7821 format + const callData = encodeFunctionData({ + abi: accountWebAuthnAbi, + functionName: "execute", + args: [mode, executionData], + }); + + const feeData = await publicClient.estimateFeesPerGas(); + + const userOp: PackedUserOperation = { + sender: deployedAddress, + nonce, // Already a BigInt from readContract + initCode: "0x", + callData, + accountGasLimits: encodePacked( + ["uint128", "uint128"], + [ + 1_000_000n, // verificationGasLimit (high for P256 verification) + 300_000n, // callGasLimit + ], + ), + preVerificationGas: 100_000n, + gasFees: encodePacked( + ["uint128", "uint128"], + [ + feeData.maxPriorityFeePerGas, // maxPriorityFeePerGas (1 gwei) + feeData.maxFeePerGas, // maxFeePerGas (2 gwei) + ], + ), + paymasterAndData: "0x", + signature: "0x" as Hex, // Placeholder, will be replaced + }; + + const userOpHash = await publicClient.readContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "getUserOpHash", + args: [userOp], + }); + + setStatusMessage("Signing transaction with WebAuthn..."); + + const { signature, metadata } = await WebAuthnP256.sign({ + challenge: userOpHash, + credentialId: credential.id, + }); + + // Prepare signature components + const rHex = `0x${signature.r.toString(16).padStart(64, "0")}` as Hex; + const sHex = `0x${signature.s.toString(16).padStart(64, "0")}` as Hex; + + setStatusMessage("Submitting UserOperation to mint NFT..."); + + const mintRequest = await fetch(`${SERVER_URL}/account/mint`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + rHex, + sHex, + metadata, + userOp: serializeBigInts(userOp), + nonce: nonce.toString(), + }), + }); +``` + +Now this starts to get a little confusing as we have a lot of encoding and nesting happening, so let's break it down piece by piece. First we have the `nonce` which we fetch from the Entrypoint contract. Next we need to encode the function data that our smart account wants to perform, which in our case is minting the NFT. + +Then we have the `mode` and `executionData` which includes the address of our NFT contract and the `mintCallData` we just made. With that `executionData` we can do one final encoding of the `callData` that will be passed to the `execute` function of our smart account. + +In order to actually run this transaction through `execute` we need to create what's called an User Operation that has all of the details of how it should be performed. We build the `userOp` object to gather the data that will be passed, and then we send that object to the entrypoint contract to get a `userOpHash`. This is what the user's passkey will sign; it's the approval of everything that's been encoded earlier. + +Once it's signed by the passkey through `WebAuthnP256.sign()` we get the `r` and `s` values from the signature and serialize them as hex strings, and we finally send all of that data to our server to execute the transaction. It's at this point if you wanted to you could send it to a bundler instead which might include a paymaster to handle gas fees. In our case the server wallet will pay gas initially, but then it will be refunded by the entrypoint once the smart account pays the gas fees for the transaciton. + + + + +#### Process User Operation on Server + +To handle processing the user operation we have an endpoint on the server called `/account/mint`: + +```ts +app.post("/account/mint", async (c) => { + const publicClient = createPublicClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + }); + + const account = privateKeyToAccount(c.env.PRIVATE_KEY as `0x${string}`); + + const walletClient = createWalletClient({ + chain: sepolia, + transport: http(c.env.RPC_URL), + account, + }); + + try { + const { + metadata, + rHex, + sHex, + userOp, + nonce: serializedNonce, + } = await c.req.json(); + + const challengeIndex = BigInt(metadata.challengeIndex); + const typeIndex = BigInt(metadata.typeIndex); + const authenticatorDataHex = metadata.authenticatorData; + const clientDataJSON = metadata.clientDataJSON; + const nonce = BigInt(serializedNonce); + + const encodedSignature = encodeAbiParameters( + [ + { name: "r", type: "bytes32" }, + { name: "s", type: "bytes32" }, + { name: "challengeIndex", type: "uint256" }, + { name: "typeIndex", type: "uint256" }, + { name: "authenticatorData", type: "bytes" }, + { name: "clientDataJSON", type: "string" }, + ], + [ + rHex, + sHex, + challengeIndex, + typeIndex, + authenticatorDataHex, + clientDataJSON, + ], + ); + + const fullUserOp = { + ...userOp, + nonce, + preVerificationGas: BigInt(userOp.preVerificationGas), + signature: encodedSignature, + }; + + const { request } = await publicClient.simulateContract({ + address: ENTRYPOINT_ADDRESS, + abi: entryPointAbi, + functionName: "handleOps", + args: [[fullUserOp], walletClient.account.address], + account: walletClient.account, + }); + + const hash = await walletClient.writeContract(request); + + const receipt = await publicClient.waitForTransactionReceipt({ + hash: hash, + }); + + if (receipt.status === "reverted") { + return c.json({ error: ` Failed to Mint: Reverted ` }, 500); + } + + return c.json({ + status: "success", + hash, + }); + } catch (error) { + return c.json({ error: ` Failed to Mint: ${error} ` }, 500); + } +}); +``` + +In order for the OpenZeppelin `WebAuthn.sol` to verify our signature and prove that the user authorized the operation, we have to prepare and encode the signature data correctly. Since we had to serialize some of the BigInt values on the client we need to reinstate them. Then we can put together the `encodedSignature` with all our values, many of them being the `metadata` that was produced in the signing process by `WebAuthnP256` from `ox`. + +Then we need to construct the `fullUserOp` which includes our updated `encodedSignature`, and we can finally submit it to the Entrypoint Contract. Once successful we can return the transaction hash to the user. + + + + + +## Try it! + +With an overview of how it all works you can test it yourself! In a terminal window run the client dev server: + +```bash title="Shell" +cd client +pnpm dev +``` + +Then in a separate window run the server: + +```bash title="Shell" +cd server +source .env +pnpm dev +``` + +You should be able to visit `http://localhost:5173` and click on the `Create Account` button to experience the full flow! + + + Make sure you are on a browser and device that supports passkeys + + +![image of demo](/webauthn-demo.png) + +## Next Steps + +This tutorial is just scratching the surface of what is possible with OpenZeppelin account abstraction. With `AccountWebAuthn.sol` we could customize the logic and build custom use cases such as multifactor authentication, social recovery, time based controls, and more! We would highly encourage you to check out what other pieces you can add into Accounts with the [Wizard](https://wizard.openzeppelin.com) under the `Accounts` tab. diff --git a/docs/content/contracts/5.x/learn/writing-automated-tests.mdx b/docs/content/contracts/5.x/learn/writing-automated-tests.mdx new file mode 100644 index 00000000..6ac7a369 --- /dev/null +++ b/docs/content/contracts/5.x/learn/writing-automated-tests.mdx @@ -0,0 +1,101 @@ +--- +title: Writing automated smart contract tests +--- + +In a blockchain environment, a single mistake could cost you all of your funds - or even worse, your users' funds! This guide will help you develop robust applications by writing automated tests that verify your application behaves exactly as you intended. + +We’ll cover the following topics: + +* [Setting up a Testing Environment](#setting-up-a-testing-environment) +* [Writing Unit Tests](#writing-unit-tests) +* [Performing Complex Assertions](#performing-complex-assertions) + +## About testing + +There is a wide range of testing techniques, from [simple manual verifications](./deploying-and-interacting#interacting-from-the-console) to complex end-to-end setups, all of them useful in their own way. + +When it comes to smart contract development though, practice has shown that contract [_unit testing_](https://en.wikipedia.org/wiki/Unit_testing) is exceptionally worthwhile. These tests are simple to write and quick to run, and let you add features and fix bugs in your code with confidence. + +Smart contract unit testing consists of multiple small, focused tests, which each check a small part of your contract for correctness. They can often be expressed in single sentences that make up a specification, such as 'the admin is able to pause the contract', 'transferring tokens emits an event' or 'non-admins cannot mint new tokens'. + +## Setting up a testing environment + +You may be wondering _how_ we’re going to run these tests, since smart contracts are executed inside a blockchain. Using the actual Ethereum network would be very expensive, and while testnets are free, they are also slow (with blocktimes of 12 seconds or more). If we intend to run hundreds of tests whenever we make a change to our code, we need something better. + +What we will use is called a _local blockchain_: a slimmed down version of the real thing, disconnected from the Internet, running on your machine. This will simplify things quite a bit: you won’t need to get Ether, and new blocks will be mined instantly. + +## Writing unit tests + +We’ll use [Chai](https://www.chaijs.com/) assertions for our unit tests, which are available by installing the Hardhat Toolbox. + +```console +$ npm install --save-dev @nomicfoundation/hardhat-toolbox +``` + +We will keep our test files in a `test` directory. Tests are best structured by mirroring the [`contracts` directory](./developing-smart-contracts#first-contract): for each `.sol` file there, create a corresponding test file. + +Time to write our first tests! These will test properties of the `Box` contract [from previous guides](./developing-smart-contracts#first-contract): a simple contract that lets you `retrieve` a value the owner previously `store` d. + +Create a `test` directory in your project root. We will save the test as `test/Box.test.js`. Each test `.js` file commonly has the tests for a single contract, and is named after it. + +```js +const { expect } = require('chai'); + +describe('Box', function () { + before(async function () { + this.Box = await ethers.getContractFactory('Box'); + }); + + beforeEach(async function () { + this.box = await this.Box.deploy(); + await this.box.waitForDeployment(); + }); + + // Test case + it('retrieve returns a value previously stored', async function () { + // Store a value + await this.box.store(42); + + // Test if the returned value is the same one + // Note that we need to use strings to compare the 256 bit integers + expect((await this.box.retrieve()).toString()).to.equal('42'); + }); +}); +``` + + +Many books have been written about how to structure unit tests. Check out the [Moloch Testing Guide](https://github.com/MolochVentures/moloch/tree/4e786db8a4aa3158287e0935dcbc7b1e43416e38/test#moloch-testing-guide) for a set of principles designed for testing Solidity smart contracts. + + +We are now ready to run our tests! + +Running `npx hardhat test` will execute all tests in the `test` directory, checking that your contracts work the way you meant them to: + +```console +$ npx hardhat test + + Box + ✓ retrieve returns a value previously stored + + 1 passing (578ms) +``` + +It’s also a very good idea at this point to set up a Continuous Integration service such as [CircleCI](https://circleci.com/) to make your tests run automatically every time you commit your code to GitHub. + +## Performing complex assertions + +Many interesting properties of your contracts may be hard to capture, such as: + +* verifying that the contract reverts on errors +* measuring by how much an account’s Ether balance changed +* checking that the proper events are emitted + +We recommend using [Hardhat Chai Matchers](https://hardhat.org/hardhat-chai-matchers/docs/overview) to help you test all of these properties, and [Hardhat Network Helpers](https://hardhat.org/hardhat-network-helpers/docs/overview) for simulating time passing on the blockchain. These tools will let you write powerful assertions without having to worry about the low-level details of the underlying Ethereum libraries. + +## Next steps + +Once you have thoroughly tested your contracts and are reasonably sure of their correctness, you’ll want to deploy them to a real network and start interacting with them. The following guides will get you up to speed on these topics: + +* [Connecting to Public Test Networks](./connecting-to-public-test-networks) +* [Deploying and Interacting](./deploying-and-interacting) +* [Preparing for Mainnet](./preparing-for-mainnet) diff --git a/docs/content/contracts/5.x/multisig.mdx b/docs/content/contracts/5.x/multisig.mdx new file mode 100644 index 00000000..8526ff10 --- /dev/null +++ b/docs/content/contracts/5.x/multisig.mdx @@ -0,0 +1,311 @@ +--- +title: 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 [Safe](https://safe.global/) (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 [accounts section](./accounts#signature-validation), the standard approach for smart contracts to verify signatures is [ERC-1271](https://eips.ethereum.org/EIPS/eip-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 [SignatureChecker](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/SignatureChecker.sol) 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 + +[ERC-7913](https://eips.ethereum.org/EIPS/eip-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 [`SignerERC7913`](/contracts/5.x/api/utils#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`. + +```solidity +// contracts/MyAccountERC7913.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {Account} from "@openzeppelin/community-contracts/account/Account.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol"; +import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {SignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/signers/SignerERC7913.sol"; + +contract MyAccountERC7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable { + constructor() EIP712("MyAccount7913", "1") {} + + function initialize(bytes memory signer) public initializer { + _setSigner(signer); + } + + function setSigner(bytes memory signer) public onlyEntryPointOrSelf { + _setSigner(signer); + } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} +``` + + +Leaving an account uninitialized may leave it unusable since no public key was associated with it. + + +### MultiSignerERC7913 + +The [`MultiSignerERC7913`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913) contract extends this concept to support multiple signers with a threshold-based signature verification system. + +```solidity +// contracts/MyAccountMultiSigner.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Account} from "@openzeppelin/community-contracts/account/Account.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol"; +import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {MultiSignerERC7913} from "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913.sol"; + +contract MyAccountMultiSigner is + Account, + MultiSignerERC7913, + ERC7739, + ERC7821, + ERC721Holder, + ERC1155Holder, + Initializable +{ + constructor() EIP712("MyAccountMultiSigner", "1") {} + + function initialize(bytes[] memory signers, uint256 threshold) public initializer { + _addSigners(signers); + _setThreshold(threshold); + } + + function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf { + _addSigners(signers); + } + + function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf { + _removeSigners(signers); + } + + function setThreshold(uint256 threshold) public onlyEntryPointOrSelf { + _setThreshold(threshold); + } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} +``` + +This implementation is ideal for standard multisig setups where each signer has equal authority, and a fixed number of approvals is required. + +The `MultiSignerERC7913` contract provides several key features for managing multi-signature accounts. It maintains a set of authorized signers and implements a threshold-based system that requires a minimum number of signatures to approve operations. The contract includes an internal interface for managing signers, allowing for the addition and removal of authorized parties. + + +`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: [`isSigner(bytes memory signer)`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913-isSigner-bytes-) to check if a given signer is authorized, [`getSigners(uint64 start, uint64 end)`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913-getSigners-uint64-uint64-) to retrieve a paginated list of authorized signers, and [`getSignerCount()`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913-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 [`MultiSignerERC7913Weighted`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913Weighted) contract extends `MultiSignerERC7913` by assigning different weights to each signer. + +```solidity +// contracts/MyAccountMultiSignerWeighted.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Account} from "@openzeppelin/community-contracts/account/Account.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC7739} from "@openzeppelin/community-contracts/utils/cryptography/signers/ERC7739.sol"; +import {ERC7821} from "@openzeppelin/community-contracts/account/extensions/ERC7821.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {MultiSignerERC7913Weighted} from "@openzeppelin/community-contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.sol"; + +contract MyAccountMultiSignerWeighted is + Account, + MultiSignerERC7913Weighted, + ERC7739, + ERC7821, + ERC721Holder, + ERC1155Holder, + Initializable +{ + constructor() EIP712("MyAccountMultiSignerWeighted", "1") {} + + function initialize(bytes[] memory signers, uint256[] memory weights, uint256 threshold) public initializer { + _addSigners(signers); + _setSignerWeights(signers, weights); + _setThreshold(threshold); + } + + function addSigners(bytes[] memory signers) public onlyEntryPointOrSelf { + _addSigners(signers); + } + + function removeSigners(bytes[] memory signers) public onlyEntryPointOrSelf { + _removeSigners(signers); + } + + function setThreshold(uint256 threshold) public onlyEntryPointOrSelf { + _setThreshold(threshold); + } + + function setSignerWeights(bytes[] memory signers, uint256[] memory weights) public onlyEntryPointOrSelf { + _setSignerWeights(signers, weights); + } + + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} +``` + +This implementation is perfect for scenarios where different signers should have varying levels of authority, such as: + +* Board members with different voting powers +* Organizational structures with hierarchical decision-making +* Hybrid governance systems combining core team and community members +* Execution setups like "social recovery" where you trust particular guardians more than others + +The `MultiSignerERC7913Weighted` contract extends `MultiSignerERC7913` with a weighting system. Each signer can have a custom weight, and operations require the total weight of signing participants to meet or exceed the threshold. Signers without explicit weights default to a weight of 1. + + +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: + +```solidity +// Example setup code +function setupMultisigAccount() external { + // Create signers using different types of keys + bytes memory ecdsaSigner = alice; // EOA address (20 bytes) + + // P256 signer with format: verifier || pubKey + bytes memory p256Signer = abi.encodePacked( + p256Verifier, + bobP256PublicKeyX, + bobP256PublicKeyY + ); + + // RSA signer with format: verifier || pubKey + bytes memory rsaSigner = abi.encodePacked( + rsaVerifier, + abi.encode(charlieRSAPublicKeyE, charlieRSAPublicKeyN) + ); + + // Create array of signers + bytes[] memory signers = new bytes[](3); + signers[0] = ecdsaSigner; + signers[1] = p256Signer; + signers[2] = rsaSigner; + + // Set threshold to 2 (2-of-3 multisig) + uint256 threshold = 2; + + // Initialize the account + myMultisigAccount.initialize(signers, threshold); +} +``` + +For a weighted multisig, you would also specify weights: + +```solidity +// Example setup for weighted multisig +function setupWeightedMultisigAccount() external { + // Create array of signers (same as above) + bytes[] memory signers = new bytes[](3); + signers[0] = ecdsaSigner; + signers[1] = p256Signer; + signers[2] = rsaSigner; + + // Assign weights to signers (Alice:1, Bob:2, Charlie:3) + uint256[] memory weights = new uint256[](3); + weights[0] = 1; + weights[1] = 2; + weights[2] = 3; + + // Set threshold to 4 (requires at least Bob+Charlie or all three) + uint256 threshold = 4; + + // Initialize the weighted account + myWeightedMultisigAccount.initialize(signers, weights, threshold); +} +``` + + +The [`_validateReachableThreshold`](/contracts/5.x/api/utils/cryptography#MultiSignerERC7913-_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: + +```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 + + + +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. + + diff --git a/docs/content/contracts/5.x/subgraphs/examples/query.mdx b/docs/content/contracts/5.x/subgraphs/examples/query.mdx new file mode 100644 index 00000000..01541586 --- /dev/null +++ b/docs/content/contracts/5.x/subgraphs/examples/query.mdx @@ -0,0 +1,214 @@ +--- +title: Query examples +--- + +## ERC20 + +### Total supply and biggest token holders + +```graphql +{ + erc20Contract(id: "") { + totalSupply { + value + } + balances(orderBy: valueExact, orderDirection: desc, where: { account_not: null }) { + account { + id + } + value + } + } +} +``` + +### Most recent transfers with transaction id + +```graphql +{ + erc20Contract(id: "") { + transfers(orderBy: timestamp, orderDirection: desc) { + from { id } + to { id } + value + transaction { id } + } + } +} +``` + +### All indexed ERC20 balances for a user + +```graphql +{ + account(id: "") { + ERC20balances { + contract{ name, symbol, decimals } + value + } + } +} +``` + +## ERC721 + +### All ERC721 tokens for a user, with metadata + +```graphql +{ + account(id: "") { + ERC721tokens { + contract { id } + identifier + uri + } + } +} +``` + +### All ERC721 tokens on a contract, with current owner + +```graphql +{ + erc721Contract(id: "") { + tokens { + identifier + owner { id } + } + } +} +``` + +### History of all transfers for a given token + +```graphql +{ + erc721Tokens(where: { + contract: "", + identifier: "", + }) { + transfers { + timestamp + from { id } + to { id } + } + } +} +``` + +## ERC1155 + +### All tokens for a registry with corresponding totalSupply and balances + +```graphql +{ + erc1155Contract(id: "") { + tokens { + identifier + totalSupply { value } + balances(where: { account_not: null }) { + account { id } + value + } + } + } +} +``` + +## Ownable + +### History of transfers for an ownable contract + +```graphql +{ + ownable(id : "") { + ownershipTransferred(orderBy: timestamp, orderDirection: asc) { + timestamp + owner { id } + } + } +} +``` + +### All ownable contract help by an account + +```graphql +{ + account(id: "") { + ownerOf { + id + } + } +} +``` + +## AccessControl + +### All roles, with details and current members for an AccessControl powered contract + +```graphql +{ + accessControl(id: "") { + roles { + role { id } + admin { role { id } } + adminOf { role { id } } + members { account { id } } + } + } +} + +``` + +### All AccessControl roles held by an account + +```graphql +{ + account(id: "") { + membership { + accesscontrolrole { + contract { id } + role { id } + } + } + } +} +``` + +## Timelock + +### Pending operations on a timelock, with details of subcalls and deadline + +```graphql +{ + timelock(id: "") { + id + operations(where: { status: "SCHEDULED"}) { + calls { + target { id } + value + data + } + timestamp + } + } +} +``` + +### All timelocked operations to an address, with status and details of the call + +```graphql +{ + account(id: "") { + timelockedCalls { + operation { + contract { id } + timestamp + status + } + value + data + } + } +} +``` diff --git a/docs/content/contracts/5.x/subgraphs/examples/subgraph.mdx b/docs/content/contracts/5.x/subgraphs/examples/subgraph.mdx new file mode 100644 index 00000000..75d34c20 --- /dev/null +++ b/docs/content/contracts/5.x/subgraphs/examples/subgraph.mdx @@ -0,0 +1,178 @@ +--- +title: Subgraph examples +--- + +## Basic application + +### App description + +We will consider an hypothetical dapp, on the Goerli network, with that consists of: + +* An `ERC20` token at address `0x163AE1e077232D6C34E1BF14fA58aA74518886Cc` and deployed at block 5059780. + +### Manifest + +The manifest for that subgraph would look someting like the following. Note that depending on you folder layout, relative paths might change: + +```yaml +specVersion: 0.0.2 +schema: + file: ../node_modules/@openzeppelin/subgraphs/generated/erc20.schema.graphql +dataSources: + - kind: ethereum/contract + name: erc20 + network: goerli + source: + address: "0x163AE1e077232D6C34E1BF14fA58aA74518886Cc" + abi: IERC20 + startBlock: 5059780 + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - ERC20Contract + abis: + - name: IERC20 + file: ../node_modules/@openzeppelin/contracts/build/contracts/IERC20Metadata.json + eventHandlers: + - event: Approval(indexed address,indexed address,uint256) + handler: handleApproval + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTransfer + file: ../node_modules/@openzeppelin/subgraphs/src/datasources/erc20.ts +``` + +## Application with 2 datasources + +### App description + +For this second example, we will extand our example to include more features: + +* An `ERC20` token, that has is `Ownable` and `Pausable` at address `0x163AE1e077232D6C34E1BF14fA58aA74518886Cc` and deployed at block 5059780. +* An OpenZeppelin `TimelockController`, which internally uses the `AccessControl` pattern at address `0x9949d9507A26b9639B882E15A897A0A79DDf2c94` and deployed at block 5059781. + +### Manifest + +For this second example, we have to support multiple modules, so for simplicity sake, we use the `all.schema.graphql`. + +```yaml +specVersion: 0.0.2 +schema: + file: ../node_modules/@openzeppelin/subgraphs/generated/all.schema.graphql +dataSources: + - kind: ethereum/contract + name: erc20 + network: goerli + source: + address: "0x163AE1e077232D6C34E1BF14fA58aA74518886Cc" + abi: IERC20 + startBlock: 5059780 + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - ERC20Contract + abis: + - name: IERC20 + file: ../node_modules/@openzeppelin/contracts/build/contracts/IERC20Metadata.json + eventHandlers: + - event: Approval(indexed address,indexed address,uint256) + handler: handleApproval + - event: Transfer(indexed address,indexed address,uint256) + handler: handleTransfer + file: ../node_modules/@openzeppelin/subgraphs/src/datasources/erc20.ts + - kind: ethereum/contract + name: ownable + network: goerli + source: + address: "0x163AE1e077232D6C34E1BF14fA58aA74518886Cc" + abi: Ownable + startBlock: 5059780 + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - Ownable + abis: + - name: Ownable + file: ../node_modules/@openzeppelin/contracts/build/contracts/Ownable.json + eventHandlers: + - event: OwnershipTransferred(indexed address,indexed address) + handler: handleOwnershipTransferred + file: ../node_modules/@openzeppelin/subgraphs/src/datasources/ownable.ts + - kind: ethereum/contract + name: pausable + network: goerli + source: + address: "0x163AE1e077232D6C34E1BF14fA58aA74518886Cc" + abi: Pausable + startBlock: 5059780 + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - Pausable + abis: + - name: Pausable + file: ../node_modules/@openzeppelin/contracts/build/contracts/Pausable.json + eventHandlers: + - event: Paused(address) + handler: handlePaused + - event: Unpaused(address) + handler: handleUnpaused + file: ../node_modules/@openzeppelin/subgraphs/src/datasources/pausable.ts + - kind: ethereum/contract + name: timelock + network: goerli + source: + address: "0x9949d9507A26b9639B882E15A897A0A79DDf2c94" + abi: Timelock + startBlock: 5059781 + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - Timelock + abis: + - name: Timelock + file: ../node_modules/@openzeppelin/contracts/build/contracts/TimelockController.json + eventHandlers: + - event: CallScheduled(indexed bytes32,indexed uint256,address,uint256,bytes,bytes32,uint256) + handler: handleCallScheduled + - event: CallExecuted(indexed bytes32,indexed uint256,address,uint256,bytes) + handler: handleCallExecuted + - event: Cancelled(indexed bytes32) + handler: handleCancelled + - event: MinDelayChange(uint256,uint256) + handler: handleMinDelayChange + file: ../node_modules/@openzeppelin/subgraphs/src/datasources/timelock.ts + - kind: ethereum/contract + name: accesscontrol + network: goerli + source: + address: "0x9949d9507A26b9639B882E15A897A0A79DDf2c94" + abi: AccessControl + startBlock: 5059781 + mapping: + kind: ethereum/events + apiVersion: 0.0.4 + language: wasm/assemblyscript + entities: + - AccessControl + abis: + - name: AccessControl + file: ../node_modules/@openzeppelin/contracts/build/contracts/AccessControl.json + eventHandlers: + - event: RoleAdminChanged(indexed bytes32,indexed bytes32,indexed bytes32) + handler: handleRoleAdminChanged + - event: RoleGranted(indexed bytes32,indexed address,indexed address) + handler: handleRoleGranted + - event: RoleRevoked(indexed bytes32,indexed address,indexed address) + handler: handleRoleRevoked + file: ../node_modules/@openzeppelin/subgraphs/src/datasources/accesscontrol.ts +``` diff --git a/docs/content/contracts/5.x/subgraphs/generate.mdx b/docs/content/contracts/5.x/subgraphs/generate.mdx new file mode 100644 index 00000000..4222fa58 --- /dev/null +++ b/docs/content/contracts/5.x/subgraphs/generate.mdx @@ -0,0 +1,61 @@ +--- +title: Automatic generation +--- + +The `graph-compile` tool provided as part of the `@amxx/graphprotocol-utils` package can be used to automatically generate tailor made schema and manifest for your application. Modules implemented as part of OpenZeppelin Subgraphs are compatible with this. + +## Describe your application + +For schema and manifest to be generated, you will have to provide a description of the contract you wish to index. + +Here is an example of configuration file that indexed 6 contracts on mainnet: + +```json +{ + "output": "generated/sample.", + "chain": "mainnet", + "datasources": [ + { "address": "0xA3B26327482312f70E077aAba62336f7643e41E1", "startBlock": 11633151, "module": [ "erc20", "accesscontrol" ] }, + { "address": "0xB1C52075b276f87b1834919167312221d50c9D16", "startBlock": 9917641, "module": [ "erc721", "ownable" ] }, + { "address": "0x799DAa22654128d0C64d5b79eac9283008158730", "startBlock": 9917642, "module": [ "erc721", "ownable" ] }, + { "address": "0xC76A18c78B7e530A165c5683CB1aB134E21938B4", "startBlock": 9917639, "module": [ "erc721", "ownable" ] }, + { "address": "0x001d1cd0bcf2e9021056c6fe4428ce15d977cfe0", "startBlock": 11127634, "module": [ "erc1155", "ownable" ] }, + { "address": "0x3d85004fa4723de6563909fabbcafee509ee6a52", "startBlock": 12322496, "module": [ "timelock", "accesscontrol" ] } + ] +} +``` + +Each contract is represented as a datasource, with an address, an (optional) start block, and a list of features. + +## Generate schema and manifest + +This configuration file can compiled using the following command + +```bash +npx graph-compiler \ + --config sample.json \ + --include node_modules/@openzeppelin/subgraphs/src/datasources \ + --export-schema \ + --export-subgraph +``` + +Note that in our example, the OpenZeppelin Subgraphs package was installed using NPM, which means the module descriptions are in `@openzeppelin/subgraphs/src/datasources`. This path must be added using the `--include` command so the compiler can find them. + +This will produce two file, following the `output` parameter provided in the configuration file: + +* `generated/sample.schema.graphql` contains a tailor made schema, that contains only the entity requiered by the indexed modules. +* `generated/sample.subgraph.yaml` contains the subgraph manifest. + +## Deploy your subgraph + +In order to deploy your subgraph to the hosted service, or to any other graphnode, you will have to create it first. This operation is documented [here](https://thegraph.com/docs/developer/deploy-subgraph-hosted). Once this is done, you can compile and deploy the produced subgraph using the `graph-cli` tools: + +```bash +npx graph-cli codegen generated/sample.subgraph.yaml +npx graph-cli build generated/sample.subgraph.yaml +npx graph-cli deploy \ + --ipfs https://api.thegraph.com/ipfs/ \ + --node https://api.thegraph.com/deploy/ \ + username/subgraphname \ + generated/sample.subgraph.yaml +``` diff --git a/docs/content/contracts/5.x/subgraphs/index.mdx b/docs/content/contracts/5.x/subgraphs/index.mdx new file mode 100644 index 00000000..a39d6456 --- /dev/null +++ b/docs/content/contracts/5.x/subgraphs/index.mdx @@ -0,0 +1,81 @@ +--- +title: Subgraphs +--- + +Modules for easily indexing OpenZeppelin Contracts activity. + +Install from npm as [`@openzeppelin/subgraphs`](https://www.npmjs.com/package/@openzeppelin/subgraphs). + +Browse on GitHub at [`OpenZeppelin/openzeppelin-subgraphs`](https://github.com/OpenZeppelin/openzeppelin-subgraphs). + +## Usage + +Subgraph are described using three components: + +* **The graphql schema**, usually named `schema.graphql`, which describes the database entities and links. +* **The subgraph manifest**, usually named `subgraph.yaml`, which describes the activity that should be listened to (addresses of contracts, events handlers, function handlers). +* **The indexing logic**, written in assembly script, which will process the blockchain activity and update the database accordingly. + +OpenZeppelin Subgraphs provide schemas description, with the corresponding indexing logic and templates for building your subgraph manifest. + +Similarly to how OpenZeppelin Contracts provide solidity code containing sets of features that one can assemble to ease building an application, OpenZeppelin subgraphs provides modules dedicated to indexing the activity corresponding to these features. These modules can be composed to index complex onchain activity without the need to actually write the indexing logic for most of the features. + +### Building your manifest + +You can build a manifest for your application using the templates provided for each module. These templates are available in `src/datasource/.yaml`. For each datasource, you will have to fill in the name, network, address, and startBlock of your contract. If a contract implements multiple modules, you will want to have multiple datasources listenning to the same address (one per module). + +**Note:** For the indexing logic to work you will have, for each module used, to name one of your datasources with the name of the module. + +The `@amxx/graphprotocol-utils` provides tooling to [automate the generation of manifests.](/contracts/5.x/subgraphs/generate) + +### Assembling your schema + +Depending on the modules you are using, your schema will have to include the corresponding entities. Assembling a schema can be difficult since graphql schema do not natively support import and merging operations. We do provide precompiled schemas for each module in `generated/.schema.graphql`. We also provide a schema that includes all the entities for all the modules in `generated/all.schema.graphql`. + +Similar to the manifest, `@amxx/graphprotocol-utils` provides tooling to [automate the generation of schemas.](/contracts/5.x/subgraphs/generate) + +## Modules + +| Module name | Availability | +| --- | --- | +| **erc20** | ✔ | +| **erc20votes** | Planned | +| **erc721** | ✔ | +| **erc777** | Planned | +| **erc1155** | ✔ | +| **erc1967upgrade** | ✔ | +| **ownable** | ✔ | +| **accesscontrol** | ✔ | +| **pausable** | ✔ | +| **timelock** | ✔ | +| **governor** | ✔ | + +## Usage example + +By combining multiple modules and datasources in your subgraph, you can build query such as the following one, which +check the details of an ERC20 token with AccessControl on top of it, and returns the balance of the administrators. + +```graphql +{ + erc20Contract(id: "") { + name + symbol + decimals + totalSupply { value } + asAccount { + asAccessControl { + admins: roles(where: { role: "0x0000000000000000000000000000000000000000000000000000000000000000" }) { + members { + account { + address: id + balance: ERC20balances(where: { contract: "" }) { + value + } + } + } + } + } + } + } +} +``` diff --git a/docs/content/contracts/5.x/tokens.mdx b/docs/content/contracts/5.x/tokens.mdx new file mode 100644 index 00000000..80ea15d6 --- /dev/null +++ b/docs/content/contracts/5.x/tokens.mdx @@ -0,0 +1,31 @@ +--- +title: 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 + +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 + +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 [fungibility](https://en.wikipedia.org/wiki/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: + +* [ERC-20](./erc20): the most widespread token standard for fungible assets, albeit somewhat limited by its simplicity. +* [ERC-721](./erc721): the de-facto solution for non-fungible tokens, often used for collectibles and games. +* [ERC-1155](./erc1155): 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. diff --git a/docs/content/contracts/5.x/upgradeable.mdx b/docs/content/contracts/5.x/upgradeable.mdx new file mode 100644 index 00000000..c12ed4d9 --- /dev/null +++ b/docs/content/contracts/5.x/upgradeable.mdx @@ -0,0 +1,83 @@ +--- +title: Using with Upgrades +--- + +If your contract is going to be deployed with upgradeability, such as using the [OpenZeppelin Upgrades Plugins](/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 [OpenZeppelin/openzeppelin-contracts-upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable). It uses `@openzeppelin/contracts` as a peer dependency. + +It follows all of the rules for [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable): constructors are replaced by initializer functions, state variables are initialized in initializer functions, and we additionally check for storage incompatibilities across minor versions. + + +OpenZeppelin provides a full suite of tools for deploying and securing upgradeable smart contracts. [Check out the full list of resources](/upgrades). + + +## 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 { +``` + + +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") public { ++ function initialize() initializer public { ++ __ERC721_init("MyCollectible", "MCO"); + } +``` + + +Use with multiple inheritance requires special attention. See the section below titled [Multiple Inheritance](#multiple-inheritance). + + +Once this contract is set up and compiled, you can deploy it using the [Upgrades Plugins](/upgrades-plugins). The following snippet shows an example deployment script using Hardhat. + +```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 + +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:` annotation to store the contract’s state variables. This follows the [ERC-7201: Namespaced Storage Layout](https://eips.ethereum.org/EIPS/eip-7201) 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 [Writing Upgradeable Contracts](/upgrades-plugins/writing-upgradeable#modifying-your-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. diff --git a/docs/content/contracts/5.x/utilities.mdx b/docs/content/contracts/5.x/utilities.mdx new file mode 100644 index 00000000..54e4b751 --- /dev/null +++ b/docs/content/contracts/5.x/utilities.mdx @@ -0,0 +1,573 @@ +--- +title: Utilities +--- + +The OpenZeppelin Contracts provide a ton of useful utilities that you can use in your project. For a complete list, check out the [API Reference](/contracts/5.x/api/utils). +Here are some of the more popular ones. + +## Cryptography + +### Checking Signatures On-Chain + +At a high level, signatures are a set of cryptographic algorithms that allow for a _signer_ to prove himself 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 ([ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)) using the secp256k1 curve, however other signature algorithms such as P256 and RSA are supported. + +#### Ethereum Signatures (secp256k1) + +[`ECDSA`](/contracts/5.x/api/utils/cryptography#ECDSA) provides functions for recovering and managing Ethereum account ECDSA signatures. These are often generated via [`web3.eth.sign`](https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#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 [`ECDSA.recover`](/contracts/5.x/api/utils/cryptography#ECDSA-recover-bytes32-bytes-), 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 [`toEthSignedMessageHash`](/contracts/5.x/api/utils/cryptography#MessageHashUtils-toEthSignedMessageHash-bytes32-). + +```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; +} +``` + + +Getting signature verification right is not trivial: make sure you fully read and understand [`MessageHashUtils`](/contracts/5.x/api/utils/cryptography#MessageHashUtils)'s and [`ECDSA`](/contracts/5.x/api/utils/cryptography#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. + +```solidity +using P256 for bytes32; + +function _verify( + bytes32 data, + bytes32 r, + bytes32 s, + bytes32 qx, + bytes32 qy +) internal pure returns (bool) { + return data.verify(data, r, s, qx, qy); +} +``` + +By default, the `verify` function will try calling the [RIP-7212](https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md) 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`. + +```solidity +using P256 for bytes32; + +function _verify( + bytes32 data, + bytes32 r, + bytes32 s, + bytes32 qx, + bytes32 qy +) internal pure returns (bool) { + // Will only call the precompile at address(0x100) + return data.verifyNative(data, r, s, qx, qy); +} +``` + + +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 ([PKIs](https://en.wikipedia.org/wiki/Public_key_infrastructure)) and [DNSSEC](https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions). + +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 [RFC8017](https://datatracker.ietf.org/doc/html/rfc8017) to include padding to the signature. + +To verify a signature using RSA, you can leverage the [`RSA`](/contracts/5.x/api/utils/cryptography#RSA) library that exposes a method for verifying RSA with the PKCS 1.5 standard: + +```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); +} +``` + + +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 [`SignatureChecker`](/contracts/5.x/api/utils/cryptography#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: + +```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: + +```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`. + +```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: + +```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. + + +OpenZeppelin Contracts provides a [JavaScript library](https://github.com/OpenZeppelin/merkle-tree) for building trees off-chain and generating proofs. + + +[`MerkleProof`](/contracts/5.x/api/utils/cryptography#MerkleProof) provides: + +* [`verify`](/contracts/5.x/api/utils/cryptography#MerkleProof-verify-bytes32---bytes32-bytes32-) - can prove that some value is part of a [Merkle tree](https://en.wikipedia.org/wiki/Merkle_tree). +* [`multiProofVerify`](/contracts/5.x/api/utils/cryptography#MerkleProof-multiProofVerify-bytes32-bytes32---bytes32---bool---) - can prove multiple values are part of a Merkle tree. + +For an on-chain Merkle Tree, see the [`MerkleTree`](/contracts/5.x/api/utils#MerkleTree) library. + +## 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 helps do runtime interface detection. Contracts provide helpers both for implementing ERC-165 in your contracts and querying other contracts: + +* [`IERC165`](/contracts/5.x/api/utils#IERC165) — this is the ERC-165 interface that defines [`supportsInterface`](/contracts/5.x/api/utils#IERC165-supportsInterface-bytes4-). When implementing ERC-165, you’ll conform to this interface. +* [`ERC165`](/contracts/5.x/api/utils#ERC165) — inherit this contract if you’d like to support interface detection using a lookup table in contract storage. You can register interfaces using [`_registerInterface(bytes4)`](/contracts/5.x/api/utils#ERC165-_registerInterface-bytes4-): check out example usage as part of the ERC-721 implementation. +* [`ERC165Checker`](/contracts/5.x/api/utils#ERC165Checker) — ERC165Checker simplifies the process of checking whether or not a contract supports an interface you care about. +* include with `using ERC165Checker for address;` +* [`myAddress._supportsInterface(bytes4)`](/contracts/5.x/api/utils#ERC165Checker-_supportsInterface-address-bytes4-) +* [`myAddress._supportsAllInterfaces(bytes4[\])`](/contracts/5.x/api/utils#ERC165Checker-_supportsAllInterfaces-address-bytes4---) + +```solidity +contract MyContract { + using ERC165Checker for address; + + 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 + +Although Solidity already provides math operators (i.e. `+`, `-`, etc.), Contracts includes [`Math`](/contracts/5.x/api/utils#Math); a set of utilities for dealing with mathematical operators, with support for extra operations (e.g., [`average`](/contracts/5.x/api/utils#Math-average-uint256-uint256-)) and [`SignedMath`](/contracts/5.x/api/utils#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: + +```solidity +contract MyContract { + using Math for uint256; + using SignedMath for int256; + + function tryOperations(uint256 a, uint256 b) internal pure { + (bool succeededAdd, uint256 resultAdd) = x.tryAdd(y); + (bool succeededSub, uint256 resultSub) = x.trySub(y); + (bool succeededMul, uint256 resultMul) = x.tryMul(y); + (bool succeededDiv, uint256 resultDiv) = x.tryDiv(y); + // ... + } + + function unsignedAverage(int256 a, int256 b) { + int256 avg = a.average(b); + // ... + } +} +``` + +Easy! + + +While working with different data types that might require casting, you can use [`SafeCast`](/contracts/5.x/api/utils#SafeCast) for type casting with added overflow checks. + + +## Structures + +Some use cases require more powerful data structures than arrays and mappings offered natively in Solidity. Contracts provides these libraries for enhanced data structure management: + +* [`BitMaps`](/contracts/5.x/api/utils#BitMaps): Store packed booleans in storage. +* [`Checkpoints`](/contracts/5.x/api/utils#Checkpoints): Checkpoint values with built-in lookups. +* [`DoubleEndedQueue`](/contracts/5.x/api/utils#DoubleEndedQueue): Store items in a queue with `pop()` and `queue()` constant time operations. +* [`EnumerableSet`](/contracts/5.x/api/utils#EnumerableSet): A [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type)) with enumeration capabilities. +* [`EnumerableMap`](/contracts/5.x/api/utils#EnumerableMap): A `mapping` variant with enumeration capabilities. +* [`MerkleTree`](/contracts/5.x/api/utils#MerkleTree): An on-chain [Merkle Tree](https://wikipedia.org/wiki/Merkle_Tree) with helper functions. +* [`Heap`](/contracts/5.x/api/utils#Heap.sol): A [binary heap](https://en.wikipedia.org/wiki/Binary_heap) to store elements with priority defined by a compartor 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 [`MerkleTree`](/contracts/5.x/api/utils#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: + + +Functions are exposed without access control for demonstration purposes + + +```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 [`push`](/contracts/5.x/api/utils#MerkleTree-push-struct-MerkleTree-Bytes32PushTree-bytes32-) and [`setup`](/contracts/5.x/api/utils#MerkleTree-setup-struct-MerkleTree-Bytes32PushTree-uint8-bytes32-) functions. + +Using custom hashing functions is a sensitive operation. After setup, it requires to keep using 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: + +```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 [binary heap](https://en.wikipedia.org/wiki/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 [`lt`](/contracts/5.x/api/utils/cryptography#Comparators-lt-uint256-uint256-) 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: + +```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 + +### Packing + +The storage in the EVM is shaped in chunks of 32 bytes, each of this chunks is known as a _slot_, and can hold multiple values together as long as these values don't exceed its size. These properties of the storage allow for a technique known as _packing_, that consists of placing values together on a single storage slot to reduce the costs associated to reading and writing to multiple slots instead of just one. + +Commonly, developers pack values using structs that place values together so they fit better in storage. However, this approach requires to load 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 [`Packing`](/contracts/5.x/api/utils#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: + +```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 [`StorageSlot`](/contracts/5.x/api/utils#StorageSlot) library allows for manipulating storage slots directly. + +```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 [`TransientSlot`](/contracts/5.x/api/utils#TransientSlot) library supports transient storage through user defined value types ([UDVTs](https://docs.soliditylang.org/en/latest/types.html#user-defined-value-types)), which enables the same value types as in Solidity. + +```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); +} +``` + + +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 [`SlotDerivation`](/contracts/5.x/api/utils#SlotDerivation) library. + +```solidity +using SlotDerivation for bytes32; +string private constant _NAMESPACE = "" // eg. example.main + +function erc7201Pointer() internal view returns (bytes32) { + return _NAMESPACE.erc7201Slot(); +} +``` + +### Base64 + +[`Base64`](/contracts/5.x/api/utils#Base64) util allows you to transform `bytes32` data into its Base64 `string` representation. + +This is especially useful for building URL-safe tokenURIs for both [`ERC-721`](/contracts/5.x/api/token/ERC721#IERC721Metadata-tokenURI-uint256-) or [`ERC-1155`](/contracts/5.x/api/token/ERC1155#IERC1155MetadataURI-uri-uint256-). This library provides a clever way to serve URL-safe [Data URI](https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs/) 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: + +./examples/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: + +./examples/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: + +```javascript +const instance = await ethers.deployContract("Box"); + +await instance.multicall([ + instance.interface.encodeFunctionData("foo"), + instance.interface.encodeFunctionData("bar") +]); +``` + +### Memory + +The [`Memory`](/contracts/5.x/api/utils#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: + +```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: + +```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.setFreeMemoryPointer(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. + + +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 [memory layout](https://docs.soliditylang.org/en/v0.8.20/internals/layout_in_memory.html) and [memory safety](https://docs.soliditylang.org/en/v0.8.20/assembly.html#memory-safety) documentation for details. + + +### Historical Block Hashes + +[`Blockhash`](/contracts/5.x/api/utils#Blockhash) provides L2 protocol developers with extended access to historical block hashes beyond Ethereum's native 256-block limit. By leveraging [EIP-2935](https://eips.ethereum.org/EIPS/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. + +```solidity +contract L1Inbox { + using Blockhash for uint256; + + function verifyBlockHash(uint256 blockNumber, bytes32 expectedHash) public view returns (bool) { + return blockNumber.blockHash() == expectedHash; + } +} +``` + + +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 [`Time`](/contracts/5.x/api/utils#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: + +```solidity +pragma solidity ^0.8.27; + +import Time from "contracts/utils/types/Time.sol"; + +contract MyDelayedContract { + using Time for *; + + 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 [AccessManager](/contracts/5.x/api/access#AccessManager) for implementing secure time-based access control. For example, when changing an admin delay: + +```solidity +function _setTargetAdminDelay(address target, uint32 newDelay) internal virtual { + uint48 effect; + (_targets[target].adminDelay, effect) = _targets[target].adminDelay.withUpdate( + newDelay, + minSetback() + ); + + emit TargetAdminDelayUpdated(target, newDelay, effect); +} +``` diff --git a/docs/content/contracts/5.x/wizard.mdx b/docs/content/contracts/5.x/wizard.mdx new file mode 100644 index 00000000..b3d943ea --- /dev/null +++ b/docs/content/contracts/5.x/wizard.mdx @@ -0,0 +1,12 @@ +--- +title: Contracts Wizard +--- + +Not sure where to start? Use the interactive generator below to bootstrap your +contract and learn about the components offered in OpenZeppelin Contracts. + + +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 [Developing Smart Contracts](./learn/developing-smart-contracts) for more guidance! + + + diff --git a/docs/content/contracts/index.mdx b/docs/content/contracts/index.mdx new file mode 100644 index 00000000..059d1556 --- /dev/null +++ b/docs/content/contracts/index.mdx @@ -0,0 +1,101 @@ +--- +title: OpenZeppelin Contracts +--- + +OpenZeppelin Contracts is a library of modular, reusable, secure smart contracts for the Ethereum network, written in Solidity. + +## Getting Started + + + + Start working with the OpenZeppelin Contracts Library. + + + Use the interactive Contracts Wizard to bootstrap your contract and learn about the components offered in OpenZeppelin Contracts. + + + +## Core Features + + + + Implementations of ERC20, ERC721, ERC1155, and other token standards. + + + Manage permissions and roles in your smart contracts securely. + + + Build decentralized governance systems for your protocols. + + + Helper contracts and libraries for common blockchain development tasks. + + + +## Token Standards + + + + Fungible token standard implementation with extensions and utilities. + + + Non-fungible token (NFT) standard with advanced features. + + + Multi-token standard for both fungible and non-fungible tokens. + + + Tokenized vault standard for yield-bearing assets. + + + +## Advanced Features + + + + Smart account implementations and account abstraction utilities. + + + Learn how to build upgradeable smart contracts safely. + + + Interactive tool to generate smart contracts with custom features. + + + Tools for deploying and upgrading smart contracts with Hardhat and Foundry. + + + +## API Reference + + + + Complete API reference for all OpenZeppelin Contracts. + + + Detailed API documentation for ERC20 tokens and extensions. + + + Complete API reference for ERC721 NFT contracts. + + + API documentation for access control and ownership patterns. + + + +## Tools & Integrations + + + + Automate onchain transactions to schedule jobs, batch calls, and relay gasless meta transactions within your self-hosted infrastructure. + + + Monitor onchain activity in real time to watch critical events, detect anomalies, trigger alerts on your preferred channels, and set automated responses with Relayer. + + + Spin up user interfaces for any deployed contract. Select the function, auto-generate a React UI with wallet-connect and multi-network support, and export a complete app. + + + Additional contracts and extensions contributed by the community. + + diff --git a/docs/content/defender/changelog.mdx b/docs/content/defender/changelog.mdx new file mode 100644 index 00000000..5460b6ee --- /dev/null +++ b/docs/content/defender/changelog.mdx @@ -0,0 +1,224 @@ +--- +title: Changelog +--- + + +# [v1.54.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.54.0) - 2023-12-07 + +## What's Changed +* Fix the lerna publish for the npm packages by [@collins-w](https://github.com/collins-w) in [#439](https://github.com/OpenZeppelin/defender-client/pull/439) +* add arbitrum sepolia support by [@MCarlomagno](https://github.com/MCarlomagno) in [#438](https://github.com/OpenZeppelin/defender-client/pull/438) +* Release preminor versions for RC by [@collins-w](https://github.com/collins-w) in [#445](https://github.com/OpenZeppelin/defender-client/pull/445) +* Remove the GPG key by [@collins-w](https://github.com/collins-w) in [#451](https://github.com/OpenZeppelin/defender-client/pull/451) +* Revert "Remove the GPG key" by [@collins-w](https://github.com/collins-w) in [#452](https://github.com/OpenZeppelin/defender-client/pull/452) +* Run the deploy steps only when lerna detects changes by [@collins-w](https://github.com/collins-w) in [#456](https://github.com/OpenZeppelin/defender-client/pull/456) +* Bump actions/upload-artifact from 3.1.2 to 3.1.3 by [@dependabot](https://github.com/dependabot) in [#376](https://github.com/OpenZeppelin/defender-client/pull/376) +* defender-client-deps: bump nx-cloud from 16.0.5 to 16.5.2 by [@dependabot](https://github.com/dependabot) in [#397](https://github.com/OpenZeppelin/defender-client/pull/397) +* Bump actions/checkout from 4.1.0 to 4.1.1 by [@dependabot](https://github.com/dependabot) in [#435](https://github.com/OpenZeppelin/defender-client/pull/435) +* defender-client-deps: bump axios from 1.4.0 to 1.6.2 by [@dependabot](https://github.com/dependabot) in [#440](https://github.com/OpenZeppelin/defender-client/pull/440) +* Bump actions/dependency-review-action from 3.1.0 to 3.1.4 by [@dependabot](https://github.com/dependabot) in [#457](https://github.com/OpenZeppelin/defender-client/pull/457) +* Add support for optimism sepolia by [@MCarlomagno](https://github.com/MCarlomagno) in [#454](https://github.com/OpenZeppelin/defender-client/pull/454) +* Add base sepolia by [@MCarlomagno](https://github.com/MCarlomagno) in [#455](https://github.com/OpenZeppelin/defender-client/pull/455) +* Increase the dependabot interval and examples from being watched by [@collins-w](https://github.com/collins-w) in [#461](https://github.com/OpenZeppelin/defender-client/pull/461) + + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.52.0...v1.54.0 + +[Changes][v1.54.0] + + + +# [v1.52.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.52.0) - 2023-11-10 + +## What's Changed +* Add environment variable endpoints for autotasks by [@shahnami](https://github.com/shahnami) in [#414](https://github.com/OpenZeppelin/defender-client/pull/414) +* Add Meld network by [@shahnami](https://github.com/shahnami) in [#419](https://github.com/OpenZeppelin/defender-client/pull/419) + + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.51.0...v1.52.0 + +[Changes][v1.52.0] + + + +# [v1.51.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.51.0) - 2023-11-07 + +## What's Changed +* Add support to scroll mainnet by [@MCarlomagno](https://github.com/MCarlomagno) in [#406](https://github.com/OpenZeppelin/defender-client/pull/406) +* Support proposals pagination by [@MCarlomagno](https://github.com/MCarlomagno) in [#422](https://github.com/OpenZeppelin/defender-client/pull/422) +* defender-client-deps: bump @babel/traverse from 7.22.8 to 7.23.2 by [@dependabot](https://github.com/dependabot) in [#398](https://github.com/OpenZeppelin/defender-client/pull/398) +* Bump ossf/scorecard-action from 2.2.0 to 2.3.1 by [@dependabot](https://github.com/dependabot) in [#412](https://github.com/OpenZeppelin/defender-client/pull/412) +* defender-client-deps: bump aws-sdk from 2.1414.0 to 2.1488.0 by [@dependabot](https://github.com/dependabot) in [#423](https://github.com/OpenZeppelin/defender-client/pull/423) +* Bump crazy-max/ghaction-import-gpg from 5.3.0 to 6.0.0 by [@dependabot](https://github.com/dependabot) in [#377](https://github.com/OpenZeppelin/defender-client/pull/377) +* Bump actions/setup-node from 3.6.0 to 4.0.0 by [@dependabot](https://github.com/dependabot) in [#407](https://github.com/OpenZeppelin/defender-client/pull/407) + + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.50.0...v1.51.0 + +[Changes][v1.51.0] + + + +# [v1.50.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.50.0) - 2023-10-25 + +## What's Changed +* Add support to scroll sepolia network by [@MCarlomagno](https://github.com/MCarlomagno) in [#379](https://github.com/OpenZeppelin/defender-client/pull/379) +* Add support for relayer status endpoint by [@zeljkoX](https://github.com/zeljkoX) in [#381](https://github.com/OpenZeppelin/defender-client/pull/381) +* Add Account client and support to retrieve quotas usage by [@zeljkoX](https://github.com/zeljkoX) in [#404](https://github.com/OpenZeppelin/defender-client/pull/404) + + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.49.0...v1.50.0 + +[Changes][v1.50.0] + + + +# [v1.49.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.49.0) - 2023-10-09 + +## What's Changed +* Support api versioning by [@MCarlomagno](https://github.com/MCarlomagno) in [#312](https://github.com/OpenZeppelin/defender-client/pull/312) +* add defender v2 env var to readme by [@MCarlomagno](https://github.com/MCarlomagno) in [#316](https://github.com/OpenZeppelin/defender-client/pull/316) +* defender-client-deps: bump eslint from 8.44.0 to 8.50.0 by [@dependabot](https://github.com/dependabot) in [#323](https://github.com/OpenZeppelin/defender-client/pull/323) +* defender-client-deps: bump nx from 16.5.1 to 16.9.0 by [@dependabot](https://github.com/dependabot) in [#322](https://github.com/OpenZeppelin/defender-client/pull/322) +* Bump step-security/harden-runner from 2.4.0 to 2.5.1 by [@dependabot](https://github.com/dependabot) in [#306](https://github.com/OpenZeppelin/defender-client/pull/306) +* defender-client-deps: bump code-style from `0c7b307` to `a6cd128` by [@dependabot](https://github.com/dependabot) in [#315](https://github.com/OpenZeppelin/defender-client/pull/315) +* defender-client-deps: bump nx from 16.9.0 to 16.9.1 by [@dependabot](https://github.com/dependabot) in [#327](https://github.com/OpenZeppelin/defender-client/pull/327) +* Add Safe support as viaType by [@MCarlomagno](https://github.com/MCarlomagno) in [#320](https://github.com/OpenZeppelin/defender-client/pull/320) +* Bump release-drafter/release-drafter from 5.23.0 to 5.24.0 by [@dependabot](https://github.com/dependabot) in [#285](https://github.com/OpenZeppelin/defender-client/pull/285) +* Bump anchore/sbom-action from 0.14.2 to 0.14.3 by [@dependabot](https://github.com/dependabot) in [#282](https://github.com/OpenZeppelin/defender-client/pull/282) +* Bump ossf/scorecard-action from 2.1.3 to 2.2.0 by [@dependabot](https://github.com/dependabot) in [#284](https://github.com/OpenZeppelin/defender-client/pull/284) +* fix deploy readme imports by [@MCarlomagno](https://github.com/MCarlomagno) in [#313](https://github.com/OpenZeppelin/defender-client/pull/313) +* defender-client-deps: bump lerna from 7.1.3 to 7.3.0 by [@dependabot](https://github.com/dependabot) in [#319](https://github.com/OpenZeppelin/defender-client/pull/319) +* defender-client-deps: bump prettier from 2.6.2 to 2.8.8 by [@dependabot](https://github.com/dependabot) in [#278](https://github.com/OpenZeppelin/defender-client/pull/278) +* [StepSecurity] Apply security best practices by [@step-security-bot](https://github.com/step-security-bot) in [#257](https://github.com/OpenZeppelin/defender-client/pull/257) +* Bump ncipollo/release-action from 1.12.0 to 1.13.0 by [@dependabot](https://github.com/dependabot) in [#328](https://github.com/OpenZeppelin/defender-client/pull/328) +* Bump actions/checkout from 3.5.2 to 4.1.0 by [@dependabot](https://github.com/dependabot) in [#329](https://github.com/OpenZeppelin/defender-client/pull/329) +* Bump actions/dependency-review-action from 2.5.1 to 3.1.0 by [@dependabot](https://github.com/dependabot) in [#330](https://github.com/OpenZeppelin/defender-client/pull/330) +* Bump step-security/harden-runner from 2.4.0 to 2.5.1 by [@dependabot](https://github.com/dependabot) in [#331](https://github.com/OpenZeppelin/defender-client/pull/331) +* Bump dotenv from 8.6.0 to 16.3.1 in /examples/pause-proposal by [@dependabot](https://github.com/dependabot) in [#332](https://github.com/OpenZeppelin/defender-client/pull/332) +* Bump @openzeppelin/defender-sentinel-client from 1.22.0 to 1.48.0 in /examples/create-sentinel by [@dependabot](https://github.com/dependabot) in [#333](https://github.com/OpenZeppelin/defender-client/pull/333) +* Bump dotenv from 8.6.0 to 16.3.1 in /examples/action-proposal by [@dependabot](https://github.com/dependabot) in [#334](https://github.com/OpenZeppelin/defender-client/pull/334) +* Bump dotenv from 8.6.0 to 16.3.1 in /examples/batch-proposal by [@dependabot](https://github.com/dependabot) in [#335](https://github.com/OpenZeppelin/defender-client/pull/335) +* Feature/paginated relayer list by [@zeljkoX](https://github.com/zeljkoX) in [#326](https://github.com/OpenZeppelin/defender-client/pull/326) +* Add support to Mantle by [@MCarlomagno](https://github.com/MCarlomagno) in [#321](https://github.com/OpenZeppelin/defender-client/pull/321) + +## New Contributors +* [@step-security-bot](https://github.com/step-security-bot) made their first contribution in [#257](https://github.com/OpenZeppelin/defender-client/pull/257) + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.48.0...v1.49.0 + +[Changes][v1.49.0] + + + +# [v1.48.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.48.0) - 2023-08-10 + +## What's Changed +* Add new matchedChecksumAddresses field to types by [@shahnami](https://github.com/shahnami) in [#293](https://github.com/OpenZeppelin/defender-client/pull/293) +* Remove provenance by [@tirumerla](https://github.com/tirumerla) in [#295](https://github.com/OpenZeppelin/defender-client/pull/295) +* PLAT-2112 Export SentinelBaseConditionSummary interface and sub interfaces by [@emnul](https://github.com/emnul) in [#297](https://github.com/OpenZeppelin/defender-client/pull/297) +* Expose network list endpoint by [@shahnami](https://github.com/shahnami) in [#292](https://github.com/OpenZeppelin/defender-client/pull/292) +* add support for base mainnet by [@mok0230](https://github.com/mok0230) in [#294](https://github.com/OpenZeppelin/defender-client/pull/294) +* add support for linea mainnet by [@mok0230](https://github.com/mok0230) in [#302](https://github.com/OpenZeppelin/defender-client/pull/302) + +## New Contributors +* [@emnul](https://github.com/emnul) made their first contribution in [#297](https://github.com/OpenZeppelin/defender-client/pull/297) + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.47.0...v1.48.0 + +[Changes][v1.48.0] + + + +# [v1.47.1](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.47.1) - 2023-07-28 + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.47.0...v1.47.1 + +[Changes][v1.47.1] + + + +# [v1.47.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.47.0) - 2023-07-12 + +## What's Changed +* Add scenario trigger type by [@dylankilkenny](https://github.com/dylankilkenny) in [#212](https://github.com/OpenZeppelin/defender-client/pull/212) +* remove git+ from repository field in package.json by [@mok0230](https://github.com/mok0230) in [#281](https://github.com/OpenZeppelin/defender-client/pull/281) +* Upgrade axios & fix tests by [@tirumerla](https://github.com/tirumerla) in [#286](https://github.com/OpenZeppelin/defender-client/pull/286) + + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.46.0...v1.47.0 + +[Changes][v1.47.0] + + + +# [v1.46.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.46.0) - 2023-06-14 + +## What's Changed +* Fix lerna positionals by [@tirumerla](https://github.com/tirumerla) in [#275](https://github.com/OpenZeppelin/defender-client/pull/275) +* defender-client-deps: bump @types/async-retry from 1.4.4 to 1.4.5 by [@dependabot](https://github.com/dependabot) in [#272](https://github.com/OpenZeppelin/defender-client/pull/272) +* Bump github/codeql-action from 2.3.6 to 2.13.4 by [@dependabot](https://github.com/dependabot) in [#267](https://github.com/OpenZeppelin/defender-client/pull/267) +* Bump actions/checkout from 3.5.2 to 3.5.3 by [@dependabot](https://github.com/dependabot) in [#269](https://github.com/OpenZeppelin/defender-client/pull/269) +* defender-client-deps: bump aws-sdk from 2.1390.0 to 2.1395.0 by [@dependabot](https://github.com/dependabot) in [#273](https://github.com/OpenZeppelin/defender-client/pull/273) +* Reference new [@openzeppelin](https://github.com/openzeppelin) scope by [@shahnami](https://github.com/shahnami) in [#276](https://github.com/OpenZeppelin/defender-client/pull/276) + + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.45.0...v1.46.0 + +[Changes][v1.46.0] + + + +# [v1.45.0](https://github.com/OpenZeppelin/defender-client/releases/tag/v1.45.0) - 2023-06-12 + +## What's Changed +* Bump defender-base-client version to the latest one - 1.44.0 by [@collins-w](https://github.com/collins-w) in [#234](https://github.com/OpenZeppelin/defender-client/pull/234) +* fix: add base goerli to valid list by [@0xsambugs](https://github.com/0xsambugs) in [#235](https://github.com/OpenZeppelin/defender-client/pull/235) +* Improve CI/CD & format fixes by [@tirumerla](https://github.com/tirumerla) in [#233](https://github.com/OpenZeppelin/defender-client/pull/233) +* Fix workflow patches & yq download url by [@tirumerla](https://github.com/tirumerla) in [#244](https://github.com/OpenZeppelin/defender-client/pull/244) +* Add license & fix wf permissions by [@tirumerla](https://github.com/tirumerla) in [#246](https://github.com/OpenZeppelin/defender-client/pull/246) +* Fix permissions to release by [@tirumerla](https://github.com/tirumerla) in [#247](https://github.com/OpenZeppelin/defender-client/pull/247) +* Fix RC workflow by [@tirumerla](https://github.com/tirumerla) in [#248](https://github.com/OpenZeppelin/defender-client/pull/248) +* Add conditions to rc workflow by [@tirumerla](https://github.com/tirumerla) in [#252](https://github.com/OpenZeppelin/defender-client/pull/252) +* Fix rc workflow bug by [@tirumerla](https://github.com/tirumerla) in [#253](https://github.com/OpenZeppelin/defender-client/pull/253) +* Fix condition on release job by [@tirumerla](https://github.com/tirumerla) in [#254](https://github.com/OpenZeppelin/defender-client/pull/254) +* Bump actions/upload-artifact from 3.1.0 to 3.1.2 by [@dependabot](https://github.com/dependabot) in [#239](https://github.com/OpenZeppelin/defender-client/pull/239) +* Bump ossf/scorecard-action from 2.1.2 to 2.1.3 by [@dependabot](https://github.com/dependabot) in [#238](https://github.com/OpenZeppelin/defender-client/pull/238) +* Bump actions/checkout from 3.1.0 to 3.5.2 by [@dependabot](https://github.com/dependabot) in [#236](https://github.com/OpenZeppelin/defender-client/pull/236) +* Bump github/codeql-action from 2.2.4 to 2.3.5 by [@dependabot](https://github.com/dependabot) in [#250](https://github.com/OpenZeppelin/defender-client/pull/250) +* Add Linea Goerli by [@ernestognw](https://github.com/ernestognw) in [#249](https://github.com/OpenZeppelin/defender-client/pull/249) +* Fix rc bug 3 by [@tirumerla](https://github.com/tirumerla) in [#255](https://github.com/OpenZeppelin/defender-client/pull/255) +* rename walletId to relayerId (deployments) by [@MCarlomagno](https://github.com/MCarlomagno) in [#251](https://github.com/OpenZeppelin/defender-client/pull/251) +* defender-client-deps: bump @types/node from 12.20.54 to 12.20.55 by [@dependabot](https://github.com/dependabot) in [#245](https://github.com/OpenZeppelin/defender-client/pull/245) +* defender-client-deps: bump web3-core-helpers from 1.9.0 to 1.10.0 by [@dependabot](https://github.com/dependabot) in [#243](https://github.com/OpenZeppelin/defender-client/pull/243) +* Generate SBOM for every release by [@tirumerla](https://github.com/tirumerla) in [#256](https://github.com/OpenZeppelin/defender-client/pull/256) +* defender-client-deps: bump @ethersproject/hash from 5.6.1 to 5.7.0 by [@dependabot](https://github.com/dependabot) in [#242](https://github.com/OpenZeppelin/defender-client/pull/242) +* defender-client-deps: bump @ethersproject/providers from 5.6.8 to 5.7.2 by [@dependabot](https://github.com/dependabot) in [#241](https://github.com/OpenZeppelin/defender-client/pull/241) +* Bump github/codeql-action from 2.3.3 to 2.3.6 by [@dependabot](https://github.com/dependabot) in [#258](https://github.com/OpenZeppelin/defender-client/pull/258) +* defender-client-deps: bump aws-sdk from 2.1367.0 to 2.1390.0 by [@dependabot](https://github.com/dependabot) in [#259](https://github.com/OpenZeppelin/defender-client/pull/259) +* defender-client-deps: bump jszip from 3.10.0 to 3.10.1 by [@dependabot](https://github.com/dependabot) in [#260](https://github.com/OpenZeppelin/defender-client/pull/260) +* defender-client-deps: bump web3-core from 1.9.0 to 1.10.0 by [@dependabot](https://github.com/dependabot) in [#261](https://github.com/OpenZeppelin/defender-client/pull/261) +* defender-client-deps: bump platform-deploy-client from 0.3.3 to 0.6.0 by [@dependabot](https://github.com/dependabot) in [#262](https://github.com/OpenZeppelin/defender-client/pull/262) +* Better deploy release docs by [@mok0230](https://github.com/mok0230) in [#265](https://github.com/OpenZeppelin/defender-client/pull/265) +* Fix: bugs in stable workflow by [@tirumerla](https://github.com/tirumerla) in [#266](https://github.com/OpenZeppelin/defender-client/pull/266) +* Test publishing by [@tirumerla](https://github.com/tirumerla) in [#274](https://github.com/OpenZeppelin/defender-client/pull/274) + +## New Contributors +* [@collins-w](https://github.com/collins-w) made their first contribution in [#234](https://github.com/OpenZeppelin/defender-client/pull/234) +* [@MCarlomagno](https://github.com/MCarlomagno) made their first contribution in [#251](https://github.com/OpenZeppelin/defender-client/pull/251) + +**Full Changelog**: https://github.com/OpenZeppelin/defender-client/compare/v1.44.0...v1.45.0 + +[Changes][v1.45.0] + + +[v1.54.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.52.0...v1.54.0 +[v1.52.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.51.0...v1.52.0 +[v1.51.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.50.0...v1.51.0 +[v1.50.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.49.0...v1.50.0 +[v1.49.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.48.0...v1.49.0 +[v1.48.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.47.1...v1.48.0 +[v1.47.1]: https://github.com/OpenZeppelin/defender-client/compare/v1.47.0...v1.47.1 +[v1.47.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.46.0...v1.47.0 +[v1.46.0]: https://github.com/OpenZeppelin/defender-client/compare/v1.45.0...v1.46.0 +[v1.45.0]: https://github.com/OpenZeppelin/defender-client/tree/v1.45.0 diff --git a/docs/content/defender/dac.mdx b/docs/content/defender/dac.mdx new file mode 100644 index 00000000..f323b02b --- /dev/null +++ b/docs/content/defender/dac.mdx @@ -0,0 +1,228 @@ +--- +title: Defender as Code Plugin +--- + +Defender as Code (DaC) is a Serverless Framework plugin for automated resource management and configuration as code. + + +This plugin is under development and behavior might change. Handle with care. + + +## Prerequisites + +Serverless Framework: https://www.serverless.com/framework/docs/getting-started/ + +## Installation + +You can initialise your Serverless project directly using our pre-configured template: + +``` +sls install --url https://github.com/OpenZeppelin/defender-as-code/tree/main/template -n my-service +``` + + +For the command above to work correctly you need access to this repo. + + +Alternatively, you can install it directly into an existing project with: + +`yarn add @openzeppelin/defender-as-code` + +## Setup + +There are a few ways you can set up the `serverless.yml` configuration: + +* Create it from scratch; +* Use Defender’s 2.0 Serverless export capability; +* Leverage the example [template](https://github.com/OpenZeppelin/defender-as-code/blob/main/template/serverless.yml) provided in the `defender-as-code` repository. + +If you already have resources such as contracts, notifications, relayers, actions, etc. in Defender, you can export a `serverless.yml` configuration file containing these resources from the manage → advanced page. + +![Defender Export Serverless](/defender/manage-advanced-export-serverless.png) + + +If you have previously deployed with `defender-as-code` to the same account and subsequently created new resources through the Defender user interface, the export function will automatically assign a `stackResourceId` to the new resources based on the name of your latest deployment stack. If you have not deployed using `defender-as-code` before, a default stack name of `mystack` will be used. + + +This plugin allows you to define Actions, Monitors, Notifications, Block Explorer API Keys, Relayers, Contracts, Policies, and Secrets declaratively from a `serverless.yml` and provision them via the CLI using `serverless deploy`. An example template below with an action, a relayer, a policy and a single relayer API key defined: + +```yaml +service: defender-as-code-template +configValidationMode: error +frameworkVersion: '3' + +provider: + name: defender + stage: $opt:stage, 'dev' + stackName: 'mystack' + ssot: false + +defender: + key: '$env:TEAM_API_KEY' + secret: '$env:TEAM_API_SECRET' + +resources: + actions: + action-example-1: + name: 'Hello world from serverless' + path: './actions/hello-world' + relayer: $self:resources.relayers.relayer-1 + trigger: + type: 'schedule' + frequency: 1500 + paused: false + # optional - unencrypted and scoped to the individual action + environment-variables: + hello: 'world!' + action-example-2: 2cbc3f58-d962-4be8-a158-1035be4b661c + + policies: + policy-1: + gas-price-cap: 1000 + whitelist-receivers: + - '0x0f06aB75c7DD497981b75CD82F6566e3a5CAd8f2' + eip1559-pricing: true + + relayers: + relayer-1: + name: 'Test Relayer 1' + network: 'sepolia' + min-balance: 1000 + policy: $self:resources.policies.policy-1 + api-keys: + - key1 + +plugins: + - '@openzeppelin/defender-as-code' +``` + +This requires setting the `key` and `secret` under the `defender` property of the YAML file. We recommend using environment variables or a secure (gitignored) configuration file to retrieve these values. Modify the `serverless.yml` accordingly. + +Ensure the Defender Team API Keys are setup with all appropriate API capabilities. + +The `stackName` (e.g. mystack) is combined with the resource key (e.g. relayer-1) to uniquely identify each resource. This identifier is called the `stackResourceId` (e.g. mystack.relayer-1) and allows you to manage multiple deployments within the same tenant. + +You may also reference existing Defender resources directly by their unique ID (e.g. `2cbc3f58-d962-4be8-a158-1035be4b661c`). These resources will not be managed by the plugin and will be ignored during the deploy process. However, you may reference them in other resources to update their configuration accordingly. +A list of properties that support direct referencing: + +* `relayer` may reference a `relayerId` in Actions +* `action-trigger` may reference an `actionid` in Monitor +* `action-condition` may reference an `actionId` in Monitor +* `address-from-relayer` may reference a `relayerId` in Relayer +* `notify-config.channels` may reference multiple `notificationId` in Monitor +* `contracts` may be used over `addresses` and reference multiple `contractId` in Monitor + The following is an example of how a direct reference to a Defender contract and relayer can be used in monitor and action respectively: + +```yaml +... +contracts: + contract-1: 'sepolia-0xd70d6A0480420b4C788AF91d0E1b0ca6141A9De8' # contractId of an existing resource in Defender +relayers: + relayer-2: 'bcb659c6-7e11-4d37-a15b-0fa9f3d3442c' # relayerId of an existing relayer in Defender +actions: + action-example-1: + name: 'Hello world from serverless' + path: './actions/hello-world' + relayer: $self:resources.relayers.relayer-2 + trigger: + type: 'schedule' + frequency: 1500 + paused: false +monitors: + block-example: + name: 'Block Example' + type: 'BLOCK' + network: 'sepolia' + risk-category: 'TECHNICAL' + # optional - either contracts OR addresses should be defined + contracts: + - $self:resources.contracts.contract-1 + ... +... +``` + +### SSOT mode + +Under the `provider` property in the `serverless.yml` file, you can optionally add a `ssot` boolean. SSOT or Single Source of Truth, ensures that the state of your stack in Defender is perfectly in sync with the `serverless.yml` template. +This means that all resources, that are not defined in your current template file, are removed from Defender, with the exception of Relayers, upon deployment. If SSOT is not defined in the template, it will default to `false`. + +Any resource removed from the `serverless.yml` file does _not_ get automatically deleted in order to prevent inadvertent resource deletion. For this behaviour to be anticipated, SSOT mode must be enabled. + +### Secrets (Actions) + +Action secrets can be defined both globally and per stack. Secrets defined under `global` are not affected by changes to the `stackName` and will retain when redeployed under a new stack. Secrets defined under `stack` will be removed (on the condition that [SSOT mode](#ssot-mode) is enabled) when the stack is redeployed under a new `stackName`. To reference secrets defined under `stack`, use the following format: `_`, for example `mystack_test`. + +```yaml +secrets: + # optional - global secrets are not affected by stackName changes + global: + foo: $self:custom.config.secrets.foo + hello: $self:custom.config.secrets.hello + # optional - stack secrets (formatted as _) + stack: + test: $self:custom.config.secrets.test +``` + +### Types and Schema validation + +We provide auto-generated documentation based on the JSON schemas: + +* [Defender Property](https://github.com/OpenZeppelin/defender-as-code/blob/main/src/types/docs/defender.md) +* [Provider Property](https://github.com/OpenZeppelin/defender-as-code/blob/main/src/types/docs/provider.md) +* [Resources Property](https://github.com/OpenZeppelin/defender-as-code/blob/main/src/types/docs/resources.md) + +More information on types can be found [here](https://github.com/OpenZeppelin/defender-as-code/blob/main/src/types/index.ts). Specifically, the types preceded with `Y` (e.g. YRelayer). For the schemas, you can check out the [docs-schema](https://github.com/OpenZeppelin/defender-as-code/blob/main/src/types/docs-schemas) folder. + +Additionally, an [example project](https://github.com/OpenZeppelin/defender-as-code/blob/main/examples/defender-test-project/serverless.yml) is available which provides majority of properties that can be defined in the `serverless.yml` file. + +## Commands + +### Deploy + +You can use `sls deploy` to deploy your current stack to Defender. + +The deploy takes in an optional `--stage` flag, which is defaulted to `dev` when installed from the template above. + +Moreover, the `serverless.yml` may contain an `ssot` property. More information can be found in the [SSOT mode](#ssot-mode) section. + +This command will append a log entry in the `.defender` folder of the current working directory. Additionally, if any new relayer keys are created, these will be stored as JSON objects in the `.defender/relayer-keys` folder. + + +When installed from the template, we ensure the `.defender` folder is ignored from any git commits. However, when installing directly, make sure to add this folder in your `.gitignore` file. + + +### Info + +You can use `sls info` to retrieve information on every resource defined in the `serverless.yml` file, including unique identifiers, and properties unique to each component. + +### Remove + +You can use `sls remove` to remove all resources defined in the `serverless.yml` file from Defender. + + +To avoid potential loss of funds, Relayers can only be deleted from the Defender UI directly. + + +### Logs + +You can use `sls logs --function ` to retrieve the latest action logs for a given action identifier (e.g. mystack.action-example-1). This command will run continiously and retrieve logs every 2 seconds. + +### Invoke + +You can use `sls invoke --function ` to manually run an action, given its identifier (e.g. mystack.action-example-1). + + +Each command has a standard output to a JSON object. + + +## Caveats + +Errors thrown during the `deploy` process, will not revert any prior changes. Common errors are: + +* Not having set the API key and secret +* Insufficient permissions for the API key +* Validation error of the `serverless.yml` file (see [Types and Schema Validation](#types-and-schema-validation)) + +Usually, fixing the error and retrying the deploy should suffice as any existing resources will fall within the `update` clause of the deployment. However, if unsure, you can always call `sls remove` to remove the entire stack, and retry. + +Action secrets are encrypted key-value pairs and injected at runtime into the lambda environment. Secrets are scoped to all actions automatically. Alternatively, you may use environment-variables to define key-value pairs that are scoped to the individual action, and available at runtime through `process.env`. Note that these values are not encrypted. diff --git a/docs/content/defender/faq.mdx b/docs/content/defender/faq.mdx new file mode 100644 index 00000000..55c840ef --- /dev/null +++ b/docs/content/defender/faq.mdx @@ -0,0 +1,35 @@ +--- +title: Frequently Asked Questions (FAQ) +--- + +OpenZeppelin Defender is the evolution of Defender, with an improved user experience, a cleaner interface, and new features that offer a more cohesive experience across the DevSecOps lifecycle. + +## How can I sign up to Defender? + +New sign-ups were disabled on June 30, 2025. Until the final shutdown on July 1, 2026, Defender will remain fully operational while we focus on the open source versions of tools like Relayers and Monitor. Detailed migration guides are coming soon to help you transition seamlessly to open source. + +[Read more](https://blog.openzeppelin.com/doubling-down-on-open-source-and-phasing-out-defender) + +## Once I migrate to Defender, can I continue using Defender legacy? + +No, once you migrate to Defender, you will no longer have access to Defender 1.0 UI and API. + +## Are there any breaking changes when migrating? + +Yes, there are multiple changes to the API endpoints that will require you to update your integrations. You can find the API docs [here](https://www.api-docs.defender.openzeppelin.com/#defender-sdk). + +## What are the pricing options for Defender? + +You can find the pricing for Defender [here](https://www.openzeppelin.com/pricing). + +## Does Defender offer support? + +We offer a Service Level and Support Agreement (SLA) for paid subscriptions. Learn more [here](#sla). + +## How can I get an upgrade my tenant account? + +You can use the billing page to upgrade your tenant account to a higher tier [here](https://defender.openzeppelin.com/v2/#/billing/). + +## Where can I see my tier quota usage? + +You can see your tier quota usage in the billing page [here](https://defender.openzeppelin.com/v2/#/billing/usage). diff --git a/docs/content/defender/guide/factory-monitor.mdx b/docs/content/defender/guide/factory-monitor.mdx new file mode 100644 index 00000000..d0176880 --- /dev/null +++ b/docs/content/defender/guide/factory-monitor.mdx @@ -0,0 +1,243 @@ +--- +title: Automatic monitoring for factory clones +--- + +The factory-clone pattern can be advantageous for minimizing gas costs. However, since each clone gets deployed to a new address, it may be a challenge to efficiently track and monitor each of these contracts. + +This guide shows how to use Defender to monitor a factory contract as well as the clone contracts created by it. Monitor automation is achieved through the following structure of Defender modules: + +* A Monitor watches for successful event emitted by the factory contract that creates a clone. If detected, it triggers an [Action](/defender/module/actions) and [passes along information](/defender/module/actions#monitor-invocations) about the transaction. +* The Action makes use of the [`defender-sdk`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) to add the address of the newly created contract to the [address book](/defender/module/address-book) for easier monitoring. +* Aditionally, the Action uses the [`defender-sdk`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) to add the clone address to the list of addresses watched by a Monitor. + +In this case, the contract ABI can be pre-supplied since clone contracts will have identical ABIs. Alternatively, you may be able to dynamically retrieve the ABI from a verified contract at a given address using [Etherscan’s API](https://docs.etherscan.io/api-endpoints/contracts). + +## Generate API Key + +To programmatically add a contract to the address book, the [`sdk`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) requires credentials in the form of an API key and secret. Create and copy the credentials in the [API keys page](https://defender.openzeppelin.com/v2/#/settings/api-keys/new). + +![Create API credentials](/defender/guide-factory-api.png) + +Now, navigate to the [Secrets](https://defender.openzeppelin.com/v2/#/settings/secrets) page in Defender and create a new secret with the name `API_KEY` and paste in the API key. Create another secret with the name `API_SECRET` and paste in the API secret. These secrets will be used by the Action securely. + +![Save API credentials](/defender/guide-factory-secrets.png) + +## Create the Action + +Navigate to the [Action creation page](https://defender.openzeppelin.com/v2/#/actions/automatic/new?), enter a name, and select `Webhook` as trigger. Then, paste the following Action code and save it: + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); + +exports.handler = async function (event) + const creds = { + apiKey: event.secrets.API_KEY, + apiSecret: event.secrets.API_SECRET, + + const client = new Defender(creds); + + const payload = event.request.body + const matchReasons = payload.matchReasons + const newCloneAddress = matchReasons[0].params._clone + const newCloneAbi = `[ + + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "ValueChanged", + "type": "event" + }, + + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + + ], + "stateMutability": "view", + "type": "function" + }, + + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]` + // Add new clone contract + await client.proposal.addContract( + network: 'sepolia', + address: newCloneAddress, + name: `Clone ${newCloneAddress`, + abi: newCloneAbi, + }) +} +``` + +![Create Action](/defender/guide-factory-create-action.png) + +The Action is now ready to be triggered by a Monitor. + + +Manually triggering this Action will be raise an error, since the Action relies on data supplied by a Monitor (such as the address of the newly deployed clone contract address). + + +## Create the Monitor + +This Monitor will watch for an event emitted by the factory contract signaling that a new clone has been created. Navigate to the [Monitor creation page](https://defender.openzeppelin.com/v2/#/monitor/new/custom), choose a name, risk category, and select the Factory contract (add the factory if it’s not already added). + +![Monitor General Information](/defender/guide-factory-monitor-general-information.png) + +Leave `Transaction Filters` as it is, and continue to the `Events` tab. Here, select the event name for clone creation and leave the event parameters blank to catch all emitted events. + +![Monitor Events](/defender/guide-factory-monitor-events.png) + +Lastly, open the `Alerts` section and select the Action created in the previous step within the `Execute an Action` dropdown. Feel free to add any other setting, like notifications, and save the Monitor. + +![Monitor Alerts](/defender/guide-factory-monitor-alerts.png) + +As with any action, the triggering of this Monitor will be recorded in the [Logs](/defender/logs). + +## Test run + +To test the set up, navigate to [Transaction Proposals](https://defender.openzeppelin.com/v2/#/transaction-proposals/new?) to manually create a clone through the factory. Select the factory contract, and call the function that creates a clone with any parameters needed. + +![Transaction Proposal to create clone](/defender/guide-factory-create-clone.png) + +Then, execute this this transaction with your preferred approval process, like a Relayer or EOA wallet. Head over to run history of the Action to verify it was triggered by the Monitor, adding the clone contract address to Defender. + +![Action Run History](/defender/guide-factory-action-run-history.png) + +## Create Monitor for clones + +Now that you have a clone contract to serve as a template for all future clone contracts, it’s time to create a Monitor for them. Navigate to the [Monitor creation page](https://defender.openzeppelin.com/v2/#/monitor/new/custom), choose a name, risk category, and select the clone contract. + +Aditionally, feel free to add any other filters for transactions, events, and functions, or notifications. Save the Monitor and observe the logs/notifications to verify that the Monitor is working as expected. + +![Monitor Clones](/defender/guide-factory-monitor-clones.png) + +## Automatically add clones to Monitor + +With the last Monitor, you can update the Action to add any newly created contract to the list of addresses being monitored by the Monitor. Update the Action code with the following code, replacing `monitorId` with the ID of the Monitor created in the previous step: + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); + +exports.handler = async function (event) + const creds = { + apiKey: event.secrets.API_KEY, + apiSecret: event.secrets.API_SECRET, + + const client = new Defender(creds); + + const payload = event.request.body + const matchReasons = payload.matchReasons + const newCloneAddress = matchReasons[0].params._clone + const newCloneAbi = `[ + + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "ValueChanged", + "type": "event" + }, + + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + + "inputs": [], + "name": "retrieve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + + ], + "stateMutability": "view", + "type": "function" + }, + + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "store", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ]` + // Add new clone contract + await client.proposal.addContract( + network: 'sepolia', + address: newCloneAddress, + name: `Clone ${newCloneAddress`, + abi: newCloneAbi, + }) + + // Add clone contract to Monitor + const monitorId = 'REPLACE' + const monitor = await client.monitor.get(monitorId) + const subscribedAddresses = monitor.addressRules[0].addresses + subscribedAddresses.push(newCloneAddress) + await client.action.update(monitorId, addresses: subscribedAddresses ) +} +``` + +Now when the Action runs, not only will it add the contract to Defender, it will also add it to the Monitor. + +To verify, execute another test run! + +## References + +* [Actions Documentation](/defender/module/actions) +* [Monitor Documentation](/defender/module/monitor) diff --git a/docs/content/defender/guide/fireblock-defender-integration.mdx b/docs/content/defender/guide/fireblock-defender-integration.mdx new file mode 100644 index 00000000..194d0897 --- /dev/null +++ b/docs/content/defender/guide/fireblock-defender-integration.mdx @@ -0,0 +1,64 @@ +--- +title: Fireblocks integration within Defender +--- + +You can directly submit transactions to Fireblocks from Defender. Fireblocks is a robust asset management solution that utilizes multi-party computation to secure all treasury operations, ensuring enhanced security and efficiency. + +## Pre-requisites + +* If you want leverage Fireblocks within Defender you can contact the OZ team to enable to Fireblocks integration for your account. + +## 1. Generate CSR file +1. To use this feature, navigate to the **Settings** page and click on **Approval Process** in the sidebar. If the Fireblocks integration is enabled for your account, go to the **Integrations** tab, which is located next to the **All Approval Process** tab. + + ![Integration tab](/defender/guide-fireblocks-integration-tab.png) + +2. Click on **Generate new API Key for Fireblocks**. Here, you will need to generate a Certificate Signing Request (CSR), which will be used within the Fireblocks platform to enable this feature and create API keys. + + ![CSR Generation Modal](/defender/guide-fireblocks-csr-modal.png) + This will trigger Defender to generate a public/private key-pair. The CSR is then generated and signed with the private key and securely stored to prevent leakage. + +## 2. Create Fireblocks API user +1. First, you will need to import the CSR within the Fireblocks UI when creating a new API user. Note that the API user will require any role that can _at least_ initiate transactions, e.g. Signer. + + ![Create API user](/defender/guide-fireblocks-add-user.png) + +2. Once the API user has been created and approved by the Fireblocks workspace owner, copy the Fireblocks API key and navigate to the Fireblocks API Keys page. You should see an incomplete API key setup, which you can then edit and complete with the Fireblocks API key. Note that you will not be able to generate a new CSR file unless you complete the setup or delete the previous incomplete one. + + ![API Key generated](/defender/guide-fireblocks-api-key.png) + +## 3. Connect Fireblocks with Defender +1. First, navigate to the **Settings** page subsequently click **Approval Process** in the sidebar, the navigate to the **Integrations** tab. Over here click on the **Paste API Key from Fireblocks**. + + ![Insert API Key Defender](/defender/guide-fireblock-paste-api-key.png) + +2. Insert the Fireblocks API key. + + ![Insert API key](/defender/guide-fireblocks-edit-api-key.png) + + + To submit a transaction to Fireblocks via Defender, ensure the correct permissions are set in Fireblocks, such as the relevant whitelisted addresses and the Transaction Access Policy (TAP). For example, you might need to whitelist the contract address you wish to interact with, as well as ensure that the newly created API user is allowed to interact with the relevant account and vaults (defined in the TAP). + + +## 4. Create Approval Process + +### Pick a Fireblocks Wallet from the List +You can pick a Fireblocks wallet from the list of available wallets by just providing the Fireblocks API key. We will attempt to fetch the list of available vaults and wallets from Fireblocks. + +![Create Defender Approval Process](/defender/guide-fireblocks-approval-process-automatic.png) + +### Manually Add a Fireblocks Wallet +In some rare cases you might not see your wallets in the list that is automatically fetched from Fireblocks. In that case you can select the `Manual` option and type in the required information manually. + +![Create Defender Approval Process](/defender/guide-fireblocks-approval-process-manual.png) + +To get your ***Vault ID***, head to Fireblocks console, click on the vault you are interested in and copy the ID (last number) from the URL. + +![Vault ID](/defender/guide-fireblocks-vault-id.png) + +To get your ***Asset Wallet Address***, head to Fireblocks console, click on the asset you are interested in and copy the address (starts with 0x). + +![Asset Wallet Address](/defender/guide-fireblocks-asset-wallet-address.png) + +## 5. Approve or Reject a Transaction +Note, Defender will not allow you to approve or reject a transaction from the UI. This is only possible via the Fireblocks mobile app or console. diff --git a/docs/content/defender/guide/forked-network.mdx b/docs/content/defender/guide/forked-network.mdx new file mode 100644 index 00000000..3db962ff --- /dev/null +++ b/docs/content/defender/guide/forked-network.mdx @@ -0,0 +1,104 @@ +--- +title: Deploy a smart contract on a forked network +--- + +Defender empowers you to harness your customized network forks for deploying and testing smart contracts, along with associated configurations of, for example, actions, monitors, and workflows. This guide will lead you through the steps of deploying a smart contract on a forked network and interacting with it. + +## Pre-requisites + +* OpenZeppelin Defender account. + +## 1. Configure your forked network + +You will setup a forked network on [Phalcon](https://phalcon.xyz) and add this network to Defender. To configure a forked network, follow these steps: + +1. Register an account on [Phalcon](https://phalcon.xyz) and create a new fork using Ethereum mainnet as the source network. +2. Ensure anti-replay protection is activated to use a distinct chain ID for your fork, preventing conflicts with public chain IDs. + + ![Phalcon create a fork](/defender/tutorial-forked-network-phalcon-create.png) +3. Copy the RPC URL and Explorer URL (this can be found under 'Scan') of your forked network. You will need it to add the network to Defender. + + ![Phalcon dashboard](/defender/tutorial-forked-networks-phalcon-dashboard.png) +4. Open [Defender Forked Networks](https://defender.openzeppelin.com/v2/#/settings/networks/forks) in a web browser. +5. Click on **Add Forked Network**. + + ![Forked Networks landing page](/defender/tutorial-forked-networks-intro.png) +6. Enter the details of your forked network which can be found in your Phalcon dashboard. +7. Click on **Save**. + + ![Forked Networks added network](/defender/tutorial-forked-networks-create.png) + + +You may use any provider to fork a network, such as [Conduit](https://conduit.xyz). However, we recommend using Phalcon as it is free and easy to use. + + +## 2. Configure the deploy environment + +You will setup a deploy environment for the forked network you just added to Defender. To configure a deploy environment, follow these steps: + +1. Open [Defender Deploy](https://defender.openzeppelin.com/v2/#/deploy) in a web browser. +2. Click on **Setup** for your production environment (or setup a test environment if your network is forked from a testnet). + + ![Deploy landing page](/defender/tutorial-forked-networks-deploy-intro.png) +3. From the network dropdown, select the forked network you just added. + + ![Delpoy wizard step 1](/defender/tutorial-forked-networks-deploy-wizard-step1.png) +4. Click on **Next** to continue. +5. When asked to provide a block explorer API key, click on **Skip this step** as it’s **not possible to use block explorer API keys for forked networks**. + + ![Delpoy wizard step 2](/defender/tutorial-forked-networks-deploy-wizard-step2.png) +6. On step 3 of the deploy wizard, create a new Relayer from which your deploy transaction will originate by clicking on **Create Relayer** from the dropdown menu. + + ![Delpoy wizard step 3](/defender/tutorial-forked-networks-deploy-wizard-step3.png) +7. Lastly, click on **Skip this step** when asked to select an upgrade approval process. **Currently, upgrades are not supported for forked networks**. +8. Make sure to copy the generated team API keys and store them in a safe place. You will need them to interact with your deploy environment. + +Your deploy environment is now setup! + + +You should fund the relayer account with enough ETH to cover the gas costs of your deploy transaction. Most providers have a faucet that you can use to fund your relayer account. For Phalcon, you can find this on the dashboard. + + +## 3. Deploy a smart contract on a forked network + +You will deploy a smart contract on the forked network you just added to Defender. To deploy a smart contract, follow these steps: + +1. Setup a JavaScript project and install the [defender-sdk-deploy-client](https://www.npmjs.com/package/@openzeppelin/defender-sdk-deploy-client) NPM package. Alternatively, you can use the [defender-sdk delpoy example script](https://github.com/OpenZeppelin/defender-sdk/blob/main/examples/deploy-contract/index.js) provided in the OpenZeppelin Defender SDK repository. +2. The deployment code will look something like this: + + ```js + const config = await client.deploy.getDeployApprovalProcess('mainnet-fork'); + console.log(config); + +const deployment = await client.deploy.deployContract( + contractName: 'Box', + contractPath: 'contracts/Box.sol', + network: 'mainnet-fork', + artifactPayload: JSON.stringify(artifactFile), + licenseType: 'MIT', + verifySourceCode: true, + // Only provide the `salt` if you wish to use `CREATE2`. Otherwise, omit this field to use `CREATE`. + salt: "a-unique-salt" +); + +const deploymentStatus = await client.deploy.getDeployedContract(deployment.deploymentId); +console.log(deploymentStatus); +``` +. Run the script to deploy the contract. **Note** that providing a `salt` will deploy the contract using `CREATE2`. Otherwise, the contract will be deployed using the `CREATE` opcode. Visit the documentation for more information on the [caveats of deployment](https://docs.openzeppelin.com/defender/tutorial/deploy#deploy-caveat). +. Once deployed, you can track the deployment status on the [Defender Deploy dashboard](https://defender.openzeppelin.com/v2/#/deploy/environment/production). + +## Next steps + +Congratulations! You have successfully deployed a smart contract on a forked network. If you have provided a `blockExplorerUrl`, you can verify the transaction on the block explorer of your forked network. + + +After deploying a contract, we recommend creating a Monitor and setting up Actions on Defender. Learn how to setup a Monitor [here](/defender/tutorial/monitor), and use Actions with its tutorial [here](/defender/tutorial/actions). + + +## References + +* [Deploy Documentation](/defender/module/deploy) +* [Actions Documentation](/defender/module/actions) +* [Monitor Documentation](/defender/module/monitor) +* [Phalcon](https://phalcon.xyz) +* [Conduit](https://conduit.xyz) diff --git a/docs/content/defender/guide/meta-tx.mdx b/docs/content/defender/guide/meta-tx.mdx new file mode 100644 index 00000000..f248a4b6 --- /dev/null +++ b/docs/content/defender/guide/meta-tx.mdx @@ -0,0 +1,518 @@ +--- +title: Relaying gasless meta-transactions with a web app +--- + +Gasless meta-transactions offer users a more seamless experience on the blockchain, potentially eliminating the need to spend money on gas fees for every interaction. This method allows users to sign a transaction for free and have it securely executed by a third party, with that party paying the gas to complete the transaction. + +Defender provides a seamless experience and secure way to implement gasless meta-transactions using Relayers. These Relayers handle sending transactions on behalf of users, eliminating the need for users to manage private keys, transaction signing, nonce management, gas estimation, and transaction inclusion. + +This demo app showcases how to implement meta-transactions using not just ERC-2771, but also explores other gasless transaction standards: + +* ERC-2771: Secure Native Meta Transactions: This [demo app](https://github.com/OpenZeppelin/workshops/tree/master/25-defender-metatx-api) implements meta-transactions using [ERC2771Forwarder](https://docs.openzeppelin.com/contracts/api/metatx#ERC2771Forwarder) and [ERC2771Context](https://docs.openzeppelin.com/contracts/api/metatx#ERC2771Context) to separate `msg.sender` from the Relayer’s address. All the user needs to do is sign a message using the account they would like to issue the transaction from. The signature is formed for the target contract and the data of the desired transaction, using the user’s private key. This signing happens off-chain and costs no gas. The signature is passed to the Relayer so it can execute the transaction for the user (and pay the gas). +* ERC-2612: Permit Function: This standard introduces a method for enabling gasless token approvals in ERC-20 tokens. Users can grant spending permission to a relayer service by signing a message instead of directly paying gas fees for the traditional "approve" function. This allows the relayer to handle token approvals on the user’s behalf. +* ERC-3009: Transfer with Authorization: This standard facilitates gasless token transfers through off-chain authorizations. Users sign messages authorizing specific token transfers, which can then be submitted to the blockchain by anyone, including a relayer service. + +A gasless meta-transaction relay can be easily and securely implemented using Defender with [Relayers](/defender/module/relayers), which allow you to send transactions easily without needing to manage private keys, transaction signing, nonce management, gas estimation, and transaction inclusion. + +## Pre-requisites + +* OpenZeppelin Defender account. +* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) and [Yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) installed + +## 1. ERC-2771: Secure Native Meta Transactions + +You can view the live [demo app](https://defender-metatx-workshop-demo.openzeppelin.com/) here. It accepts registrations directly if the user has the available funds to pay for the transaction, otherwise the data is sent as a meta-transaction. + +In the example code, the functionality of the [`SimpleRegistry` contract](https://github.com/OpenZeppelin/workshops/blob/master/25-defender-metatx-api/contracts/SimpleRegistry.sol) is to take a string and store it. The contract’s [meta-transaction implementation](https://github.com/OpenZeppelin/workshops/blob/master/25-defender-metatx-api/contracts/Registry.sol) achieves the same result by decoupling the signer from the sender of the transaction. + +When comparing the code, note the meta-transaction’s use of `_msgSender()` as opposed to the SimpleRegistry’s use of `msg.sender`. By extending from `ERC2771Context` and `ERC2771Forwarder`, the contract becomes meta-transaction capable. + + +All OpenZeppelin contracts are compatible with the use of `_msgSender()`. + + +The second fundamental change between the two contracts is the need for the meta-transaction contract ([Registry](https://github.com/OpenZeppelin/workshops/blob/master/25-defender-metatx-api/contracts/Registry.sol)) to specify the address of the trusted forwarder, which in this case is the address of the `ERC2771Forwarder` contract. + +### 1.1 Configure the project + +First, fork the repository and navigate to the directory for this guide. There, install the dependencies with `yarn`: + +``` +$ git clone https://github.com/openzeppelin/workshops.git +$ cd workshops/25-defender-metatx-api/ +$ yarn +``` + +Create a `.env` file in the project root and supply an API key and secret from the [API keys page](https://defender.openzeppelin.com/v2/#/settings/api-keys/new). A private key will be used for local testing but the Relayer is used for actual contract deployment. + +``` +PRIVATE_KEY="0xabc" +API_KEY="abc" +API_SECRET="abc" +``` + +### 1.2 Create Relayer + +Run the Relayer creation script, which will use the Defender API parameters in the `.env` file: + +``` +$ yarn create-relay +``` + +The Relayer is created using the `defender-sdk` package: + +```jsx +// ... +const client = new Defender(creds); + +// Create Relayer using Defender SDK client. +const requestParams = + name: 'MetaTxRelayer', + network: 'sepolia', + minBalance: BigInt(1e17).toString(), +; + +const relayer = await client.relay.create(requestParams); +// ... +``` + +After creating it, the script will fetch the Relayer ID and create an API key and secret set to send transacitons via it. The Relayer ID is automatically stored in the `relayer.json` file, and its API paramteres in the `.env` file. + +### 1.3 Compile the Contract Using Hardhat + +Within the `contracts` directory, you can find the both the `SimpleRegistry.sol` and `Registry.sol` contracts. The former contract contains the meta-transaction functionality, as you can see here: + +```jsx +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +import "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; + +contract Registry is ERC2771Context + event Registered(address indexed who, string name); + + mapping(address => string) public names; + mapping(string => address) public owners; + + constructor(ERC2771Forwarder forwarder) // Initialize trusted forwarder + ERC2771Context(address(forwarder)) { + + + function register(string memory name) external + require(owners[name] == address(0), "Name taken"); + address owner = _msgSender(); // Changed from msg.sender + owners[name] = owner; + names[owner] = name; + emit Registered(owner, name); + +} +``` + +Run `npx hardhat compile` to compile it for deployment. + +### 1.4 Deploy Using Relayer + +You can easily deploy a compiled smart contract without handling a private key by using the Relayer client from the [`defender-sdk`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) package. + +The `deploy.js` script pulls the Relayer’s credentials from the local `.env` file along with the artifacts for the `Registry` and `ERC2771Forwarder` contracts and uses ethers.js to deploy. The relevant addresses of these contracts are saved to the local file `deploy.json`. + +```jsx +// ... +const creds = + relayerApiKey: process.env.RELAYER_API_KEY, + relayerApiSecret: process.env.RELAYER_API_SECRET, +; +const client = new Defender(creds); + +const provider = client.relaySigner.getProvider(); +const signer = client.relaySigner.getSigner(provider, speed: 'fast' ); + +const forwarderFactory = await ethers.getContractFactory('ERC2771Forwarder', signer) +const forwarder = await forwarderFactory.deploy('ERC2771Forwarder') + .then((f) => f.deployed()) + +const registryFactory = await ethers.getContractFactory('Registry', signer) +const registry = await registryFactory.deploy(forwarder.address) + .then((f) => f.deployed()) +// ... +``` + +Run this script with `yarn deploy`. + +After the contracts are deployed, the Relayer key and secret can be safely deleted; they are not needed unless additional local testing is desired. The contract addresses will be saved in the `deploy.json` file. + +### 1.5 Create Action via API + +The demo app uses an [Action](/defender/module/actions) to supply the necessary logic for telling the Relayer to send a transaction to the `Forwarder` contract, supplying the signer’s address. The Action will get triggered by each call to its webhook from the app. + +Due to the tight relationship between components, the Relayer credentials are securely available to the Action simply by instantiating a new provider and signer. + +The position of the Action here is crucial -- only the Action’s webhook is exposed to the frontend. The Action’s role is to execute the transaction according to the logic assigned to it: if the user has funds, they pay for the transaction. If not, the Relayer pays for the transaction. + +It’s important that the Relayer’s API key and secret are insulated from the frontend. If the Relayer keys were exposed, anyone could potentially use the Relayer to send any transaction they wanted. + +Here is the code for the Action, found in `action/index.js`: + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); +const ethers = require('hardhat') + +const ForwarderAbi = require('../../src/forwarder'); +const ForwarderAddress = require('../../deploy.json').ERC2771Forwarder; + +async function relay(forwarder, request, signature, whitelist) + // Decide if we want to relay this request based on a whitelist + const accepts = !whitelist || whitelist.includes(request.to); + if (!accepts) throw new Error(`Rejected request to ${request.to`); + + // Validate request on the forwarder contract + const valid = await forwarder.verify(request, signature); + if (!valid) throw new Error(`Invalid request`); + + // Send meta-tx through relayer to the forwarder contract + const gasLimit = (parseInt(request.gas) + 50000).toString(); + return await forwarder.execute(request, signature, gasLimit ); +} + +async function handler(event) + // Parse webhook payload + if (!event.request || !event.request.body) throw new Error(`Missing payload`); + const { request, signature = event.request.body; + console.log(`Relaying`, request); + + // Initialize Relayer provider and signer, and forwarder contract + const creds = ... event ; + + const client = new Defender(creds); + + const provider = client.relaySigner.getProvider(); + const signer = client.relaySigner.getSigner(provider, speed: 'fast' ); + const forwarder = new ethers.Contract(ForwarderAddress, ForwarderAbi, signer); + + // Relay transaction! + const tx = await relay(forwarder, request, signature); + console.log(`Sent meta-tx: $tx.hash`); + return txHash: tx.hash ; +} + +module.exports = + handler, + relay, + +``` + +Note that the Action code must include an `index.js` file that exports a handler entrypoint. If the code relies on any external dependencies (such as an imported ABI) it’s necessary to bundle the Action using webpack, rollup, etc. You can create an Action via [Defender](https://defender.openzeppelin.com/v2/#/actions/automatic/new?) or with the [`defender-sdk`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) package. + +Run `yarn create-action` to compile the code and create the Action with the bundled code via the SDK’s `action.create()` method: + +```jsx +// ... +const actionId = await client.action.create( + name: "Relay MetaTx", + encodedZippedCode: await client.action.getEncodedZippedCodeFromFolder('./build/action'), + relayerId: relayerId, + trigger: { + type: 'webhook' + , + paused: false +}); +// ... +``` + +Head to [Defender Actions](https://defender.openzeppelin.com/v2/#/actions/automatic) and copy the Actions’s webhook so that you can test functionality and connect the app to the Action for relaying meta-transactions. + +![Copy Webhook](/defender/guide-meta-tx-copy-webhook.png) + +Save the Action webhook in your `.env` file as `WEBHOOK_URL` and in the /app `.env` file as the `REACT_APP_WEBHOOK_URL`. + +Test the meta-transaction’s functionality with `yarn sign` followed by `yarn invoke`. + +### 1.6 Create Web App + +The key building blocks have been laid, so next it is a matter of crafting a web application that makes use of these components. + +You can see the details of this relationship in the [`register.js`](https://github.com/OpenZeppelin/workshops/blob/master/25-defender-metatx-api/app/src/eth/register.js) file. The user’s transaction request is sent to the Relayer by way of the Action’s webhook, and this executes the Actions’s logic given the parameters supplied by the application. Note that the signer’s nonce is incremented from the transaction. + +```jsx +import ethers from 'ethers'; +import createInstance from './forwarder'; +import signMetaTxRequest from './signer'; + +async function sendTx(registry, name) + console.log(`Sending register tx to set name=${name`); + return registry.register(name); +} + +async function sendMetaTx(registry, provider, signer, name) + console.log(`Sending register meta-tx to set name=${name`); + const url = process.env.REACT_APP_WEBHOOK_URL; + if (!url) throw new Error(`Missing relayer url`); + + const forwarder = createInstance(provider); + const from = await signer.getAddress(); + const data = registry.interface.encodeFunctionData('register', [name]); + const to = registry.address; + + const request = await signMetaTxRequest(signer.provider, forwarder, to, from, data ); + + return fetch(url, + method: 'POST', + body: JSON.stringify(request), + headers: { 'Content-Type': 'application/json' , + }); +} + +export async function registerName(registry, provider, name) + if (!name) throw new Error(`Name cannot be empty`); + if (!window.ethereum) throw new Error(`User wallet not found`); + + await window.ethereum.enable(); + const userProvider = new ethers.BrowserProvider(window.ethereum); + const userNetwork = await userProvider.getNetwork(); + console.log(userNetwork) + if (userNetwork.chainId !== 11155111) throw new Error(`Please switch to Sepolia for signing`); + + const signer = userProvider.getSigner(); + const from = await signer.getAddress(); + const balance = await provider.getBalance(from); + + const canSendTx = balance.gt(1e15); + if (canSendTx) return sendTx(registry.connect(signer), name); + else return sendMetaTx(registry, provider, signer, name); + +``` + +## 2. ERC-2612: Permit Function +EIP-2612 introduces the [permit](https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#ERC20Permit) function, a tool for enabling gasless transactions in ERC-20 tokens. By extending the ERC-20 interface with a method allowing users to modify their allowance via a signed message instead of the approve function, this standard empowers users to approve tokens without directly paying gas fees. This standard enables relayer services to execute transactions on behalf of users by paying gas fees, while the user only needs to sign a message. +``` +function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external +``` +This function modifies the `allowance` of the spender for the owner’s tokens, based on a signed approval. The signature is split into `v`, `r`, and `s` components for verification. + +### 2.1 EIP-712 signing front-end +How it uses EIP-712 for structured data signing: EIP-2612 leverages EIP-712 for creating and signing structured data. This provides a human-readable representation of the data being signed, enhancing security and user experience. Example code: + +```jsx +// ... + const domain = + name: name, + version: '1', + chainId: chainId, + verifyingContract: ERC20_ADDRESS, + ; + + const types = + Permit: [ + { name: 'owner', type: 'address' , + name: 'spender', type: 'address' , + name: 'value', type: 'uint256' , + name: 'nonce', type: 'uint256' , + name: 'deadline', type: 'uint256' , + ] + }; + + const value = + owner: OWNER_ADDRESS, + spender: SPENDER_ADDRESS, + value: amount, + nonce: nonce, + deadline: deadline, + ; + + + const signature = await wallet.signTypedData(domain, types, value); + const sig = ethers.Signature.from(signature); + const recoveredAddress = ethers.verifyTypedData(domain, types, value, signature); + + const request = + owner: OWNER_ADDRESS, + spender: SPENDER_ADDRESS, + amount, + deadline, + v: sig.v, + r: sig.r, + s: sig.s + ; + + return fetch(`$url/relayerForwardMessage`, + method: 'POST', + body: JSON.stringify(request), + headers: { 'Content-Type': 'application/json' , + }); +``` +### 2.2 Relayer service +Create a back-end service to interact with Defender Relayers. The service will initially require the setup of [Defender Relayers](https://docs.openzeppelin.com/defender/manage/relayers). Once configured, it will handle incoming requests from the front-end and forward the signed EIP-712 message to the contract. The service will utilize the Relayers to execute the contract’s permit function, allowing the Relayer to cover gas fees. The service will facilitate token approvals for end-users, enabling subsequent operations with the Relayers, such as transferring tokens to different wallets. +```jsx +import ethers, defender from "hardhat"; + +// ... +const creds = + relayerApiKey: process.env.RELAYER_API_KEY, + relayerApiSecret: process.env.RELAYER_API_SECRET, +; +const client = new Defender(creds); + +const provider = client.relaySigner.getProvider(); +const signer = client.relaySigner.getSigner(provider, speed: 'fast' ); + +const erc20 = await ethers.getContractAt("ERC20Token", CONTRACT_ADDRESS); + +// You can now use these values to call the permit function +// permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) +const tx = await erc20.permit( + request.owner, + request.spender, + request.amount, + request.deadline, + request.v, + request.r, + request.s +); + +await tx.wait(); +console.log("Permit executed!"); + +// Example subsequent operation +const transferTx = await erc20.transferFrom(request.owner, to, request.amount); +await transferTx.wait(); +// ... +``` + +## 3. ERC-3009: Transfer with Authorization +ERC-3009 introduces a standard for gasless token transfers through off-chain authorizations. This standard allows users to sign messages authorizing token transfers, which can then be submitted to on-chain by anyone, through the [Defender Relayers](https://docs.openzeppelin.com/defender/manage/relayers) service. Comparison with EIP-2612 (signing differences): +While EIP-2612 focuses on approvals, ERC-3009 directly authorizes transfers. The key differences are: + +* Purpose: ERC-3009 authorizes specific transfers, while EIP-2612 approves an allowance. +* Flexibility: ERC-3009 doesn’t require EIP-712 for structured data signing, offering more flexibility in message formatting. +* Time Window: ERC-3009 includes validAfter and validBefore parameters, allowing for more precise control over when the authorization can be executed. + +The function definition: +``` +function transferWithAuthorization( + address from, + address to, + uint256 value, + uint256 validAfter, + uint256 validBefore, + bytes32 nonce, + uint8 v, + bytes32 r, + bytes32 s +) external +``` + +### 3.1 EIP-712 signing front-end +Similar to ERC-2612, you can use the EIP-712 format to sign messages on the front-end as the end user. While ERC-3009 offers more flexibility for front-end message signing, this example adheres to the EIP-712 standard. Example code: + +```jsx + //... + + const validAfter = Math.floor(Date.now() / 1000); // Now + const validBefore = validAfter + 3600; // 1 hour from validAfter + const value = ethers.parseEther("10"); // Amount to transfer + const nonce = ethers.randomBytes(32); + + const domain = + name: name, + version: '1', + chainId: chainId, + verifyingContract: ERC20_ADDRESS, + ; + + const types = + TransferWithAuthorization: [ + { name: 'from', type: 'address' , + name: 'to', type: 'address' , + name: 'value', type: 'uint256' , + name: 'validAfter', type: 'uint256' , + name: 'validBefore', type: 'uint256' , + name: 'nonce', type: 'bytes32' , + ] + }; + + const valueToSign = + from: FROM_ADDRESS, + to: TO_ADDRESS, + value: value, + validAfter: validAfter, + validBefore: validBefore, + nonce: nonce, + ; + + + const signature = await wallet.signTypedData(domain, types, valueToSign); + const sig = ethers.Signature.from(signature); + const request = + from: FROM_ADDRESS, + to: TO_ADDRESS, + value, + validAfter, + validBefore, + nonce, + v: sig.v, + r: sig.r, + s: sig.s + ; + + return fetch(`$url/relayerForwardMessage`, + method: 'POST', + body: JSON.stringify(request), + headers: { 'Content-Type': 'application/json' , + }); +``` +### 3.2 Relayer service +Create a back-end service to interact with Defender Relayers. The service will initially require the setup of Defender Relayers. Once configured, it will handle incoming requests from the front-end and forward the signed messages to the contract. The service will utilize the Relayers to execute the contract’s `transferWithAuthorization` function, allowing the Relayer to cover gas fees. The service will facilitate the transfer of tokens for the end-users. +```jsx +import ethers, defender from "hardhat"; + +// ... +const creds = + relayerApiKey: process.env.RELAYER_API_KEY, + relayerApiSecret: process.env.RELAYER_API_SECRET, +; +const client = new Defender(creds); + +const provider = client.relaySigner.getProvider(); +const signer = client.relaySigner.getSigner(provider, speed: 'fast' ); + +const erc20 = await ethers.getContractAt("ERC20Token", CONTRACT_ADDRESS); + +const tx = await erc20.transferWithAuthorization( + request.from, + request.to, + request.value, + request.validAfter, + request.validBefore, + request.nonce, + request.v, + request.r, + request.s +); + +await tx.wait(); +console.log("TransferWithAuthorization executed!"); +// ... +``` + +## Try the app + +Install the necessary dependencies and run the app. + +``` +$ cd app +$ yarn +$ yarn start +``` + +1. Open app: [http://localhost:3000/](http://localhost:3000/) +2. Change to Sepolia network in Metamask +3. Enter a name to register and sign the meta-transaction in Metamask +4. Your name will be registered, showing the address that created the meta-transaction and the name. + +Use the frontend to see it working for yourself! Compare what happens when you sign the registry with an account that has funds, and then try it with an account that has a zero ETH balance. + +## References + +* [Demo repo - Meta-Transaction Name Registry](https://github.com/OpenZeppelin/workshops/tree/master/25-defender-metatx-api) +* [Documentation - Meta Transactions](https://docs.openzeppelin.com/contracts/api/metatx) diff --git a/docs/content/defender/guide/private-network.mdx b/docs/content/defender/guide/private-network.mdx new file mode 100644 index 00000000..be4e769f --- /dev/null +++ b/docs/content/defender/guide/private-network.mdx @@ -0,0 +1,91 @@ +--- +title: Adding a complete Private Network +--- + +Private Networks allow you to customize your account by adding compatible mainnets and testnets. You can then use them as any other supported network to deploy, monitor, and manage smart contracts on those networks. This guide will lead you through the steps of adding a Private Network with a subgraph and Safe contracts. + +## Pre-requisites + +* OpenZeppelin Defender account. + +## 1. Configure Private Network + +As an example, this guide uses [Tenderly](https://tenderly.co/) to create a network to use. Follow these steps: + +1. Regsiter an account on Tenderly and create a fork network from Ethereum mainnet. Toggle the custom chain ID and set it to a unique value to prevent conflicts with public chain IDs. + + ![Tenderly create network](/defender/guide-tenderly-private-network.png) +2. Copy the network RPC and go to the [Private Network page on Defender](https://defender.openzeppelin.com/v2/#/settings/networks/private/new). Fill and submit the form with the network information, leaving the optional fields on blank (which will be configured in the next steps): + + ![Configure network on Defender](/defender/guide-configure-private-network.png) + +## 2. Deploy Safe contracts + +With the Private Network created, you can now deploy the Safe contracts, which can be used for multisigs or CREATE2 deployments. Follow these steps: + +1. Clone the `safe-smart-account` repository and install the dependencies: + + ``` + git clone https://github.com/safe-global/safe-smart-account && cd safe-smart-account && npm install + ``` +2. Create a new wallet, copy its mnemonic, and paste it in the `.env` file alongside the RPC url of the Private Network in the `NODE_URL` parameter. For example, with [Foundry](https://book.getfoundry.sh/): + + ``` + cast wallet new-mnemonic + ``` +3. Fund the wallet with native tokens (like Ether) via Tenderly. + + ![Fund wallet on Private Network](/defender/guide-fund-private-network-relayer.png) +4. Paste the private key of the wallet and network RPC in the following command to deploy the CREATE2 Deployer contract. Copy the contract address in `contractAddress` for the next step. + + ``` + cast send --rpc-url NETWORK_RPC --private-key PRIVATE_KEY --create 0x604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3 + ``` +5. Replace the `deterministicDeployment` function in `hardhat.config.ts` with the following code, replacing `YOUR_CONTRACT_ADDRESS` and `YOUR_WALLET_ADDRESS`: + + ```jsx + const deterministicDeployment = (): DeterministicDeploymentInfo => + return { + factory: "YOUR_CONTRACT_ADDRESS", + deployer: "YOUR_WALLET_ADDRESS", + funding: BigNumber.from(100000).mul(BigNumber.from(100000000000)).toString(), + signedTx: "0xf8a58085174876e800830186a08080b853604580600e600039806000f350fe7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf326a0b1fd9f4102283a663738983f1aac789e979e220a1b649faa74033f507b911af5a061dd0f2f6f2341ee95913cf94b3b8a49cac9fdd7be6310da7acd7a96e31958d7", + ; + }; + ``` +6. Run the following command to deploy the Safe contracts (don’t worry about the verification errors): ++ +``` +npm run deploy-all custom +``` +7. Navigate to the [`Private Networks` page on Defender](https://defender.openzeppelin.com/v2/#/settings/networks/private) and click on the edit button of the network you created. + + ![Edit Private Network on Defender](/defender/guide-edit-private-network.png) +8. Copy the following addreses from the Safe contracts deployment output and paste them on Defender: + * Safe Master Address: `Safe` + * Safe Proxy Factory Address: `SafeProxyFactory` + * Safe Multi-Send Call-Only Address: `MultiSendCallOnly` + * Safe Create Call Address: `CreateCall` + +## 3. Create subgraph + +Subgraphs on Defender are powered by [TheGraph](https://thegraph.com). In order to create one for a Private Network, the network must be first supported by the TheGraph. [Here’s](https://github.com/graphprotocol/graph-tooling/blob/121843e982c69ffb31aae911431a68a2349ea062/packages/cli/src/protocols/index.ts#L91) the list of supported networks as data sources. Follow these steps to create a subgraph: + +1. Clone the Defender subgraph toolkit repository and install the dependencies: + + ``` + git clone https://github.com/OpenZeppelin/defender-subgraphs && cd defender-subgraphs && yarn + ``` +2. Follow the steps in the [README](https://github.com/OpenZeppelin/defender-subgraphs/blob/main/README.md). +3. Copy the subgraph URL and paste it in the `Subgraph URL` field on the Defender Private Network configuration. + +![Subgraph URL on Defender](/defender/guide-subgraph-private-network.png) + +## Next steps + +Congratulations! You have successfully added a complete Private Network. You can now use it to deploy and test your smart contracts with the Safe contracts and subgraph. + +## References + +* [Safe contracts deployments](https://github.com/safe-global/safe-smart-account#deployments) +* [Defender subgraph toolkit](https://github.com/OpenZeppelin/defender-subgraphs) diff --git a/docs/content/defender/guide/timelock-roles.mdx b/docs/content/defender/guide/timelock-roles.mdx new file mode 100644 index 00000000..50109d58 --- /dev/null +++ b/docs/content/defender/guide/timelock-roles.mdx @@ -0,0 +1,84 @@ +--- +title: How to manage roles of a TimelockController +--- + +Defender allows you to oversee and command contract permissions of any smart contract that uses [AccessControl from OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/api/access#AccessControl). This guide will lead you through the steps of importing a TimelockController contract, creating a proposal, and managing its roles. A `TimelockController` is a smart contract that enforces a delay between when an operation is queued and when it can be executed. This mechanism is commonly used in decentralized governance to increase security and provide transparency, allowing stakeholders to observe and react to changes before they are executed. TimelockController uses the following `AccessControl`` setup: + +* 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). +* 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. + +## Pre-requisites + +* OpenZeppelin Defender account. + +## 1. Creating TimelockController + +First, you need to deploy a contract that implements `TimelockController`. For example, using OpenZeppelin Contracts 5.0: + +```solidity +//SPDX-License-Identifier: Unlicense +pragma solidity ^0.8.18; + +import "@openzeppelin/contracts/governance/TimelockController.sol"; + +contract Timelock is TimelockController + constructor(uint256 minDelay, address[] memory proposers, address[] memory executors) TimelockController(minDelay, proposers, executors, msg.sender) { +} +``` + +This contract will grant the **Admin** role to the deployer, and the **Proposer** and **Executor** roles to the accounts passed as arguments. If you want to grant the **Proposer** and **Executor** roles to the same account, you can pass the same address twice. After deploying the contract, copy its address and proceed to the next step. + +## 2. Importing to Defender + +Navigate to the [Address Book on Defender](https://defender.openzeppelin.com/v2/#/address-book/new) and add your contract with its address and network. If the contract is verified, Defender will automatically pick up the ABI. Otherwise, you need to [paste the ABI](https://gist.github.com/mverzilli/a35ab1b5bd7039167cc9270e9fd60632) manually. + +## 3. Creating a proposal + +[Defender Transaction Proposals](/defender/module/transaction-proposals) allows you to create and manage proposals. Proposals in turn can directly execute actions on schedule delayed execution of functions through the TimelockController contract. In order to create a new proposal, navigate to the [Transaction Proposal creation page](https://defender.openzeppelin.com/v2/#/transaction-proposals/new?) and select your imported `TimelockController`. + +Then, select the `schedule` function from the functions dropdown and fill the information about the proposal you want to execute. For example: + +![Proposal Data](/defender/guide-timelock-roles-schedule.png) + +Finally, select the approval process to send this proposal. This depends on who is the proposal of your `TimelockController` contract. For this guide, the proposer is an EOA wallet. With Defender, you can create an approval process that is connected to your EOA and send this proposal with it. + +![Proposer](/defender/guide-timelock-proposer.png) + +You can then execute the proposal by creating another Transaction Proposal and selecting the `execute` function with the previous proposal data. + +## 4. Granting a role + +Now, you can use [Defender Access Control](/defender/module/access-control) to grant or revoke roles of your `TimelockController` contract. Navigate to [Access Control](https://defender.openzeppelin.com/v2/#/access-control/contracts) and select your imported `TimelockController` contract. + +![Timelock Controller Roles](/defender/guide-timelock-roles.png) + +In order to grant someone a role, you need to have access to the address that holds admin power over the role. For example, with the DEFAULT_ADMIN_ROLE, you can grant or revoke any role. In this case, you can use an EOA that holds the DEFAULT_ADMIN_ROLE to grant the PROPOSER_ROLE to another address. + +First, expand the dropdown of `PROPOSER_ROLE` and paste the address to grant the role to. + +![Receiver of role](/defender/guide-timelock-role-receiver.png) + +Then, scroll down and use an approval process that holds the DEFAULT_ADMIN_ROLE to send the transaction, like an EOA wallet. Click on `Save Changes`, which will send the transaction to your wallet. + +![Grant a role](/defender/guide-timelock-roles-grant.png) + +On the right side of the page, you will see that the transaction was executed and the role was granted to the address you selected. + +![Granted a role](/defender/guide-timelock-roles-granted.png) + +## Revoking a role + +To revoke a role, you need do the same steps but unselect the address to revoke the role from in the dropdown. + +![Remover of role](/defender/guide-timelock-role-remover.png) + +Then, use the same approval process and save the changes. The transaction should be executed and the role revoked, as confirmed on the right side of the page. + +![Revoked role](/defender/guide-timelock-role-revoked.png) + +## References + +* [Transaction Proposals Documentation](/defender/module/transaction-proposals) +* [Access Control Documentation](/defender/module/access-control) +* [TimelockController Documentation](https://docs.openzeppelin.com/contracts/governance#timelock) diff --git a/docs/content/defender/guide/upgrade-actions-dependencies.mdx b/docs/content/defender/guide/upgrade-actions-dependencies.mdx new file mode 100644 index 00000000..470be4c3 --- /dev/null +++ b/docs/content/defender/guide/upgrade-actions-dependencies.mdx @@ -0,0 +1,22 @@ +--- +title: Upgrading Actions Dependencies +--- + +Actions must be kept updated with the latest Node.js runtime and dependencies versions to ensure they run in an up-to-date and secure environment. Occasionally, Node.js and dependencies versions get deprecated on Defender, which means that the Actions running on those Node.js versions (and related dependencies) must be upgraded to the latest ones to ensure they continue to function as expected. + +## Upgrade Process + +When there is a major Node.js version deprecation, the Defender team notifies users about this event and sets a deadline for upgrading actions to the latest dependency versions. If no action is taken by the deadline, Defender automatically upgrades the actions on behalf of users. + + +We encourage users to make the upgrade process by themselves as the automatic upgrade might introduce breaking changes in dependencies. + + +## How to Upgrade Actions Runtimes + +1. Check the Action [dependencies latest versions](https://docs.openzeppelin.com/defender/module/actions#environment) and search for any breaking changes in your Action code. +2. Make any necessary code changes. +3. Test the Action code. A safe approach to do this: + a. Create a new Action with the same code using the target dependencies version and run it to verify that it works as expected. + b. If your action sends relayer transactions using [defender-sdk](https://docs.openzeppelin.com/defender/sdk) (or any other Defender legacy package), validate that it works by connecting a testnet relayer with your action. +4. Once you have verified that the code and dependencies are compatible, upgrade the Action dependencies to the latest version. diff --git a/docs/content/defender/guide/usage-notification.mdx b/docs/content/defender/guide/usage-notification.mdx new file mode 100644 index 00000000..59ffee2d --- /dev/null +++ b/docs/content/defender/guide/usage-notification.mdx @@ -0,0 +1,63 @@ +--- +title: Manage custom and system usage notifications +--- + +Defender enables you to receive notifications when your usage exceeds a certain threshold. This guide will walk you through the steps of setting up custom usage notifications and managing system usage notifications. Defender currently tracks usage metrics such as: + +* Monitor Alerts: The number of times your monitors have triggered an alert. +* Action Runs: The number of times your actions have been executed. +* Relayer Transactions: The number of transactions your relayers have processed. +* Code Inspector Reports: The number of reports generated by Code Inspector. +* Transaction Proposals: The number of proposals created. + +In addition, Defender provides default system usage notifications triggered when your usage exceeds 90% and 100%, respectively. For users with overages or metered billing enabled, only system usage notifications are triggered when usage surpasses 200%. These notifications are sent to the email addresses of all tenant administrator users. + +## Pre-requisites + +* OpenZeppelin Defender account. + +## 1. Configure a custom usage notification + +You will configure a custom usage notification for when your Action Runs usage exceeds 75% of your quota. You will set up an alert to be sent to all tenant administrators when this happens. To configure a custom usage notification, follow these steps: + +1. Open [Defender Billing Settings](https://defender.openzeppelin.com/v2/#/billing/settings) in a web browser. +2. Click on **Create Notification** in the top-right corner of the Notifications section. + + ![Billing Settings](/defender/guide-usage-notifications-all.png) + +3. In the **Create Notification** dialog: + * Enter a name for the notification. For example, `Action Runs Usage 75%`. + * Select the usage metric you want to monitor. In this case, select `Action Runs`. + * Set the threshold to 75. Below, you will see the current usage and the absolute value of the threshold for the selected metric. + * Select the "All Tenant Administrators" notification channel. For now, your selection is limited to ***Slack*** and ***Email***. + + ![Usage Notification Create Dialog](/defender/guide-usage-notifications-create.png) + +At any time, you may pause or unpause usage notifications. You can do this by toggling the switch next to the notification name. You can also manage your notifications by clicking on the three dots next to the notification threshold. From the dropdown menu, you can edit or delete the notification. System usage notifications can only be paused. + +![Manage Notification](/defender/guide-usage-notifications-edit-menu.png) + +## 2. Pause a system usage notification + +You will pause a system usage notification for when Monitor Alerts usage exceeds 90% (or 200% for users with overages enabled) of your quota. If overages are enabled for your account, you will see the **Surpassing** notifications. Otherwise, you will only see the **Nearing** and **Exceeding** notifications. + +To pause a system usage notification, follow these steps: + +1. Open [Defender Billing Settings](https://defender.openzeppelin.com/v2/#/billing/settings) in a web browser. +2. Filter the notifications table by selecting the "System" tab. +3. Locate the **Nearing Monitor Alerts** or **Surpassing Monitor Alerts** notification. +4. Toggle the switch to pause the notification. + +![Nearing Monitor Alerts](/defender/guide-usage-notifications-system-unmetered.png) + +![Surpassing Monitor Alerts](/defender/guide-usage-notifications-system.png) + + +If a user disabled system notifications from their profile, they will also be opted out of all system notifications, regardless of their individual paused state. + + +![Disable System Notifications](/defender/guide-profile-disable-system-notifications.png) + +## Next steps + +Congratulations! You have successfully set up a custom usage notification. Once your Action Runs usage exceeds the 75% threshold, all your tenant administrators will receive an email notification. In addition, you have paused the default system usage notification for when Monitor Alert exceeds the 90% (or 200% for users with overages enabled) threshold. Therefore, your tenant administrators will no longer receive a system email notification when this happens. diff --git a/docs/content/defender/index.mdx b/docs/content/defender/index.mdx new file mode 100644 index 00000000..db9ba551 --- /dev/null +++ b/docs/content/defender/index.mdx @@ -0,0 +1,130 @@ +--- +title: Defender +--- + + + + +New sign-ups were disabled on June 30, 2025. Until the final shutdown on July 1, 2026, Defender will remain fully operational while we focus on the open source versions of tools like Relayers and Monitor. Detailed migration guides are coming soon to help you transition seamlessly to open source. + +[Read more](https://blog.openzeppelin.com/doubling-down-on-open-source-and-phasing-out-defender) + + + +OpenZeppelin Defender is a mission-critical developer security platform to **code**, **audit**, **deploy**, **monitor**, and **operate** blockchain applications with confidence. + +Integrating directly into the developer workflow, Defender makes it easy and fast for developers and operators to prevent and fix security issues pre and post-deployment. + +## Subscriptions +Defender offers flexible subscriptions to match your team’s needs and support your project at any scale. + +* **Builder (Free)** - Suitable for individuals and small projects that are getting started on testnets and need access to all basic features with limited quotas. +* **Professional** - For mature projects running on mainnets that need access to premium features, higher quotas and metered billing, as well as access to OpenZeppelin support and SLA. +* **Enterprise** - For large projects with higher volumes that need a custom plan to meet their project needs, with higher quotas, access to all premium and security features, and a dedicated support channel. + +## Modules + +Defender modules work seamlessly together, providing users powerful features and a superior, integrated experience. Learn more about each module by clicking on its card. + + + + Automatic code analysis powered by AI models and tools developed by our security experts. + + + Manage the smart contract audit process and track issues and resolutions. + + + Manage deployments and upgrades to ensure secure releases. + + + Send transactions to the blockchain via Defender automatically. + + + Detect smart contract activity and anomalies through trigger actions and alerts. + + + Create transactions to be executed on-chain. + + + Create automated actions to perform on-chain and off-chain operations. + + + Create a shared repository of user-friendly names for your accounts or contracts. + + + Manage smart contract accounts, roles, and permissions easily. + + +## Available networks +Defender works with most mainnet and testnet networks, as well as local mainnet forks. + +* [**Arbitrum One**](https://arbitrum.io/), [**Arbitrum Nova**](https://nova.arbitrum.io/), **Arbitrum Sepolia**. +* [**Aurora**](https://aurora.dev/) and **Aurora Testnet**. +* [**Avalanche C**](https://docs.avax.network/dapps) and **FUJI C-Chain**. +* [**Base Mainnet**](https://www.base.org/) and **Base Sepolia**. +* [**Binance Smart Chain**](https://docs.binance.org/smart-chain/guides/bsc-intro.html) and **BSC testnet**. +* [**Celo**](https://celo.org/) and **Alfajores**. +* [**Ethereum Mainnet**](https://ethereum.org/en/), **Sepolia** testnet and **Holesky** testnet. +* [**Fantom**](https://fantom.foundation/what-is-fantom-opera/) and **Fantom Testnet**. +* [**Fuse**](https://fuse.io/). +* [**Gnosis Chain**](https://www.gnosis.io/) +* [**Hedera**](https://hedera.com/) and **Hedera Testnet**. +* [**Japan Open Chain**](https://www.japanopenchain.org/en/docs/developer/mainnet) and **Japan Open Chain Testnet**. +* [**Linea Mainnet**](https://linea.build/) and **Linea Sepolia**. +* [**Scroll Mainnet**](https://scroll.io/) and **Scroll Sepolia**. +* [**Mantle Mainnet**](https://www.mantle.xyz/) and **Mantle Sepolia**. +* [**Meld Mainnet**](https://www.meld.com/) and **Meld Testnet**. +* [**Moonbeam**](https://moonbeam.network/), **Moonriver**, and **Moonbase Alpha (Testnet)**. +* [**OP Mainnet**](https://optimism.io/), **OP Sepolia**. +* [**Polygon** (POL)](https://www.polygon.technology/), **Amoy**, [**zkEVM**](https://polygon.technology/polygon-zkevm) and **Polygon Cardona zkEVM testnet** +* [**Scroll Mainnet**](https://scroll.io/) and **Scroll Sepolia**. +* [**Unichain**](https://www.unichain.org/) and **Unichain Sepolia**. +* [**zkSync Era Mainnet**](https://zksync.io/) and **zkSync Era Sepolia**. +* [**Geist Mainnet**](https://www.playongeist.com//) and **Polter Testnet**. +* [**Abstract Mainnet**](https://docs.abs.xyz/overview) and **Abstract Sepolia**. +* [**Peaq Mainnet**](https://www.peaq.network/) and **Peaq Agung**. +* [**Sei**](https://www.sei.io/) and **Sei Testnet (Atlantic-2)**. + +If there is any other network or layer-2 solution you would like to use from Defender, please [reach out to us via the form on Defender](#feedback)! + +### Status +You can check the status of Defender and the supported networks on our [status page](https://status.defender.openzeppelin.com/), where you can also subscribe to receive notifications. If a supported network experiences issues, some features in Defender for that network may not work properly. + +## Integrations +Integrations throughout Defender allow users to connect with other services and tools. Find the list of integrations and more information [here](/defender/integrations). + +## Service Level and Support Agreement +For our [paid subscriptions](https://www.openzeppelin.com/pricing), we’re offering enhanced service reliability, guaranteed uptime, and priority support. You can learn more on our [Service Level and Support Agreement (SLA) page](https://www.openzeppelin.com/service-level-agreement). + +## Feedback + +As a Defender user, your feedback is important! Please provide us feedback by accesing the form on the bottom-right corner of your screen. + +![Feedback form button bottom-right corner](/defender/feedback-button.png) diff --git a/docs/content/defender/integrations.mdx b/docs/content/defender/integrations.mdx new file mode 100644 index 00000000..f736daa6 --- /dev/null +++ b/docs/content/defender/integrations.mdx @@ -0,0 +1,43 @@ +--- +title: Integrations +--- + +Defender seamlessly integrates with your existing tools and workflows, so you can easily secure your project throughout the secure development lifecycle. + +## SDK Plugins +* [**Hardhat**](https://hardhat.org/): Code, deploy and upgrade your Hardhat projects through Defender. +* [**Foundry**](https://getfoundry.sh/): Code your Foundry projects with support of Defender. + +## IDEs +* [**Remix**](https://remix.ethereum.org/): Deploy your Remix contracts using Defender deployment environments through the Defender Remix Plugin. + +## Libraries +* [**OpenZeppelin Contracts**](https://www.openzeppelin.com/contracts): Speed up your smart contract development with security and performance baked in. + +## Continuous Integration +* [**GitHub**](/defender/module/code): Install Code Inspector to maximize security in every project PR. +* [**Defender as Code (DaC)**](/defender/dac): Serverless Framework plugin for automated resource management. + +## Key Management & Transaction Execution +* [**Safe**](https://app.safe.global/): Use Safe multisigs to approve Defender operations. +* [**Fireblocks**](https://www.fireblocks.com/): Use Fireblocks assets to approve Defender operations. +* [**Flashbots**](https://www.flashbots.net/): Send private transactions to prevent MEV and other attack vectors. + +## Notification & Logging +* [**Datadog**](https://www.datadoghq.com/): Push notifications and logs to your Datadog system. +* [**PagerDuty**](https://www.pagerduty.com/): Configure PagerDuty to receive notifications and trigger operations. +* [**Opsgenie**](https://www.atlassian.com/software/opsgenie): Configure Opsgenie to receive notifications for their alert management services. +* [**Slack**](https://slack.com/): Configure Slack channels to receive notifications. +* [**Telegram**](https://telegram.org/): Configure Telegram bots to receive notifications. +* [**Discord**](https://discord.com/): Configure Discord channels to receive notifications. +* **Webhooks**: Configure any Webhook to manage any type of alert with complete flexibility. +* **Email**: Receive alerts via any Email. + +## Threat Detection & Transaction Data +* [**Etherscan**](https://etherscan.io/): Visualize transactions from Defender using Etherscan-based explorers. +* [**Blockscout**](https://www.blockscout.com/): Visualize transactions from Defender using Blockscout-based explorers. + +## Source Code +* [**Github**, window=_black](https://github.com/): Install our [Code Github application](/defender/module/code) to scan your project with every pull request to identify potential vulnerabilities and suggest improvements to enhance your code quality. + +If there is any other integration you would like, please [reach out to us via the form on Defender](#feedback)! diff --git a/docs/content/defender/logs.mdx b/docs/content/defender/logs.mdx new file mode 100644 index 00000000..8e6c8a06 --- /dev/null +++ b/docs/content/defender/logs.mdx @@ -0,0 +1,94 @@ +--- +title: Logs +--- + +Defender generates log trails of every potentially relevant event in the system. This includes manual actions, such as modifying an Action or Monitor, as well as automated activity, such as sending a transaction or firing a notification. Logs can be optionally forwarded to Datadog and Splunk for aggregation or exported. + +## Use cases + +* Track user actions on your team by monitoring sign ins and activity across the application +* Detect potential attacks on your infrastructure from failed sign in attempts +* Follow relayer activity to understand the transactions being sent from your accounts +* Keep an audit trail of all module configuration changes +* Filter by severity, type, user, module, time, and more + +## Log Entries + +Every log entry is structured with the following format: + +* `Module`: The origin of the log entry, such as Monitor, Actions, Workflows, Deploy, etc. +* `Date and Time`: The day and time the log entry was generated with the format `Day Month Year` and 12-hour clock format in the browser local timezone +* `Severity`: The severity level of the log entry, ranging from `trace` to `error` +* `Subject`: The trigger/cause that generated the log. If contract related, the contract name is displayed +* `Event`: Type of acitivty logged. For example, `Created`, `Added`, `Deledated`, `Updated`, etc. +* `Description`: Details on the event. For example, `Contract was updated`, `Address book entry was updated`, etc. +* `User`: Team user that generated the log entry, directly or indirectly + +![Logs Page](/defender/logs.png) + +You can click on each log entry to get a detailed view, which includes the tenant id, user id, activity response, request, log id, timestamp, and more. + +![Logs Detailed View](/defender/logs-detailed.png) + +## Log Forwarding + +Generated logs can be forwarded to Datadog and Splunk, or any other service that supports API Key authentication. You can use this to aggregate all logs across your infrastructure in a single place. + +## Setup Log Forwarding Destination + +To set up a log forwarding destination, open the Logs page and click the 'Send Logs to an External Service' button. + +Form fields: + +* **URL** field is a required field. All logs are forwarded to this URL address using HTTP POSTs. +* **API Header Name** is optional. This is the name of the request header that contains the API Key value. Most log management services require it. Please refer to your log management service documentation to determine if you need it. +* **API Key** is an optional field. API Key is sent with every request for authentication purposes. Most log management services require it. Please refer to your log management service documentation to determine if you need it. +* **Log Types** lets you specify which subset of Defender generated logs you want to have forwarded based on Defender components. +* **Log Levels** lets you specify which subset of Defender generated logs you want to have forwarded based on the log levels. For example debug logs can be used for Autotasks debugging purposes and they can contain data that should not be exported to external systems. + + +In the next section we will cover how to setup Log Forwarding with Splunk and Datadog but it is worth noting that Log Forwarding works with any other service that supports API Key authentication. + + +### Splunk + +Forwarding logs to Splunk is done by using Splunk HEC(HTTP Event Collector). +Documentation for setting up logging with Splunk HEC can be found [here](https://docs.splunk.com/Documentation/Splunk/latest/Data/UsetheHTTPEventCollector). + + +Log Forwarding does not work with Splunk trial accounts because of Splunk internals. + + +Example: + +* **URL**: `https://username.splunkcloud.com/services/collector/raw` +* **API Header Name**: `Authorization` +* **API Key**: `Splunk xxxxxxxxxxxxxxxxxxxxxxxxxxx` + + +`URL` value is dynamic as URL includes account username. + + + +`API Key` should contain `Splunk` prefix. + + +### Datadog + +Documentation for setting up logging on Datadog can be found [here](https://docs.datadoghq.com/logs/). + +Example: + +* **URL**: `https://http-intake.logs.datadoghq.com/api/v2/logs` +* **API Header Name**: `DD-API-KEY` +* **API Key**: `xxxxxxxxxxxxxxxxxxxxxxxxxxx` + + +Datadog uses different sites around the world. For example, if you are relying on an EU server the `URL` field value should be https://http-intake.logs.datadoghq.eu/api/v2/logs + + + +`API Key` value can be obtained from Datadog site by opening `Logs` section from the left menu. +Go to `Cloud` section and select `AWS` provider. +After following those steps, the `API Key` value is displayed in the bottom section of the page. + diff --git a/docs/content/defender/module/access-control.mdx b/docs/content/defender/module/access-control.mdx new file mode 100644 index 00000000..7fecf7f3 --- /dev/null +++ b/docs/content/defender/module/access-control.mdx @@ -0,0 +1,39 @@ +--- +title: Access Control +--- + +Access control allows you to seamlessly oversee and command contract permissions on a grand scale, with the power to view and control access at a granular level. Currently supports [ownable](https://docs.openzeppelin.com/contracts/4.x/access-control#ownership-and-ownable) and [role-based](https://docs.openzeppelin.com/contracts/4.x/access-control#role-based-access-control) access control. + + +At this time, access control is supported across all networks except **Hedera**, **Fantom Testnet** and **Arbitrum Nova**. + + +## Use cases + +* Manage ownership of contracts and proxies, including the ability to transfer ownership to a multisig or DAO +* Identify roles, and remove or give permissions to addresses through multisigs or wallets +* Import contracts from the supported networks, or use contracts deployed through Defender. + +## Contracts + +The main page shows all your contracts from the [Address Book](/defender/module/address-book) that have an access control interface supported. You can also add an existing contract to the Address Book by clicking on the "Add new contract" button. This is a one-time form where you specify the network, address and name. The contract’s ABI will be automatically pulled from the respective block explorers if available. If not, you will have to enter the ABI manually. When a contract is added, the main page will update and show it if applicable. + +![Access control main page](/defender/access-control.png) + +* Not Ownable: contract has no ownership interface +* Ownable: current owner address +* Roles: number of roles found in the contract + +## Contract + +The contract page contains all the information about the contract selected, including the name, environment, network, address, and roles found. Defender performs on-chain syncs with the contract every minute, with the last sync time found on the top right of the page. + +Access control automatically tries to fetch all roles within a contract, but there’s the possibility of missing some. In this case, you can add a role manually by clicking on the "Add new role" button and specifying the name of it. + +When modifying a role, you have to choose which admin address to use. In the case of a multisig as admin, the transaction to modify the role will be wrapped into a proposal, which will be pending until approved by the other signers (if any) and executed. You can see the pending proposals on the right side of the page. + +![Access control contract page](/defender/access-control-contract.png) + + +We provide a quickstart tutorial to change access control roles using Defender. Check it out [here](/defender/tutorial/access-control)! + diff --git a/docs/content/defender/module/actions.mdx b/docs/content/defender/module/actions.mdx new file mode 100644 index 00000000..012493e8 --- /dev/null +++ b/docs/content/defender/module/actions.mdx @@ -0,0 +1,499 @@ +--- +title: Actions +--- + +Actions allow you to implement custom app logic for on-chain and off-chain operations. You can enable automated responses to threats detected by the [Monitor](/defender/module/monitor) and [Workflows](/defender/module/actions#workflows) modules. + +## Use cases + +* Automate smart contract operations +* Execute actions as response to a [Monitor](/defender/module/monitor) alert +* Automate [Workflow](/defender/module/actions#workflows) steps with Actions +* Call external APIs and interact with other smart contracts + +## Actions + +Actions are automated Javascript pieces of code that can be executed via triggers. + +![Automatic Action](/defender/auto-action-general-info.png) + +### Triggers + +The following triggers are supported: + +* **Schedule**: Choose a frequency, and Defender will invoke the function at the specified interval. Note that the specified interval is between two consecutive execution starts, _not_ between the end of one run and the beginning of the next one. Alternatively, it’s possible to specify when the action should run using [cron expressions](https://crontab.cronhub.io/). +* **Webhook**: Defender will create a webhook URL for the action, which will be executed whenever an HTTP POST request is sent to that endpoint. The URL can be regenerated at any time. When invoked, the HTTP request information will be injected into the action, and the HTTP response will include the action run info along with any data returned from the action. + + +When sending requests to the webhook, make sure to include the `Content-Type: application/json` header. We have a strict requirement for this header to be present in the request. Otherwise you will see `415 Unsupported Media Type` error. + + +* **Monitor**: Triggered by a Defender [Monitor](/defender/module/monitor). It will contain a body property with the details for the triggering event that you can use to run custom logic. + +### Environment + +Actions run in a [node 20 environment](https://nodejs.org/dist/latest-v20.x/docs/api/) with 256mb RAM and a 5-minute timeout. The code for each action must be smaller than 5mb in size. For ease-of-use, a set of common dependencies are pre-installed in the environment. The latest action dependenacy version `v2025-01-16` has the following dependencies: + +```jsx +"@datadog/datadog-api-client": "^1.0.0-beta.5", +"@fireblocks/fireblocks-web3-provider": "^1.3.1", +"@gnosis.pm/safe-core-sdk": "^0.3.1", +"@gnosis.pm/safe-ethers-adapters": "^0.1.0-alpha.3", +"@openzeppelin/defender-admin-client": "1.54.6", +"@openzeppelin/defender-autotask-client": "1.54.6", +"@openzeppelin/defender-autotask-utils": "1.54.6", +"@openzeppelin/defender-kvstore-client": "1.54.6", +"@openzeppelin/defender-relay-client": "1.54.6", +"@openzeppelin/defender-sdk": "1.15.2", +"@openzeppelin/defender-sentinel-client": "1.54.6", +"axios": "^1.7.4", +"axios-retry": "3.5.0", +"ethers": "5.5.3", +"fireblocks-sdk": "^2.5.4", +"graphql": "^15.5.1", +"graphql-request": "3.4.0", +"web3": "1.9.0" +``` + + +Defender dependencies that are not under the `@openzeppelin` namespace are now deprecated. Impacted dependencies are defender-admin-client, defender-autotask-client, defender-autotask-utils, defender-kvstore-client, defender-relay-client, defender-sentinel-client + + + +If other dependencies are needed, a JavaScript module bundler, such as rollup or webpack, can be used. Refer to [this sample project](https://github.com/OpenZeppelin/defender-sdk/tree/main/examples/custom-ethers-pkg) to learn how. Contact us to add a dependacy you think other users would find useful! + + + +We love [Typescript](https://www.typescriptlang.org/) in the OpenZeppelin development team, and we hope you do too! If you want to write your actions in TypeScript, you’ll need to first compile them using `tsc` or via your bundler of choice, and then upload the resulting JavaScript code. Unfortunately, we don’t support coding directly in TypeScript in the user interface. All `defender-sdk` packages are coded in TypeScript and are packaged with their type declarations. You can also use the [openzeppelin/defender-sdk-action-client](https://www.npmjs.com/package/@openzeppelin/defender-sdk-action-client) package for type definitions for the event payload. + + +### Runtime Upgrades + +Actions must be kept updated with recent Node.js runtime versions to ensure they run in an up-to-date and secure environment. Occasionally, we enforce runtime upgrades to all Actions to a minimum runtime version. The process consists of: + +1. Sending email notifications before the automatic upgrade. +2. Displaying a UI banner under the actions page to warn about the upcoming automatic upgrade. +3. Making a forum announcement. +4. On the automatic upgrade day, Defender automatically upgrades all action runtimes to the minimum required version. + + +we suggest checking your Actions, making necessary changes, and upgrading in advance to prevent any breaking changes. + + +### Defining code + +#### Handler function + +Your code must export an async `handler` function that will be invoked on each execution of the action. + +```jsx +exports.handler = async function(event) + // Your code here + +``` + +The following interface contains the `event` types injected by Defender when invoking an Action: + +```typescript +export interface ActionEvent + /** + * Internal identifier of the relayer function used by the relay-client + */ + relayerARN?: string; + + /** + * Internal identifier of the key-value store function used by the kvstore-client + */ + kvstoreARN?: string; + + /** + * Internal credentials generated by Defender for communicating with other services + */ + credentials?: string; + + /** + * Read-only key-value secrets defined in the Action secrets vault + */ + secrets?: ActionSecretsMap; + + /** + * Contains a Webhook request, Monitor match information, or Monitor match request + */ + request?: ActionRequestData; + /** + * actionId is the unique identifier of the Action + */ + actionId: string; + /** + * Name assigned to the Action + */ + actionName: string; + /** + * Id of the the current Action run + */ + actionRunId: string; + /** + * Previous Action run information + */ + previousRun?: PreviousActionRunInfo; + +``` + +#### Relayer integration + +If you connect your automatic action to a relayer, then Defender will automatically inject temporary credentials to access the relayer from the action code. Simply pass the event object to the relayer client in place of the credentials: + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); + +exports.handler = async function(event) + const client = new Defender(event); + + // Use relayer for sending txs or querying the network... + +``` + +This allows you to send transactions using the relayer from actions without having to set up any API keys or secrets. Furthermore, you can also use the relayer’s JSON RPC endpoint for making queries to any Ethereum network without having to configure API keys for external network providers. + +We also support [`ethers.js`](https://www.npmjs.com/package/@openzeppelin/defender-relay-client#ethersjs) for making queries or sending transactions via the relayer. To use ethers.js replace the above snippet with this: + +```jsx +const DefenderRelaySigner, DefenderRelayProvider = require('defender-relay-client/lib/ethers'); +const ethers = require('ethers'); + +exports.handler = async function(event) + const provider = new DefenderRelayProvider(event); + const signer = new DefenderRelaySigner(event, provider, { speed: 'fast' ); + // Use provider and signer for querying or sending txs from ethers, for example... + const contract = new ethers.Contract(ADDRESS, ABI, signer); + await contract.ping(); +} +``` + +If you prefer [`web3.js`](https://www.npmjs.com/package/@openzeppelin/defender-relay-client#web3js): + +```jsx +const DefenderRelayProvider = require('defender-relay-client/lib/web3'); +const Web3 = require('web3'); + +exports.handler = async function(event) + const provider = new DefenderRelayProvider(event, { speed: 'fast' ); + const web3 = new Web3(provider); + // Use web3 instance for querying or sending txs, for example... + const [from] = await web3.eth.getAccounts(); + const contract = new web3.eth.Contract(ABI, ADDRESS, from ); + await contract.methods.ping().send(); +} +``` + +#### Monitor invocations + +Actions triggered from a Monitor can have two types of body properties and scheme, depending what type of Monitor triggered the action: + +* In the case of a Defender monitor, the body will contain the [monitor event schema](/defender/module/monitor#monitor-event-schema). + +If the action is written in TypeScript, `BlockTriggerEvent` type from the [defender-sdk-action-client](https://www.npmjs.com/package/@openzeppelin/defender-sdk-action-client) package can be used. + +```jsx +exports.handler = async function(params) + const payload = params.request.body; + const matchReasons = payload.matchReasons; + const sentinel = payload.sentinel; + + // if contract monitor + const transaction = payload.transaction; + const abi = sentinel.abi; + + // custom logic... + +``` + +#### Webhook invocations + +When an action is invoked via a webhook, it can access the HTTP request info as part of the `event` parameter injected in the handler. Likewise, the return value will be included in the `result` field of the HTTP response payload. + +```jsx +exports.handler = async function(event) + const { + body, // Object with JSON-parsed POST body + headers, // Object with key-values from HTTP headers + queryParameters, // Object with key-values from query parameters + = event.request; + + return + hello: 'world' // JSON-serialized and included in the `result` field of the response + ; +} +``` + +At the moment only JSON payloads are supported, and only non-standard headers with the `X-` or `Stripe-` prefix are provided to the action. + +A sample response from the webhook endpoint looks like the following, where `status` is one of `success` or `error`, `encodedLogs` has the base64-encoded logs from the run, and `result` has the JSON-encoded value returned from the execution. + +```json + + "autotaskRunId": "37a91eba-9a6a-4404-95e4-38d178ba69ed", + "autotaskId": "19ef0257-bba4-4723-a18f-67d96726213e", + "trigger": "webhook", + "status": "success", + "createdAt": "2021-02-23T18:49:14.812Z", + "encodedLogs": "U1RBU...cwkK", + "result": "{\"hello\":\"world\"", + "requestId": "e7979150-44d3-4021-926c-9d9679788eb8" +} +``` + + +Actions that take longer than 25 seconds to complete will return a response with a pending state. Nevertheless, the action will continue to run in the background and eventually complete (in less than 5 minutes). + + + +If `"message":"Missing Authentication Token"` is the response to a Webhook HTTP request, double check that the request was actually a POST. This response occurs when issuing a GET. + + + +Webhook requests have strict content-type requirements. If the request does not have a `Content-Type: application/json` header, the action invocation will return a `415 Unsupported Media Type` error. Please make sure to include this header in your requests. + + +#### Secrets +Defender secrets allow you to store sensitive information, such as API keys and secrets that can be accessed securely from actions.\ +Action secrets are key-value case-sensitive pairs of strings, that can be accessed from action code using the `event.secrets` object. There is no limit to the number of secrets used by an action. Secrets are shared across all actions, and not specific to a single one. + +```jsx +exports.handler = async function(event) + const { mySecret, anApiKey = event.secrets; +} +``` + +Secrets are encrypted and stored in a secure vault, only decrypted for injection when the action runs. Once written, a secret can only be deleted or overwritten from the user interface, but not read. + + +An action may log the value of a secret, accidentally leaking it. + + + +While it’s possible to use secrets to store private keys for signing messages or transactions, we recommend to use a Defender relayer instead. Signing operations for Defender relayers provide an extra level of security over loading the private key in action code and signing there. + + +#### Key-value data store + +The action key-value data store allows to persist simple data across action runs and between different actions. It can be used to store transaction identifiers, hashed user emails, or even small serialized objects. + +You can interact with your key-value store through an instance of `Defender`, which is initialized with the payload injected in the your Action `handler` function. Once initialized, you can call `kvstore.get`, `kvstore.put`, or `kvstore.del`. + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); + +exports.handler = async function (event) + const client = new Defender(event); + + await client.keyValueStore.put('myKey', 'myValue'); + const value = await client.keyValueStore.get('myKey'); + await client.keyValueStore.del('myKey'); +; +``` + +The key-value store allows to get, put, and delete key-value pairs, which must be strings that are limited to 1 KB and values to 300 KB. + + +Data stored is shared across all actions. To isolate the records managed by each action, prefixing the keys with a namespace unique to each action is recommended. + + + +Each item expires 90 days after its last update. If long-lived data store is needed, we recommend setting up an external database and use action secrets to store the credentials for connecting to it. + + +#### Notifications + +Actions can send notifications through various channels already defined in the Defender Notifications settings. This integration allows you to quickly inform other connected systems about changes detected or made by actions. + +To send a notification, you should use `notificationClient.send()`, as shown in the following example: +```js +exports.handler = async function(credentials, context) + const { notificationClient = context; + + try + notificationClient.send({ + channelAlias: 'example-email-notification-channel-alias', + subject: 'Action notification example', + message: 'This is an example of a email notification sent from an action', + ); + } catch (error) + console.error('Failed to send notification', error); + +} +``` + +For email notifications, basic HTML tags are supported. Here’s an example of how to generate an HTML message: +```js + +function generateHtmlMessage(actionName, txHash) + return ` +

Transaction sent from Action ${actionName

+

Transaction with hash $txHash was sent.

+`; +} + +exports.handler = async function(event, context) + const { notificationClient = context; + + const relayer = new Relayer(credentials); + + const txRes = await relayer.sendTransaction( + to: '0xc7464dbcA260A8faF033460622B23467Df5AEA42', + value: 100, + speed: 'fast', + gasLimit: '21000', + ); + + try + notificationClient.send({ + channelAlias: 'example-email-notification-channel-alias', + subject: `Transaction sent from Action ${event.actionName`, + message: generateHtmlMessage(event.actionName, txRes.hash), + }); + } catch (error) + console.error('Failed to send notification', error); + +} +``` + +To send a metric notification, use the `notificationClient.sendMetric()` method instead, as shown in the following example: + +```js +exports.handler = async function(credentials, context) + const { notificationClient = context; + + try + notificationClient.sendMetric({ + channelAlias: 'example-email-notification-channel-alias', + name: 'datadog-test-metric', + value: 1, + ); + } catch (error) + console.error('Failed to send notification', error); + +} +``` + + +If an invalid or paused notification channelAlias is passed, an error will be thrown. + + + +If a notification cannot be sent for any other reason, no error will be thrown, but a status message will be added to the action logs. For example, if a notification to a webhook channel that has an inactive URL is sent, a log entry will be added but no error will be thrown. + + + +If multiple notification channels are using the same alias, the notification will be sent to all of them. + + +#### Error handling + +Automatic action invocations that result in an error contain an `errorType` field in the action run response that will be set to an [ActionErrorType as defined in defender-sdk](https://github.com/OpenZeppelin/defender-sdk/blob/340fce19e35cfed420c94369630ee8f70254c9ac/packages/action/src/models/action-run.res.ts#L6). A user readable error will also appear in the Run History view. + +### Local development + +If you want to reproduce the behavior of an action locally for debugging or testing, follow these steps: + +* Initialize a new npm project (`npm init`) +* Set the `dependencies` key in `package.json` to the packages indicated in the [Environment](#environment) section above +* Download `yarn.lock`: 📎 [yarn.lock]() +* Run `yarn install --frozen-lockfile`. + +You can also use the following template for local development, which will run the action code when invoked using `node`. It will load the relayer credentials from environment variables, or use the injected credentials when run by Defender. + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); + + +// Entrypoint for the action +exports.handler = async function(event) + const client = new Defender(credentials); + // Use client.relaySigner for sending txs + + +// To run locally (this code will not be executed in actions) +if (require.main === module) + const { RELAYER_API_KEY: apiKey, RELAYER_API_SECRET: apiSecret = process.env; + exports.handler( apiKey, apiSecret ) + .then(() => process.exit(0)) + .catch(error => console.error(error); process.exit(1); ); +} +``` + +Remember to send any other value that your action expects in the `event` object, such as secrets or monitor events. + +### Updating code + +You can edit an action’s code via the Defender interface, or programmatically via API using the [`defender-sdk`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) npm package. The latter allows to upload a code bundle with more than a single file: + + +The code bundle must not exceed 5MB in size after being compressed and base64-encoded, and it must always include an `index.js` at the root of the zip file to act as the entrypoint. + + +## Workflows + +Workflows allow you to instantly detect, respond, and resolve threats and attacks with pre-defined actions and scenarios. You can conduct attack simulations and test real-world scenarios on forked networks too. + +Workflows are processes that combine automatic actions and transaction templates. Actions can be run in parallel or connected sequentially. Workflows can be triggered manually or via a [Monitor](/defender/module/monitor). + +Creating workflows is a seamless experience guided through a form that allows you to organize actions in the workflow process easily. + +![Create Workflow](/defender/actions-start-workflow.png) + +To populate a workflow, you have to drag existing actions from the list on the right onto the form. Actions are executed vertically, meaning the previous actions must finish successfully to begin the execution of the new row. Parallel actions are executed at the same time. However, the workflow stops completely if an action exits with an error. + +![Edit Workflow](/defender/actions-workflow.png) + +To run multiple actions in parallel, click "Add Parallel Sequence" and drag actions into the available side-by-side boxes. + +![Parallel Workflow](/defender/actions-parallel-workflow.png) + +You can drag actions back off the workflow to remove them or click the visible minus icon in the upper right to remove an empty step. The "Save" button on the top right saves the workflow with its configuration and name. + + +We provide a quickstart tutorial to create and use Workflows. Check it out [here](/defender/tutorial/workflows)! + + +## A complete example + +The following example uses ethers.js and the relayer integration to send a transaction calling `execute` on a given contract. Before sending the transaction, it checks a `canExecute` view function and validates if a parameter received via a webhook matches a local secret. If the transaction is sent, it returns the hash in the response, which is sent back to the webhook caller. + +```jsx +const ethers = require("ethers"); +const DefenderRelaySigner, DefenderRelayProvider = require('defender-relay-client/lib/ethers'); + +// Entrypoint for the action +exports.handler = async function(event) + // Load value provided in the webhook payload (not available in schedule or sentinel invocations) + const { value = event.request.body; + + // Compare it with a local secret + if (value !== event.secrets.expectedValue) return; + + // Initialize relayer provider and signer + const provider = new DefenderRelayProvider(event); + const signer = new DefenderRelaySigner(event, provider, speed: 'fast' ); + + // Create contract instance from the signer and use it to send a tx + const contract = new ethers.Contract(ADDRESS, ABI, signer); + if (await contract.canExecute()) + const tx = await contract.execute(); + console.log(`Called execute in ${tx.hash`); + return tx: tx.hash ; + } +} +``` + + +The code does not need to wait for the transaction to be mined. Defender will take care of monitoring the transaction and resubmitting if needed. The action only needs to send the request and exit. + + +## Security considerations + +The code for each action is isolated in Defender, and actions are restricted via strict access controls to have zero access to other Defender internal infrastructure. The only exception is that an action may access its linked relayer, which is negotiated via temporary credentials injected by the action service upon each execution. Still, the action can only call the relayer’s exposed methods and has no direct access to the backing private key or any other services. + + +We provide a quickstart tutorial to create an automatic action for a smart contract using Defender. Check it out [here](/defender/tutorial/actions)! + diff --git a/docs/content/defender/module/address-book.mdx b/docs/content/defender/module/address-book.mdx new file mode 100644 index 00000000..6ecdc667 --- /dev/null +++ b/docs/content/defender/module/address-book.mdx @@ -0,0 +1,9 @@ +--- +title: Address Book +--- + +The Address Book allows you to create a shared repository of user-friendly names for your accounts or contracts. You can set up these names anywhere you see an address in Defender just by clicking on it, or you can manage your entire Address Book in the dedicated section. Defender automatically creates Address Book entries for you when you import accounts and contracts in other modules. + +When working with products in Defender, account and contract information will be directly sourced from the Address Book whenever you are required to enter an address, so you can easily fetch addresses from your Address Book when configuring monitors and actions. + +![Manage Address Book](/defender/manage-address-book.png) diff --git a/docs/content/defender/module/audit.mdx b/docs/content/defender/module/audit.mdx new file mode 100644 index 00000000..8bded02c --- /dev/null +++ b/docs/content/defender/module/audit.mdx @@ -0,0 +1,82 @@ +--- +title: Audit +--- + +Audit allows you to summon our team of security experts to verify your system works as intended with the highest level of scrutiny. You can track issues and resolutions, and interact directly with auditors for faster and more efficient communication. + +## Use cases + +* Maintain a searchable repository of all smart contract audits and issues. +* Streamline the interactions between auditors and developers. +* Automate the fix-review process for all identified issues. +* Track all issues identified in an audit to completion. + +## Auditors and Reports + +Auditors have special roles in Defender. Audit Managers are able to sync audit reports to Defender from GitHub and add the Auditors, which are able to read and track issues and questions. Audit Manager can also identify which members of your team have permission to read or comment on audits. + +Once an Audit Manager syncs and delivers an audit report to Defender, it will be visible to any team members who have been assigned the Audit Read or Audit Comment role. All active and historic audit reports will be visible, and team members can click on any audit report to view the details. + +![Audit Status Header](/defender/audit-status.png) + +The Audit page initially includes the project’s name, status, and date of report. Below this, you can find the executive summary of the audit, which provides an overview of the audit’s findings and the overall security posture of the smart contract or project. It also includes links and information on the scope, timeline, and Auditors. + +Below the executive summary, you can find the differente sections of the audit. + +### Overview + +The "Overview" section provides context and background information about the project or smart contract being audited. This section typically starts with a brief introduction to the project, including the project’s name, purpose, and a general description of what it aims to achieve. It’s followed by an overview of the architecture and key components of the project, supported by a high-level description of how the smart contract or blockchain application is structured and how different modules or components interact with each other. Information about the technology stack used in the project may be included. This can encompass the programming languages, libraries, frameworks, and blockchain platforms employed in the development. + +Depending on the audit, this section may also contain additional information about the smart contract’s privileged roles to explain its purpose and responsibilities within the project. This helps auditors and readers understand who has control over critical functions and what actions they can perform. Similarly, trust assumptions are commonly mentioned to outline implicit or explicit assumptions made by the project’s developers about the security and reliability of external components, services, or entities that the project relies on. Lastly, the "Overview" section may include client-found vulnerabilities if provided by the client. These are vulnerabilities or issues that have been identified by the project’s users or clients before the audit, helping the auditors understand the context and history of security concerns related to the project. + +### Issues + +The "Issues" section provides an in-depth examination of security vulnerabilities, bugs, or concerns discovered during the audit process. Within this section, you can filter the issues by content, severity, or status. Filtering by content allows you to look for issues with specific titles or descriptions. Filtering by severity allows you to look for issues with a specific severity level, like Critical, High, Medium, Low, Note, or Client Reported. Filtering by status allows you to look for issues with a specific status, like Unresolved, No Response, Responded, Resolved, Partially Resolved, Acknowledged Not Resolved, or Acknowledged Will Resolve. + +![Audit Issue Filters](/defender/audit-filter.png) + +Each issue within this section contains a title, description, date, and status. You can click on one to expand the information and be able to respond to it. + +![Audit Side Page](/defender/audit-side.png) + +The description explains the nature of the issue, its potential impact, and any technical details necessary for understanding the problem. The issue is also tagged with a severity assessment, helping to describe the impact, likelihood, and difficulty of an issue for prioritization and transparency. + +### Recommendations + +The "Recommendations" section offers actionable guidance to improve the security posture of the project based on the audit results. This section is often composed of mitigation strategies, monitor recommendations, incident response plans, potential pitfalls, and other technical and non-technical advice. + +Monitoring and incident response recommendations included in this section are crucial for ensuring the ongoing security and resilience of your blockchain project or smart contract. These recommendations focus on proactive measures to detect and respond to security incidents, breaches, or anomalies effectively. The information given will provide details on how to apply these recommendations with Defender [Monitors](/defender/module/monitor), [Actions](/defender/module/actions), and [Workflows](/defender/module/actions#workflows). We recommend following the tutorials found in this documentation to learn how to use the Defender modules, like the [Monitor](/defender/tutorial/monitor), [Actions](/defender/tutorial/actions), and [Workflow](/defender/tutorial/workflows) tutorials. + +Recommendations often include: + +* **Event Monitoring**: event monitoring to track and analyze events emitted by your smart contracts. This is essential for identifying unusual or unexpected behavior that may indicate a security issue. +* **Custom Alerts**: Custom alerts based on specific conditions or events. This aligns with the incident response recommendation to have alerting mechanisms in place. +* **Automated Response**: Automated responses to specific events or conditions, enabling rapid incident mitigation. Combined with [Actions](/defender/module/actions) and [Workflows](/defender/module/actions#workflows). +* **Threshold Monitoring**: Monitoring for unusual deviations from established thresholds is a common incident response practice. Defender can assist in setting up threshold-based monitoring with [Monitors](/defender/module/monitor) and to react with automatic [Workflows](/defender/module/actions#workflows). +* **Deployment Considerations**: Minimize risk while avoiding unnecessary delays and post-deployment using Defender [Deploy](/defender/module/deploy). Employ automatic analysis to avoid storage collisions or other issues and benefit from features like cross-chain deterministic deployments, bytecode verification, and more. + +### Conclusion + +The "Conclusion" section summarizes the audit’s findings and provides an overall assessment of the project’s security posture. This section begins with a concise summary of the key findings from the audit, including a recap of critical security vulnerabilities, issues, or concerns discovered during the assessment with the security levels assigned to each identified issue, helping stakeholders understand which issues posed the highest risks. Depending on the issues found, the "Conclusion" section may also restate the high-priority recommendations for addressing issues, emphasizing the immediate actions needed to enhance security. + +This section will also communicate the risks associated with the project’s current security status to help project owners, investors, and users make informed decisions. Additional valuable insights may be provided to support certain decisions by stakeholders, such as deployment, further development, and improvements. If recommendations were provided, these will be restated in this section as a high-level overview. + +## Fix-Review process + +After an audit report is delivered, the individual issues within a report are open to response and comment until they are each finalized by an Audit Manager. Auditors and team members assigned the Audit Comment role are allowed to respond and comment. Team members may ask questions to Auditors or supply information. + +In order to initiate a response on an issue, click on the issue within the "Issues" section of the audit page and then click on "Reply to this issue". + +![Audit Issue Response](/defender/audit-new-issue.png) + +Team members may leave comments for auditors. Specifically, team members can supply links to pull requests (PRs) or commits in their GitHub repository, which represent fixes associated with a specific issue. Multiple links may be added. + +![Audit Issue Reply](/defender/audit-reply-issue.png) + +Defender will keep and display a trail of all communications between the auditors and team members on each issue. + +![Audit Trail](/defender/audit-trail.png) + +Depending on the outcome of fixes and reviews, auditors may update the status of issues to Partially Resolved or Resolved. Once the fix-review process is complete for all issues, the Audit Manager will finalize the audit, after which the full trail of activity is visible, but no more responses or comments are allowed. At the end of the audit, the Audit Manager can also provide a PDF report of the audit, including the fix-review process. + +For any questions regarding your audit process, please get in touch with your assigned Audit Manager. You can provide Defender feedback via [its feedback form](#feedback) — your comments and suggestions will be instrumental in helping us shape the future of the Audit module! diff --git a/docs/content/defender/module/code.mdx b/docs/content/defender/module/code.mdx new file mode 100644 index 00000000..d1e39301 --- /dev/null +++ b/docs/content/defender/module/code.mdx @@ -0,0 +1,286 @@ +--- +title: Code Inspector +--- + +Code Inspector seamlessly integrates with Github to maximize security with every step of your development process via automatic code analysis powered by machine learning intelligence and state-of-the-art tools developed by our security experts. + +For every push to your code, the OpenZeppelin Code Inspector dives into a detailed examination, identifying potential vulnerabilities and suggesting improvements to enhance your code quality. It generates a succinct report, summarizable in your PR comments for quick and immediate access, while a more detailed report is made available on Defender. + +## Use cases + +* Automatically conduct security analysis on pull requests, identifying vulnerabilities and suggesting improvements. +* Utilize summarized reports in the pull request for immediate insights into your code’s health and security. +* Access comprehensive, detailed reports on Defender for an in-depth understanding of potential vulnerabilities and code optimization areas. +* Use OpenZeppelin’s Dependency Checker to identify reused contracts and match them against a database of known vulnerabilities. +* Apply static analysis rules to Solidity files, identifying potential issues and their severity for high-quality code maintenance. + +## Features + +Some issues found by Code Inspector are detected by our AI models. Keep in mind that while we provide confidence levels, the models may occasionally generate incorrect or misleading information. Make sure to verify the information accordingly and we’d love to hear your feedback. + +Code Inspector has a wide range of functionalities designed to enhance security, efficiency, and code quality. These processes are powered by machine learning models and state-of-the-art tools developed by our security experts. + +* **Reused Code**: Identifies vulnerabilities in reused smart contract code by generating a unique fingerprint for each contract and matching it against a database of known issues. +* **Vulnerable Dependencies**: Notify when a OpenZeppelin Contracts dependency with a known vulnerability is used, providing you with the necessary information to address the issue. +* **Test Suggestions**: Opportunities to apply fuzzing tests to functions, providing you with areas where further testing could prove beneficial. +* **External Call Safety**: Safety of external calls, highlighting any instances that might pose a severity concern. +* **Standard Compatibility**: Compatibility of your contracts with established standards, flagging potential compatibility issues. +* **Reentrancy Attack Vectors**: Potential reentrancy attack vectors at both the file and function level, assigning these threats high-severity labels. +* **Code Readability**: Missing docstrings in functions and misspelled words throughout the codebase, ensuring your code is as legible and understandable as possible. +* **Code Efficiency and Security Practices**: Areas in your code where improvements can be made for better performance and highlights potential security risks, enabling you to optimize and secure your contracts more effectively. + +## Installation + +Installation must be initiated from Defender. Installing the app directly from GitHub will not correctly set up the integration. + +The installation process connects your Defender account and your GitHub repositories. Follow the following steps: + +1. Navigate to the [Code Inspector page](https://defender.openzeppelin.com/v2/#/code) on Defender. +2. Click on the Install Code Inspector button, which redirects you to Github. +3. Select and approve the repositories to install the app. +4. Generate your first report by creating or updating a pull request on a repository that has the app installed. + +The installation is currently done solely through Defender, ensuring a seamless connection for the reports. Following the installation, no additional setup is required. If you encounter installation issues, refer to our [Troubleshooting](#installation-issues) section for guidance. + +## Usage + +Code Inspector is designed to streamline your code analysis workflow. Once installed, the app is triggered whenever a pull request (PR) is opened or a new commit is pushed to an existing PR in your GitHub repository. To avoid skipping the report, the PR must contain at least a modification to any Solidity file or the `package.json` file. + +The app automatically generates a summary report for each new commit, which is posted as a comment in your PR. This summary report is continuously updated with the latest issues discovered in the most recent commit, and the commit hash is directly viewable in the report. + +Along with the summary reports in PR comments, a detailed report for each commit is created and can be accessed on Defender, offering in-depth information on the identified issues and how to fix them. + +If you wish to stop receiving reports for a specific repository, it’s as simple as navigating to the Code Inspector settings and removing the respective repository from the list. + +### Statuses +These are the possible statuses of reports: + +* `Running`: The report has been triggered, and it’s running. +* `Succeeded`: The report has been successfully generated, and it’s available in Defender and Github. +* `Failed`: The report has failed. Make sure that your repository contains valid Solidity files. If this issue persists, please get in touch. +* `Throttled`: The report has been throttled as your tenant exceeded the Code Inpsector quota limit. If you would like to increase your quota, please get in touch. +* `Skipped`: The report has been skipped. Check that the PR and/or commit contains at least one modification to a Solidity file or the `package.json` file. + +## Reports + +### Summary Report + +The summary report provides a clear overview of potential vulnerabilities detected in the code during the review process. The report is conveniently categorized by process and severity level, making it easier to identify areas that need attention. You can navigate to the complete report on Defender via the provided link. Each report is tied to a specific commit, ensuring accurate tracking of changes and issues over time. + +image::code-report-summary.png[Summary Report] + +In this example, you can see the number of issues detected by Code Inspector, along with their respective severity levels. By clicking the link at the bottom of the report, you can view the full details of these vulnerabilities on Defender. + +### Issues + +Full reports identify issues within your smart contracts using a broad range of rules. The rules cover many aspects, such as known vulnerabilities, best practices, code efficiency, and secure coding principles. + +![Full Report 1](/defender/contract-inspector-detailed-report.png) + +Each issue is assigned a severity level based on the potential impact on the contract’s functionality and security. An explanation accompanies each flagged issue, articulating the reason for the concern. + +Every issue has a suggested resolution tailored to improving your code quality and overall security. This might include recommendations to refine your code, modify visibility scopes, apply necessary mathematical checks, enhance documentation, or adhere to a specific Ethereum standard. + +Depicted below is an example of a vulnerability detected in a dependency with a brief description of its potential impact. The specific dependency and its version are outlined, pinpointing where the problem exists. + +![Dependency Checker Report](/defender/dependency-checker-detailed-report.png) + +To help you resolve these issues, recommendations on updates or patches that can address the vulnerabilities are provided along with the relevant advisory links for a more detailed understanding of the issue. + +By reviewing and applying the proposed solutions in this report, you can enhance the robustness and reliability of your smart contracts, ensuring adherence to best practices and industry standards. This makes the audit process smoother and improves the preparedness of your contracts for successful deployment. + +### Standards + +This feature introduces a new section in the report called ***Standards***. This section provides insights into your contracts that implement interfaces from the OpenZeppelin Contracts library. For example, if you are using an `IERC20`, Code Inspector will check the implementation details and properties to ensure you are using this ERC correctly. + +## Detailed Checks + +For each function and event defined in an interface, Code Inspector performs comprehensive checks to verify compliance with the standard. These checks include: + +* ***Signature:*** Ensures that the function or event signature matches the standard’s specification. Example: `transfer(address,uint256)` +* ***Visibility:*** Checks that the visibility of functions and events adheres to the standard’s requirements. Example: `external`, `public` +* ***Mutability:*** For functions, ensures that the mutability is correctly specified as per the standard. Example: `view`, `pure` +* ***Return Type:*** Verifies that the return types of functions align with the expected types defined in the standard. Example `returns (bool)` +* ***Parameter Names:*** Confirms that the parameter names are consistent with those defined in the standard, enhancing code readability and maintainability. Example: `transfer(address recipient, uint256 amount)` +* ***Return Names:*** Ensures that the return variable names, where used, are consistent with the standard. Example: `returns (bool success)` + +If any attribute of a function or event fails, the entire function or event is marked as failed. Additionally, if any function or event within an implementation fails, the entire implementation is considered non-compliant. + +## Configuration + +You can configure repository-specific parameters for Code Inspector using a file called `defender.config.json` in the root directory. The structure of the file is separated into processes run by Code Inspector. Each process has a list of parameters that you can adjust to your needs. For example, you can specify which directories to scan, which is useful to prevent Code Inspector from analyzing test or script files written in Solidity. + +### Contract Inspector +Contract Inspector runs a [set of rules](#rules) to detect potential issues in your code. You can configure the following parameters: + +* `enabled`: ability to turn off the Contract Inspector. False meaning not to run, and true to run. (default: `true`) +* `scan_directories`: list of directories to scan with paths starting from the root directory. default: `["."]` +* `include_rules`: list of rules to run. default: [all rules](#rules). +* `exclude_rules`: list of rules to not run. default: none. + +### Dependency Checker +Dependency Checker verifies that your dependencies are not vulnerable to known issues. You can configure the following parameters: + +* `enabled`: ability to turn off Dependency Checker. False meaning not to run, and true to run. (default: `true`) + +#### Example + +The following `defender.config.json` configuration file will disable Dependency Checker and run Contract Inspector on the `src/contracts1` and `src/contracts2` directories, excluding two rules (`naming-convention` and `unused-state`). + +```json + + "contract_inspector": { + "enabled": true, + "scan_directories": ["src/contracts1", "src/contracts2"], + "exclude_rules": ["naming-convention", "unusued-state"] + , + "dependency_checker": + "enabled": false + +} +``` + +## Assets + +The Assets page allows you to manage your Code Inspector assets, supporting two types: Smart Contracts and GitHub repositories. When you install the Code Inspector GitHub app and select repositories, they will appear on this page. Additionally, you can create Smart Contract assets from addresses in your address book. Note that Smart Contract assets must be verified on Etherscan for this feature to work. + +Smart Contract assets can be manually triggered for reports on the report page. GitHub assets can be triggered manually and automatically from new pull requests (PRs) and commits. + +![Code Assets](/defender/code-assets.png) + +### Settings + +The Asset Settings page offers two main settings to manage how Defender handles asset security: Automatically Generate Report and Vulnerability Detection. + +#### Automatically Trigger Report for Pull Requests + +Automatically Generate Report, is specifically for GitHub repositories. When enabled, this setting ensures continuous monitoring by generating new reports automatically whenever a new pull request (PR) is opened or a new commit is pushed to a PR. + +#### Active Vulnerability Detection and Notification + +Active Vulnerability Detection and Notification provides a robust security measure by automatically scanning your assets for new vulnerabilities. +This setting is applicable to both GitHub repositories and smart contracts. To enable this feature for Github repositories assets, you must activate the setting and select a specific branch to be monitored. + +* **Automated Scanning**: Leveraging Defender’s advanced smart contracts scanning capabilities, when our team becomes aware of a new vulnerability, whether disclosed or undisclosed, we promptly update our scanning algorithms to detect it. We then scan all assets configured to be monitored +* **Automated Notification**: Once the automatic scanning is completed an automated system notification email will be sent to inform you whether the vulnerability was detected in your monitored assets. If detected, the notifications include relevant information tailored to the nature of the vulnerability. +* **Risk Mitigation**: When detected, to aid in addressing and mitigating detected vulnerabilities, notifications may include suggestions for risk mitigation, providing actionable steps for administrators to protect their smart contracts and associated assets. + +By enabling Active Vulnerability Detection and Notification for your assets, you can benefit from continuous, automated scanning and timely notifications, empowering you to respond quickly to new threats and maintain the security of your smart contracts and GitHub repositories. This proactive approach ensures that you can stay ahead of potential vulnerabilities and safeguard your code effectively. + +## Settings + +The Settings page allows you to manage the permissions and access level of the Code Inspector. If you need to make changes to the repositories that the app has access to, a convenient link takes you directly to the GitHub settings page of the app, facilitating effortless repository management. + +In the Github tab, you can globally suspend or uninstall the app, giving you complete control over its operation within your projects. + +![Code Inspector Github](/defender/code-settings-advanced.png) + +## Troubleshooting + +### Installation Issues + +* **Installing the app outside Defender**: Code Inspector must be installed via Defender. If you attempt to install it from elsewhere, the installation will not succeed. Ensure you’re logged in to your OpenZeppelin account and navigate to Code Inspector from Defender for a successful installation. +* **Code Inspector Access**: Access to Code Inspector is required for a successful installation. If you find that you don’t have access and you think this is a mistake, contact OpenZeppelin support to get the necessary permissions. + +### Repository Size Issues + +Errors related to analysis timeouts are often caused by large codebases. To mitigate this issue, it is recommended to use the `scan_directories` option in the defender configuration file to scope the analysis to relevant files only. By specifying which directories to include in the scan, the configuration file can significantly reduce processing times and prevent timeouts. For detailed instructions on setting up the configuration file, please refer to the [Configuration](/defender/module/code#configuration) section. + +## Rules + +| ID | Description | Severity | +| --- | --- | --- | +| `alert-uniswap-v2-router-liquidity-considerations` | Identifies any instance of a Uniswap Router V2 addLiquidity call. | note | +| `array-length-to-stack` | Identifies when the length of an array can be written to the stack to save gas. | note | +| `call-with-arbitrary-address-bytes` | Identifies a potentially unsafe external call. | medium | +| `chainlink-deprecated-functions` | Identifies usage of chainlink’s deprecated functions. | medium | +| `check-consistent-usage-of-msgsender-msgdata` | Identifies usage of `msg.sender` or `msg.data` when `_msgSender()` or/and `_msgData()` are present | note | +| `check-effect-interact` | Identifies a possible violation of the check, effect and interact pattern. | ethtrust | +| `check-erc4337-compatibility` | Identifies if the contract may not be compatible with ERC-4337. | note | +| `check-return-data-from-external-call` | Identifies when the external call return data check is missing. | note | +| `constants-not-using-proper-format` | Identifies when a constant is not using the proper format. | note | +| `dangerous-strict-equality` | Identifies the use of strict equalities that can cause a Gridlock. | medium | +| `default-values-assigned` | Identifies an instance of a variable initialized to its default value. | note | +| `delegatecall-to-arbitrary-address` | Identifies when an there is a delegatecall or call code to an arbitrary address. | high | +| `delegatecall-usage` | Identifies an instance of delegatecall. | ethtrust | +| `different-pragma-directives` | Identifies whether different Solidity versions are used. | low | +| `disableinitializers-not-called-in-implementation-constructor` | Identifies if `_disableInitializers()` is not being called in the constructor of an Initializable contract | note | +| `doc-code-mismatch-model` | Identifies a possible docstrings and code mismatch. | low | +| `duplicated-import` | Identifies duplicated imports. | note | +| `exact-balance` | Identifies whether a balance is compared to an exact value. | ethtrust | +| `external-call-reentrancy-attack-vector` | Identifies external calls as a possible vector for a reentrancy attacks. | note | +| `fallback-with-return-value` | Identifies if there are fallback functions with return values. | note | +| `floating-pragma` | Identifies pragma directives that do not specify a particular, fixed version of Solidity. | low | +| `function-init-state-variable` | Identifies when a state variable is initialized by a function. | note | +| `function-level-access-control-model` | Identifies a possible access control attack vector on a function. | high | +| `function-level-reentrancy-model` | Identifies a possible reentrancy attack vector on a function. | high | +| `function-visibility-too-broad` | Identifies if function visibility is unnecessarily broad. | note | +| `gas-limit-on-call` | Identifies when an external call has a hard-coded gas limit. | low | +| `hashing-dynamic-values` | Identifies a hashing of a packed encoded dynamic value. | ethtrust | +| `identify-hardhat-console-import` | Identifies a hardhat console import. | note | +| `identify-to-do-comments` | Identifies todo comments in the code. | note | +| `inconsistent-order-contract` | Identifies when a contract has a inconsistent order. | note | +| `inconsistent-use-named-returns` | Identifies inconsistent usage of named returns within a codebase. | note | +| `incorrect-format-onERC721Received` | Identifies when a contract includes the `onERC721Received` function and it has an incorrect format. | note | +| `incorrect-modifier` | Identifies an incorrect definition of a modifier. | medium | +| `incremental-update-optimization` | Identifies the use of `i`` (rather than ``i`) to save gas in for loop headers. | note | +| `indecisive-license` | Identifies when a file has multiple SPDX licenses. | note | +| `int-negative-evaluation-overflow` | Identifies when additive inverse of an int variable is evaluated. | note | +| `lack-of-gap-variable` | Identifies when an upgradeable contract does not have a gap variable. | low | +| `lack-of-indexed-event-parameters` | Identifies when a lack of indexed event parameter. | note | +| `lack-of-security-contact` | Identifies when a contract does not have a security contact. | note | +| `lack-of-spdx-license-identifier` | Identifies when a lack of SPDX license identifier. | note | +| `lock-ether` | Identifies any instance of locked ETH within a contract. | high | +| `memory-side-effect-assembly` | Identifies when some code may be vulnerable to a Solidity compiler vulnerability. | medium | +| `missing-docstrings` | Identifies when a function is missing docstrings. | low | +| `missing-initializer-modifier` | Identifies when a function is missing the initializer modifier. | low | +| `missing-mapping-named-parameters` | identifies when a mapping is missing named parameters | note | +| `missing-return` | Identifies when a function is missing the return statement. | low | +| `misuse-boolean-literal` | Detects the misuse of a Boolean literal (used in complex expressions or as conditionals). | medium | +| `msg-value-loop` | Identifies usage of msg.value inside a loop. | note | +| `multiple-contracts-per-file` | Identifies multiple contract declarations per file. | note | +| `name-reused` | Identifies in a codebase when two or more contracts have the same name. | note | +| `non-explicit-imports` | Identifies a non-explicit import. | note | +| `not-operator-assembly` | Identifies usage of the `not` operator inside assembly code because it functions differently than in other languages. | note | +| `outdated-solidity-version` | Identifies a Solidity file with an outdated Solidity version. | note | +| `overriding-state-values-in-constructor` | Identifies cases where state variables are explicitly set to values within a contract but are overwritten by the constructor. | note | +| `possible-incorrect-abi-decode` | Identifies the potential for incorrect ABI decoding. | note | +| `possible-return-bomb` | Identifies a possible vector for a return bomb attack. | note | +| `pragma-spans-breaking-changes` | Identifies a Solidity file with pragma that spans versions of solidity where breaking changes may have been introduced. | low | +| `precision-loss-div-before-mul` | Identifies possible precision loss due to division before multiplication | note | +| `redundant-safemath-library` | Identifies a redundant use of SafeMath library. | note | +| `replace-revert-strings-custom-errors` | Identifies when a revert string could be replaced by a custom error. | note | +| `require-instead-of-revert` | Identifies when a require statement does not check for any conditions. | low | +| `require-missing-message` | Identifies when an error message is missing from the require statement. | low | +| `require-multiple-conditions` | Identifies a require statement with multiple conditions. | low | +| `revert-missing-message` | Identifies when a revert statement is missing the error message. | low | +| `selfdestruct-usage` | Identifies an instance of selfdestruct. | ethtrust | +| `state-updated-without-event` | Identifies if a function is updating the state without an event emission. | note | +| `state-var-visibility-not-explicitly-declared` | Identifies when the visibility of a state variable that has not been explicitly declared. | note | +| `sushiswap-callback-attack` | Identifies possible attacks on sushiswap callback where a fake pool address can pass authorization check. | note | +| `swapped-arguments-function-call` | Identifies when the arguments of function call have been swapped. | note | +| `too-many-digits` | Identifies a literal number with many digits. | note | +| `transferfrom-dangerous-from` | Identifies usage of `transferFrom` with `from` parameter not being a `msg.sender`. | high | +| `unchecked-call-success` | Identifies when the external call fail check is missing. | ethtrust | +| `unchecked-increment` | Identifies that an incremental update is not wrapped in an unchecked block. | note | +| `unchecked-keyword` | Identifies unchecked code inside a function. | note | +| `unchecked-math` | Identifies a potentially unsafe usage of unchecked math. | high | +| `unicode-direction-control` | Identifies the use of unicode direction control character. | ethtrust | +| `unnecessary-assignment` | Identifies an unnecessary assignment of a variable. | note | +| `unnecessary-cast` | Identifies an unnecessary cast. | note | +| `unsafe-abi-encoding` | Identifies any use of unsafe ABI encoding. | low | +| `unsafe-mint-ERC721` | Identifies if a `_mint` function is used instead of `_safeMint` in ERC721 context. | note | +| `unused-arguments` | Identifies an unused function argument. | note | +| `unused-enum` | Identifies an unused enum. | note | +| `unused-error` | Identifies an unused error. | note | +| `unused-event` | Identifies an unused event. | note | +| `unused-function` | Identifies an unused function with internal or private visibility. | note | +| `unused-imports` | Identifies an unused import. | note | +| `unused-named-returns` | Identifies an unused named return variable. | note | +| `unused-state-variable` | Identifies an unused state variable. | note | +| `unused-struct` | Identifies an unused struct. | note | +| `use-of-transfer-send` | Identifies instance of transfer or send. | low | +| `use-of-uint-instead-of-uint256` | Identifies if an `int/uint` is used instead of `int256/uint256`. | note | +| `variable-could-be-constant` | Identifies variables that could be declared as `constant`. | note | +| `variable-could-be-immutable` | Identifies variables that are only ever set in the constructor and could be `immutable`. | note | +| `void-constructor-call` | Identifies the call to a constructor that is not implemented. | low | diff --git a/docs/content/defender/module/deploy.mdx b/docs/content/defender/module/deploy.mdx new file mode 100644 index 00000000..6b313bda --- /dev/null +++ b/docs/content/defender/module/deploy.mdx @@ -0,0 +1,92 @@ +--- +title: Deploy +--- + +Deploy allows you to deploy and upgrade smart contracts across chains securely. You can prove that the code running on-chain matches the audited implementation and minimize crucial mistakes that can lead to losses or issues. + +## Use cases + +* Configure production and test environments with granularity over deployer addresses. +* Pay gas for deployments automatically. +* Ensure all smart contract deployments are verified on block explorers. +* Manage deployments to multiple chains from a single place. +* Automate multisig approval process for upgrades. +* Fully compatible with CI/CD for automated releases. +* Track the details and history for every deployment or upgrade. + +## Environments + +Deploy is divided into production and test environments, with mainnet networks for the former and testnet networks for the latter. Each environment is associated with one or more networks according to its type of environment, which allows to separate test contracts from production safely. To facilitate the onboarding process, the setup for each environment is configured through a wizard, which swiftly walks you through the following steps. + +### Wizard + +#### Step 1: Networks + +In the first step, you select the networks to use. Deploy supports multi-chain deployments, allowing users to deploy contracts to the same addresses across multiple networks. You can add or remove networks at any time through the environment page. + + +To maintain the best security practices, the wizard will only show networks according to the type of environment chosen to prevent mixing networks. + + +#### Step 2: Deploying + +In this step, users choose the default approval process for each network. The resource associated to this approval process is the one that will be used to pay and execute the deployment transactions. If you don’t have an approval process for a network, the wizard will allow you to create one of either type Relayer, Safe or EOA ("Externally Owned Account"). You can learn more about approval processes [here](/defender/settings#approval-processes). + +#### Step 3: Upgrading + +This last step is optional but recommended if you plan to deploy upgradeable contracts. Here you choose the approval process for upgrades for each network. You can find a list of supported processes [here](/defender/settings#approval-processes). + +## Environment Page + +Once inside an environment, you can see the configuration and activity of deployments within it. + +### Configuration + +You can edit the configuration of any network within the environment. To do so, click the edit button to reach the configuration page, where you can add or remove a network and change the default approval processes. We use system block explorer API keys to automatically verify contracts on Etherscan. You can manage your own keys in the configuration page. + +### History + +The history table shows the activity within the environment, allowing you to check the status of deployments or upgrades. There are three possible statuses: + +* `COMPLETE`: The deployment has been successfully executed in the network. +* `PENDING`: The upgrade is waiting for approval. +* `FAILED`: The deployment has failed. Make sure the relayer or EOA associated to the approval process has enough funds and that the smart contract is valid. + +## Deploying and Upgrading + +After an environment is configured, you can use it for deployments and upgrades. To do so, we provide an [API](https://www.npmjs.com/package/@openzeppelin/defender-sdk-deploy-client), and [Hardhat](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades) and [Foundry](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades) plugins. The Deploy API is recommended for custom projects that don’t use Hardhat or Foundry. On the other hand, the plugins are extremely easy to use as they only require a few lines of code to implement. + +Defender will use the default approve process for deployments and upgrades. However, the Hardhat and Foundry plugins allow you to specify a different approval process for upgrades. If a block explorer API key is not provided, Defender will try to verify the contracts with the default key. Otherwise, it will use the one provided. + + +We provide a quickstart tutorial to deploy and upgrade a smart contract using Defender with Hardhat and Foundry. Check it out [here](/defender/tutorial/deploy)! + + + +Using `CREATE2` may affect `msg.sender` behavior; see documentation for details [here](/defender/tutorial/deploy#caveats)! + + +## Metadata + +To identify, tag, or classify deployments, you can use the `metadata` field, which includes properties like `commitHash`, `tag`, or any custom property that suits your use case. + +```js + const client = new Defender( + apiKey: process.env.API_KEY, + apiSecret: process.env.API_SECRET, + ); + + const deployment = await client.deploy.deployContract( + contractName: 'Example', + ... + metadata: { + commitHash: '4ae3e0d', + tag: 'v1.0.0', + anyOtherField: 'anyValue', + , + }); +``` + +Once the deployment is submitted, these metadata fields will be displayed in the Defender UI under _Metadata_, formatted in JSON. + +![Deploy Metadata](/defender/deploy-metadata-1.0.png) diff --git a/docs/content/defender/module/monitor.mdx b/docs/content/defender/module/monitor.mdx new file mode 100644 index 00000000..c714ab72 --- /dev/null +++ b/docs/content/defender/module/monitor.mdx @@ -0,0 +1,444 @@ +--- +title: Monitor +--- + +Monitors allow you to gain full visibility into your smart contracts' risks and behaviors. You can detect threats, get alerts on threats and anomalies, and automatically respond and resolve issues. + +## Use cases + +* Monitor crucial events and authorized functions like ownership transfers, pauses, or mints. +* Alert on potentially dangerous transactions or operational issues. +* Integrate notifications to Slack, Telegram, Discord, email, PagerDuty, Opsgenie or custom APIs. +* Use pre-built templates to setup monitoring quickly and easily. +* Combine with other Defender modules to execute on-chain transactions with monitor triggers. + +## Monitors + +Monitors can be created from scratch or from templates, which are designed for common use cases. Templates automatically pre-populate a monitor, so you can easily modify and re-adapt them. + +![Monitor Templates](/defender/monitor-templates.png) + +Monitors are organized into five categories, according to the type of risk they monitor: + +* Governance +* Access Control +* Suspicious Activity +* Financial +* Technical + +You can also specify a severity level, which your team can use to group or filter monitors by. + +* High Severity +* Medium Severity +* Low Severity + +## Configuring a Monitor + +A monitor watches all on-chain contract transactions and notifies when one matches the parameters, filters, or events. + + +At this time, Monitors are supported across all [networks](#networks) except Fantom. + + +### General Information +* **Name**: The name assigned to the monitor. +* **Risk Category**: The risk category of the monitor, useful for filtering or grouping. +* **Contracts**: The smart contracts to monitor. Monitoring relies on the underlying ABI, so you may only have 1 ABI per monitor. In case of multiple smart contracts, the monitor must adhere to the same ABI/Interface. +* **Confirmation Blocks**: If you want to be notified only after a certain level of confidence that the transaction is accepted, a higher confirmation block level is recommended, but if you want to be notified as soon as possible with tolerance to reorganizations, a lower confirmation block level is better. In chains where safe and finalized block tags are accepted, you can also select them as confirmation block level. + +### Matching Rules + +For a transaction to trigger a notification, it must satisfy **ALL** of the following: + +* Transaction **MUST** have a To, From, or Address (from the log) that matches the configured address. +* If a **Transaction Filter** is specified + * The Transaction **MUST** match the **Transaction Filter**. +* If **Events** are selected + * The Transaction **MUST** emit any of the selected Events and match the **Event Condition** (if any) +* If **Functions** are selected + * The Transaction **MUST** directly invoke any of the selected Functions (contract calls are not detected at the moment) and match the **Function Condition** (if any) + +### Transaction Filters + +Transaction filters allows to narrow the transactions being monitored. These are entered as property expressions or Javascript code, offering great flexibility. To accommodate comparisons for checksum and non-checksum addresses, comparisons are case-insensitive. + + +To receive ALL transactions that involve the selected events/functions, transaction conditions should not be specified. + + +* Conditions can use `AND`, `OR`, `NOT` and `()` +* Conditions can use `==`, `<`, `>`, `>=`, `<=` to compare +* Number values can be referred to by Hex (0xabc123) or Decimal (10000000000) +* String values can only be compared via `==` +* Includes basic math operators: `+`, `-`, `*`, `/`, `^` + + +If a transaction filter condition is specified, then a transaction MUST meet this condition in order to trigger a notification. + + +Transaction Conditions can refer to the following properties + +* `to` is the _to_ address for the transaction +* `from` is the _from_ address for the transaction +* `gasPrice` is the price of gas sent in the transaction. In EIP1559 transactions, it's equal to or below the `maxFeePerGas`. +* `maxFeePerGas` is the maximum price the transaction was willing to pay for the transaction. Only existent in EIP1559 transactions. +* `maxPriorityFeePerGas` is the maximum amount of wei over the `BASE_FEE` the transaction is willing to pay to the miner for inclusion. Only existent in EIP1559 transactions. +* `gasLimit` is the gas limit sent in the transaction +* `gasUsed` is the amount of gas used in the transaction +* `value` is the value sent in the transaction +* `nonce` is the nonce for the specific transaction +* `status` is a derived value and can be compared with `"success"` or `"failed"` + +#### Example Conditions + +Transactions that are reverted + +```jsx +status == "failed" +``` + +Transactions excluding those from 0xd5180d374b6d1961ba24d0a4dbf26d696fda4cad + +```jsx +from != "0xd5180d374b6d1961ba24d0a4dbf26d696fda4cad" +``` + +Transactions that have BOTH a gasPrice higher than 50 gwei AND a gasUsed higher than 20000 + +```jsx +gasPrice > 50000000000 and gasUsed > 20000 +``` + +### Custom Filters + +Custom filters supports custom code for filtering transactions. If a custom filter is specified, it will be called with a list of matches found for a given block. This allows the monitor to use other datasources and custom logic to evaluate whether a transaction matches. + + +Only transactions that match other conditions (event, function, transaction) will invoke the custom filter. + + + +Each invocation can contain up to 25 transactions. + + +#### Request Schema + +The request body will contain the following structure. The `MonitorConditionRequest` type from the [defender-sdk-action-client](https://www.npmjs.com/package/@openzeppelin/defender-sdk-action-client) package can be used for custom filters in Typescript. + +```jsx + + "events": [ + { + "hash": "0xab..123", // the transaction hash + "timestamp": "1699857792", // the timestamp of the transaction (block) + "blockNumber": 18561272, // the block number of the transaction + "blockHash": "0xab..123", // block hash from where this transaction was seen + "transaction": { // eth_getTransactionReceipt response body + ... // see https://eips.ethereum.org/EIPS/eip-1474 + "cumulativeGasUsed": "0xc3614", + "effectiveGasPrice": "0x6e214d78", + "gasUsed": "0x6075", + "logs": [ + { ... + ], + "logsBloom": "0x4..0", + "status": "0x1", + "from": "0xab..123", + "to": "0xab..123", + "transactionHash": "0xab..123", + "transactionIndex": "0x9", + "type": "0x2" + }, + "matchReasons": [ // the reasons why monitor triggered + + "type": "event", // event, function, or transaction + "address": "0x123..abc", // address of the event emitting contract + "signature": "...", // signature of your event/function + "condition": "value > 5", // condition expression (if any) + "args": ["5"], // parameters by index (unnamed are present) + "params": { "value": "5" // parameters by name (unnamed are not present) + } + ], + "matchedAddresses": ["0xabc..123"], // the addresses from this transaction your are monitoring + "matchedChecksumAddresses": ["0xAbC..123"], // the checksummed addresses from this transaction your are monitoring + "monitor": + "id": "44a7d5...31df5", // internal ID of your monitor + "name": "Monitor Name", // name of your monitor + "abi": [...], // abi of your addresses (or undefined) + "addresses": ["0x000..000"], // addresses your monitor is watching + "confirmBlocks": 0, // number of blocks monitor waits (can be 'safe' or 'finalized' on PoS clients) + "network": "rinkeby" // network of your addresses + "chainId": 4 // chain Id of the network + , + "metadata": "..." // metadata (if available) + } + ] +} +``` + +#### Response Schema + +The custom filter must return a structure containing all matches. Returning an empty object indicates no match occurred. The type for this object is `MonitorConditionResponse`. + + +Errors will be treated as a non-match. + + +```jsx + + "matches": [ + { + "hash": "0xabc...123", // transaction hash + "metadata": { + "foo": true // any object to be shared with notifications + + }, + + "hash": "0xabc...123" // example with no metadata specified + + ] +} +``` + +#### Example Custom Filters + +```jsx +exports.handler = async function(payload) + const conditionRequest = payload.request.body; + const matches = []; + const events = conditionRequest.events; + for(const evt of events) { + + // add custom logic for matching here + + // metadata can be any JSON-marshalable object (or undefined) + matches.push({ + hash: evt.hash, + metadata: { + "id": "customId", + "timestamp": new Date().getTime(), + "numberVal": 5, + "nested": { "example": { "here": true } + } + }); + } + return matches +} +``` + +### Events and Functions + +Events and functions can be selected as filters. Selecting multiple events acts as an OR clause (triggered for ANY selected events). The same applies for functions. + +Conditions for events or functions can further narrow the monitor. These can refer to arguments in the signature either by name (if the argument is named) or by index (e.g., $0, $1...). The variables must match the types shown in the interface. If left empty, the condition will be ignored. + + +If no events or functions are specified, then ALL transactions to or from the contracts will be in scope. + + + +Monitors seamlessly supports notifications for events emitted by a smart contract on all networks, regardless of whether they are triggered directly or through internal calls from a third contract. However, the capability to track and provide notifications for internal function calls within a contract is currently limited to the Ethereum mainnet. + + +#### Example Conditions + +Transactions that emit a `Transfer(...)` event with a value between 1 and 100 ETH (in hex) + +```jsx +// Event Signature: Transfer(address to, address from, uint256 value) +value > 0xde0b6b3a7640000 and value < 0x56bc75e2d63100000 +``` + +Transactions that emit a `ValsEvent(...)` event with an array with a first element equal to 5 + +```jsx +// Event Signature: ValsEvent(uint256[3] vals) +vals[0] == 5 +``` + +Transactions that invoke a `greet(...)` function with an unnamed string of "hello" + +```jsx +// Function Signature: greet(address, string) +$1 == "hello" +``` + +## Alerts + +A monitor can use any supported notification channel for alerting. It’s also possible to connect an Action or a Workflow that should run with the monitor. + +To prevent repeated alerts from individual monitors and control the notification rate, you can use the Alert Threshold and Minimum time between consecutive notifications fields. + +* Alert Threshold: Define the number of times a monitor must trigger per unit of time before a notification is sent or an action is fired. The unit of time is defined by the Minimum time between consecutive notifications field. +* Minimum time between consecutive notifications: Set the the minimum wait time between sending notifications. + +![Configure Defender Monitor alerts](/defender/monitor-alert-v2.png) + +### Notifications + +You can create **Notifications** attached to the Monitor alerts for getting notified about the on-chain events across many channels, see more about how to create and configure them in [this section](/defender/settings/notifications). + +#### Customizing Notification + +You can also modify the message body content and formatting using the Customize notification checkbox below the notification channel selector. + +##### Template + +```md +**Monitor Name** + +{ monitor.name } + +**Network** + +{ monitor.network } + +**Block Hash** + +{ blockHash } + +**Transaction Hash** + +{ transaction.transactionHash } + +**Transaction Link** + +[Block Explorer]({ transaction.link }) + +{ matchReasonsFormatted } + +**value** + +{ value } +``` + +#### Preview + +```md +*Monitor Name* + +Monitor + +*Network* + +rinkeby + +*Block Hash* + +0x22407d00e953e5f8dabea57673b9109dad31acfc15d07126b9dc22c33521af52 + +*Transaction Hash* + +0x1dc91b98249fa9f2c5c37486a2427a3a7825be240c1c84961dfb3063d9c04d50 + +https://rinkeby.etherscan.io/tx/0x1dc91b98249fa9f2c5c37486a2427a3a7825be240c1c84961dfb3063d9c04d50[Block Explorer] + +*Match Reason 1* + +_Type:_ Function + +_Matched Address_:_ 0x1bb1b73c4f0bda4f67dca266ce6ef42f520fbb98 + +_Signature:_ greet(name) + +_Condition:_ name == 'test' + +_Params:_ + +name: test + +*Match Reason 2* + +_Type:_ Transaction + +_Condition:_ gasPrice > 10 + +*Value* + +0x16345785D8A0000 +``` + +##### Message Syntax + +Custom notifications support a limited set of markdown syntax: + +* Bold (`***this text is bold***`) +* Italic (`*this text*` and `_this text_` are italic) +* Links (this is a `[link](https://example.com)`) + +There is partial support for additional markdown syntax, but rendering behavior varies by platform. Email supports full HTML and has the richest feature set, but other messaging platforms have limitations, including support for standard markdown features such as headings, block quotes, and tables. Combinations of the supported features (e.g., bold and italicized text) also have mixed support. A warning message will appear directly below the editor if the markdown contains any syntax with mixed platform support. + +#### Monitor Event Schema +You can access the following schema when using custom notification templates. This schema is also passed to the Action if you configure your monitor to execute one. +```jsx + + "transaction": { // eth_getTransactionReceipt response body + ... // see https://eips.ethereum.org/EIPS/eip-1474 + "cumulativeGasUsed": "0xc3614", + "effectiveGasPrice": "0x6e214d78", + "gasUsed": "0x6075", + "logs": [ + { ... + ], + "logsBloom": "0x4..0", + "status": "0x1", + "from": "0xab..123", + "to": "0xab..123", + "transactionHash": "0xab..123", + "transactionIndex": "0x9", + "type": "0x2" + }, + "blockHash": "0xab..123", // block hash from where this transaction was seen + "matchReasons": [ // the reasons why monitor triggered + + "type": "event", // event, function, or transaction + "address": "0x123..abc", // address of the event emitting contract + "signature": "...", // signature of event/function + "condition": "value > 5", // condition expression (if any) + "args": ["5"], // parameters by index (unnamed are present) + "params": { "value": "5" // parameters by name (unnamed are not present) + } + ], + "matchedAddresses":["0x000..000"] // the addresses from this transaction monitored + "monitor": + "id": "44a7d5...31df5", // internal ID of monitor + "name": "Monitor Name", // name of monitor + "abi": [...], // abi of address (or undefined) + "addresses": ["0x000..000"], // addresses monitored + "confirmBlocks": 0, // number of blocks monitor waits (can be 'safe' or 'finalized' on PoS clients) + "network": "rinkeby" // network of address + "chainId": 4 // chain Id of the network + , + "value": "0x16345785D8A0000" // value of the transaction + "metadata": ... // metadata injected by action condition (if applicable) +} +``` + +##### Dynamic Content + +Custom notification templates render dynamic content using inline templating. Any string surrounded by double curly braces will be resolved against the Event Schema. Deeply nested items (including those in arrays) can be accessed using dot notation. + +In addition to the standard event schema, the following parameters are injected for usage in custom notification messages: + +* `transaction.link` +* `matchReasonsFormatted` + +#### Character Limit + +Messages will be truncated if they exceed a platform’s character limit. The best practice is to limit messages to 1900 characters. + +## Settings + +In the settings tab, you can specify the default notification channel associated with the different severities: High, Medium, or Low. + +![Monitor Settings](/defender/monitor-settings.png) + +## Pausing and Deleting + +From the monitor page, you can pause created monitors that are active. By clicking on the dotted button on the card, you can delete or save the monitor as a template. + +Saving a monitor as a template stores its configuration and parameters, which can be found by clicking the template gallery tab. + + +We provide a quickstart tutorial to monitor a smart contract using Defender. Check it out [here](/defender/tutorial/monitor)! + diff --git a/docs/content/defender/module/relayers.mdx b/docs/content/defender/module/relayers.mdx new file mode 100644 index 00000000..6e0af163 --- /dev/null +++ b/docs/content/defender/module/relayers.mdx @@ -0,0 +1,727 @@ +--- +title: Relayers +--- + +Relayers allow you to send on-chain transactions via regular API requests or through other Defender modules, like Actions, Workflows, and Deploy. Relayers also automate the payment of gas fees and take care of private key secure storage, transaction signing, nonce management, gas pricing estimation, and resubmissions. With Relayers, you don’t have to worry about storing private keys on your back-end servers or monitoring gas prices and transactions to ensure they get confirmed. + +## Use cases + +* Execute transactions on smart contracts automatically to trigger a state transition. +* Update an on-chain oracle with external data. +* Send meta-transactions to build a gasless experience. +* React to sign-ups in your app by airdropping tokens to your new users. +* Sweep funds from protocol contracts to secure wallets, +* Build bots with complete custom logic and flexibility. + +## What's a Relayer? + +A Relayer is an Ethereum-based externally-owned account (EOA) assigned exclusively to your team. Every time you create a new Relayer, Defender will create a new private key in a secure vault. Whenever you request Defender to send a transaction through that Relayer, the corresponding private key will be used for signing. + +You can think of each Relayer as a queue for sending transactions, where all transactions sent through the same Relayer will be sent in order and from the same account, controlled exclusively by your team. Learn more about the technical implemention [here](#under-the-hood). + +![Manage Relayers](/defender/manage-relayers.png) + +To create a Relayer, simply click the ***Create Relayer*** button on the top-right section of the page, specify a name and select the network. + +![Manage Relayers Detail](/defender/manage-relayers-detail.png) + + +Keep in mind that you’ll need to fund each Relayer individually with ETH (or the native chain token) to ensure they have enough funds to pay for the gas of the transactions you send. Defender will send you an email notification if a Relayer’s funds drop below 0.1 ETH. + + + +Testnet Relayers created through the Deploy wizard will be automatically funded if possible. Read more [here](/defender/module/deploy#step-3-upgrading). + + +### API Keys + +Each Relayer can have one or more **API keys** associated with it. In order to send a transaction through a Relayer, you will need to authenticate the request with one an API key/secret pair. You can create or delete API keys as you see fit, and this will not change the sending address or Relayer balance. + +To create an API key for a Relayer, click on the Relayer and then on the **More** button to expand the dropdown and select **Create API Key**. + +![Manage Relayers Create API Key](/defender/manage-relayers-create-api-key.png) + +Once the API Key is created, make sure to write down the secret key. The API secret is only visible once during the creation — if you don’t write it down, it’s lost forever. + +![Manage Relayer API Key](/defender/manage-relayer-api-key.png) + + +The API key of a Relayer is ***not*** related to its private key. The private key is always kept within a secure key vault and never exposed (see the [Security considerations](#security-considerations) section for more info). This decoupling allows you to freely rotate API keys while keeping the same address for your Relayer. + + +### Addresses + +Whenever you create a Relayer, a fresh EOA will be created to back it. For security reasons, it’s not possible to import an existing private key into a Relayer nor export the private key of a Relayer created by Defender. If you grant a privileged role to a Relayer address in your system to avoid lock-in, consider having an administrative method for switching it to a different one if needed. + +### Policies + +You can limit a Relayer’s behavior by specifying policies. + +To configure a Relayer’s policies, go to the [Relayer page](https://defender.openzeppelin.com/v2/#/relayers), select the Relayer, and then go to the **Policies** tab. You will then see a form where you can opt to enable policies and tweak their parameters. + +![Manage Relayer Policies](/defender/manage-relayer-policies.png) + +#### Gas price cap +Specify a maximum gas price for every transaction sent with the Relayer. When this policy is enabled, Defender will overwrite the `gasPrice` or `maxFeePerGas` of any transaction that goes beyond the specified cap. Take into account that the gas price for a transaction is specified based on gas price oracles at the moment the Relayer actually sends the transaction to be mined, so this policy can be used as a protection on gas price surges. + + +In addition to the maximum gas price policy you can specify here, Defender implements a minimum gas price policy for networks that have minimum gas requirements. Check requirements with the individual networks you use. + + +#### Receiver whitelist +Specify a list of authorized contracts for every transaction sent using the Relayer. Defender will reject and discard any transaction whose destination address is not in the list. + + +The whitelist applies only to the `to` field of a transaction. It doesn’t filter ERC20 or other assets receivers. + + +#### EIP1559 Pricing +Specify if the transactions the Relayer sends should be EIP1559 by default or not. This applies whenever the Relayer sends a transaction with dynamic gas pricing or a non specified `gasPrice` or `maxFeePerGas`/`maxPriorityFeePerGas`. Note that this policy option is only shown for EIP1559 compatible networks. + + +EIP1559 Pricing policy is enabled by default for new Relayers. If you have a Relayer that was created without the default opt-in, you can always enable this flag. + + +#### Private transactions +Specify if the transactions should be sent via private mempool. This means that a transaction will not be publicly seen until it’s included in a block. + +The parameter can be toggled between the following states: `true` to enable private mempool transactions, `false` to opt for public visibility. Alternatively, users can specify the transaction speed by setting the value to either `flashbots-normal` or `flashbots-fast`. By default, when the policy is set to `true`, the speed defaults to `flashbots-normal`, allowing for seamless inclusion while maintaining transaction privacy. This configuration empowers users to tailor their transaction strategy to suit their specific privacy and speed requirements effectively. You can read about faster transactions with Flashbots [here](https://docs.flashbots.net/flashbots-protect/quick-start#faster-transactions). + + +Private transactions are only enabled for _mainnet_ by using the [Flashbots Protect RPC](https://docs.flashbots.net/flashbots-protect/rpc/quick-start). So, the same [key considerations](https://docs.flashbots.net/flashbots-protect/rpc/quick-start#key-considerations) might apply while sending private transactions through Defender. + + +## Relayer Groups + +Relayer Groups are collections of individual relayers that work together to submit transactions. By grouping relayers, you can increase the overall transaction throughput and redundancy, which enhances the reliability of the transaction submission process. Relayer Groups are designed to distribute the workload across multiple relayers, ensuring that no single relayer becomes a bottleneck. + +### Benefits of Relayer Groups + +* ***Increased Throughput:*** Relayer Groups can handle a higher volume of transactions because the workload is spread across multiple relayers. +* ***Redundancy:*** If one relayer in the group fails or becomes slow, others can take over, reducing the risk of delays. +* ***Efficiency:*** By coordinating multiple relayers, you can optimize transaction submission and ensure that transactions are processed as quickly as possible. +* ***Centralised Management:*** The ability to manage multiple relayers under a single API key simplifies administration, making it easier to maintain control over a complex system. + +### Drawbacks of Relayer Groups + +* ***Unified Configuration:*** Policies and configurations apply uniformly across all relayers in the group, making it difficult to manage individual relayer settings. +* ***Limited Functionality:*** Certain functionalities, like message signing, are not available for relayers that are part of a group. +* ***Group-Restricted Operation:*** Relayers within a group cannot be used independently; they must function collectively as part of the group. +* ***Potential Transaction Order Issues:*** Since transactions are distributed among different relayers based on their condition, they may not be processed in the order they were received, leading to some transactions being mined out of sequence. + +### Health Monitoring +Relayer groups rely on regular health checks to ensure that transactions are distributed efficiently among the relayers in the group. These health checks assess the performance and availability of each relayer, helping the system decide which relayers are best suited to handle new transactions. + +The system regularly evaluates each relayer in the group. It calculates a "weight" for each relayer based on several key factors. These weights are then used to determine how transactions should be distributed within the group, with priority given to the most reliable and responsive relayers. + +* ***Speed of First Transaction Processing (Highest Priority):*** The most important factor is how quickly a relayer starts processing transactions. The system looks at the time it takes for the first pending transaction to be sent and processed. A faster relayer is considered healthier and is given a higher priority. +* ***Number of Pending Transactions:*** The system checks how many transactions are waiting in each relayer’s queue. If a relayer has a lot of pending transactions, it might indicate that it’s overloaded and could struggle to process new transactions quickly. +* ***Remaining Balance:*** The relayer’s available balance is also considered. A relayer needs enough balance to cover transaction fees. If a relayer’s balance is low, it may have difficulty processing transactions, which affects its health score. + +Users can manually adjust the weight of individual relayers. For example, setting a weight of 0 would prevent a relayer from being used, offering precise control over which relayers are active. + +## Sending transactions + +The easiest way to send a transaction via a Relayer is using the [`Defender SDK`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) package. The client is initialized with an API key/secret and exposes a simple API for sending transactions through the corresponding Relayer. + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); +const client = new Defender( + relayerApiKey: 'YOUR_API_KEY', + relayerApiSecret: 'YOUR_API_SECRET' +); + +const tx = await client.relayerSigner.sendTransaction( + to, value, data, gasLimit, speed: 'fast' +); + +const mined = await tx.wait(); +``` + + + +For better reliability of the relayers, we recommend sending no more than **50 transactions/min** on a single relayer especially on fast moving chains like Polygon, Optimism, Arbitrum etc.. For example, if you want 250 transactions/min throughput, you would need to load balance across 5 relayers. These 5 relayers can be part of the same account. + + + +You don’t need to enter a private key when initializing a Relayer client, since the private key is kept secure in the Defender vault. + + + +Currently, _zkSync_ doesn’t have a way to precisely calculate `gasLimit` other than using the `eth_estimateGas` endpoint. Therefore, Defender can’t do any gasLimit and overrides the user input with the RPC estimation. + + +### Using ethers.js + +The Relayer client integrates with [ethers.js](https://docs.ethers.io/v6/) via a custom [signer](https://docs.ethers.org/v6/api/providers/#Signer). This allows you switch to a Relayer and send transactions with minimal changes in your codebase. + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); +const ethers = require('ethers'); + +const credentials = relayerApiKey: YOUR_RELAYER_API_KEY, relayerApiSecret: YOUR_RELAYER_API_SECRET ; +const client = new Defender(credentials); + +const provider = client.relaySigner.getProvider(); +const signer = client.relaySigner.getSigner(provider, speed: 'fast', validUntil ); + +const erc20 = new ethers.Contract(ERC20_ADDRESS, ERC20_ABI, signer); +const tx = await erc20.transfer(beneficiary, 1e18.toString()); +const mined = await tx.wait(); +``` + +In the example above, we are also using a `DefenderRelayProvider` for making calls to the network. The signer can work with any provider, such as `ethers.getDefaultProvider()`, but you can rely on Defender as a network provider as well. + +You can read more about the ethers integration [here](https://www.npmjs.com/package/@openzeppelin/defender-sdk-relay-client). + +### Using web3.js + +The Relayer client integrates with [web3.js](https://web3js.readthedocs.io/) as well as via a custom [provider](https://web3js.readthedocs.io/en/v1.3.4/web3-eth.html#providers). This allows you to send transactions with a Relayer and query the network using the familiar web3 interface. + +```jsx +const Defender = require('@openzeppelin/defender-sdk'); +const Web3 = require('web3'); + +const credentials = relayerApiKey: YOUR_RELAYER_API_KEY, relayerApiSecret: YOUR_RELAYER_API_SECRET ; +const client = new Defender(credentials); + +const provider = client.relaySigner.getProvider(); + +const web3 = new Web3(provider); + +const [from] = await web3.eth.getAccounts(); +const erc20 = new web3.eth.Contract(ERC20_ABI, ERC20_ADDRESS, from ); +const tx = await erc20.methods.transfer(beneficiary, (1e18).toString()).send(); +``` + +In the example above, the `transfer` transaction is signed and broadcasted by the Relayer, and any additional JSON RPC calls are routed via Defender private endpoint. + +You can read more about the web3 integration [here](https://www.npmjs.com/package/@openzeppelin/defender-sdk-relay-client). + +### Intents Support with Relay Signer + +When using the Defender SDK’s relay signer to send transactions from the context of an ethers/web3 contract, it is important to note that transactions in [intent mode](#the-intent-mechanism) are not supported. If the API returns an [intent response](#intent-response), an error will be thrown due to the current implementation expecting certain fields that are absent in the intent response. + +To avoid this issue, it is recommended to fallback to using the default SDK `sendTransaction` method for transaction submissions. This ensures that the transaction can be processed without errors. + +Additionally, the current implementation does not track transactions that are resubmitted, which can lead to complications when multiple hashes are generated due to retries. It is crucial to implement logic that can handle these scenarios effectively, ensuring that the process completes even when transaction hashes change in the background. + +### EIP1559 support + +Since not all of the supported networks are EIP1559 compatible, the EIP1559 transaction support is only enabled for those ***networks identified as compatible*** and enabled by the team. + +A Relayer can send EIP1559 transactions in the following ways: + +* Sending a transaction via UI with the [`EIP1559Pricing`](#eip1559-pricing) policy ***enabled*** +* Sending a transaction via API with both `maxFeePerGas` and `maxPriorityFeePerGas` specified +* Sending a transaction via API with `speed` and with the [`EIP1559Pricing`](#eip1559-pricing) policy ***enabled*** + +Once any transaction is sent, ***it will have the same type*** on every stage of its lifecycle (such as replacement and repricing), so it’s currently not possible to change the type if it’s already been submitted. + + +Any attempt to send `maxFeePerGas` or `maxPriorityFeePerGas` to non-EIP1559 compatible networks will be rejected and discarded by the Relayer. + + +You can tell if a network supports EIP1559 by looking at the Relayer [policies](#policies). If the EIP1559Pricing policy doesn’t show up, it means that we haven’t added EIP1559 support for that network. + + +If you notice an EIP1559 compatible network that we already support but doens’t have the EIP enabled, please don’t hesitate to reach out via [https://www.openzeppelin.com/defender2-feedback](https://www.openzeppelin.com/defender2-feedback). + + +### Private transactions + +Private transaction allows a Relayer to send transactions without being visible on the public mempool, and instead, the transaction is relayed via a private mempool using a special `eth_sendRawTransaction` provider, which will vary depending on the network and current support (such as Flashbots network coverage). + +A Relayer may send a private transaction in any of the following ways: + +* Sending a transaction via API with the [`privateTransactions`](#private-transactions) policy ***enabled*** or set to `flashbots-normal` or `flashbots-fast` +* Sending a transaction via API with `isPrivate` parameter set to `true` +* Sending a transaction via UI and checking the Mempool Visibility checkbox + +![Mempool visibility checkbox on Relayer's send transaction view](/defender/relayer-mempool-visibility-check.png) + + +Sending a transaction with the `isPrivate` flag set to `true` to a network that doesn’t support private transactions will be rejected and discarded by the Relayer. + + +Currently, only the following network is supported + +* **Mainnet**: Via [Flashbots Protect RPC](https://docs.flashbots.net/flashbots-protect/rpc/quick-start) + +### Speed + +Instead of the usual `gasPrice` or `maxFeePerGas`/`maxPriorityFeePerGas`, the Relayer may also accept a speed parameter, which can be `safeLow`, `average`, `fast`, or `fastest`. These values are mapped to actual gas prices when the transaction is sent or resubmitted and vary depending on the state of the network. + +If speed is provided, the transaction would be priced according to the `EIP1559Pricing` Relayer policy. + + +Mainnet gas prices and priority fees are calculated based on the values reported by [EthGasStation](https://ethgasstation.info/), [EtherChain](https://etherchain.org/tools/gasPriceOracle), [GasNow](https://www.gasnow.org/), [Blockative](https://docs.blocknative.com/gas-platform), and [Etherscan](https://etherscan.io/gastracker). In Polygon and its testnet, the [gas station](https://gasstation-mainnet.matic.network/v2) is used. In other networks, gas prices are obtained from a call to `eth_gasPrice` or `eth_feeHistory` to the network. + + +### Fixed Gas Pricing + +Alternatively, you may specify a ***fixed gasPrice*** or a ***fixed combination of maxFeePerGas and maxPriorityFeePerGas*** for a transaction, by setting either the `gasPrice` parameter or `maxFeePerGas` and `maxPriorityFeePerGas` parameters. Transactions with a fixed pricing are either mined with the specified pricing or replaced with a NOOP transaction if they couldn’t be mined before [validUntil](#valid-until) time. + +Keep in mind that you have to provide either `speed`, `gasPrice`, `maxFeePerGas`/`maxPriorityFeePerGas` or none, but not a mix between them in a send transaction request. + + +Whenever a send transaction request is sent without any pricing parameter, it will be priced with a `fast` default speed. + + + +If you’re providing both fixed `maxFeePerGas` and `maxPriorityFeePerGas`, make sure that `maxFeePerGas` is greater or equal than `maxPriorityFeePerGas`. Otherwise, it’ll be rejected. + + +### Valid Until + +Every transaction via a Relayer is valid for submission to the network until `validUntil` time. After `validUntil` time the transaction is replaced by a NOOP transaction in order to prevent Relayers from getting stuck at the transaction’s nonce. A NOOP transaction does nothing except advancing the Relayer’s nonce. + +`validUntil` defaults to 8 hours after the transaction creation. Note that you can combine validUntil with a [fixed pricing](#fixed-gas-pricing) to achieve extremely fast mining times and beating other transactions on `gasPrice` or `maxFeePerGas`. + +If you’re using `ethers.js`, you may set a `validForSeconds` option instead of `validUntil`. In the example below, we configure a `DefenderRelaySigner` to issue a transaction which will be valid for 120 seconds after its creation. + +```jsx +const DefenderRelayProvider, DefenderRelaySigner = require('@openzeppelin/defender-sdk-relay-signer-client/ethers'); +const ethers = require('ethers'); + +const credentials = apiKey: API_KEY, apiSecret: API_SECRET ; +const provider = new DefenderRelayProvider(credentials); +const signer = new DefenderRelaySigner(credentials, provider, speed: 'fast', validForSeconds: 120 ); +``` + + +`validUntil` is a UTC timestamp. Make sure to use a UTC timezone and not a local one. + + +### Transaction IDs + +Since a Relayer may resubmit a transaction with an updated gas pricing if it does’t get confirmed in the expected time frame, the `hash` of a given transaction may change over time. To track the status of a given transaction, the Relayer API returns a `transactionId` identifier you can use to [query](https://www.npmjs.com/package/@openzeppelin/defender-sdk-relay-signer-client) it. + +```jsx +import Relayer from '@openzeppelin/defender-sdk-relay-signer-client'; +const relayer = new Relayer( apiKey: API_KEY, apiSecret: API_SECRET ); +const latestTx = await relayer.getTransaction(tx.transactionId); +``` + +The returned transaction object `latestTx` will have the following shape: + +```jsx +interface RelayerTransactionBase + transactionId: string; // Defender transaction identifier + hash: string; // Ethereum transaction hash + to: string; + from: string; + value?: string; + data?: string; + speed: 'safeLow' | 'average' | 'fast' | 'fastest'; + gasLimit: number; + nonce: number; + status: 'pending' | 'sent' | 'submitted' | 'inmempool' | 'mined' | 'confirmed' | 'failed'; + chainId: number; + validUntil: string; + +``` + + +The `getTransaction` function will return the latest view of the transaction from the Defender service, which gets updated every minute. + + +### Replace Transactions + +While a Relayer will automatically resubmit transactions with increased gas pricing if they are not confirmed, and will automatically cancel them after their valid-until timestamp, you can still manually replace or cancel your transaction if it has not been mined yet. This allows you to cancel a transaction if it is no longer valid, tweak its TTL, or bump its speed or gas pricing. + +To do this, use the `replaceByNonce` or `replaceById` of the `@openzeppelin/defender-sdk-relay-client`: + +```jsx +// Cancel tx payload (tx to a random address with zero value and data) +replacement = + to: '0x6b175474e89094c44da98b954eedeac495271d0f', + value: '0x00', + data: '0x', + speed: 'fastest', + gasLimit: 21000 +; + +// Replace a tx by nonce +tx = await relayer.replaceTransactionByNonce(42, replacement); + +// Or by transactionId +tx = await relayer.replaceTransactionById('5fcb8a6d-8d3e-403a-b33d-ade27ce0f85a', replacement); +``` + +You can also replace a pending transaction by setting the `nonce` when sending a transaction using the `ethers` or `web3.js` adapters: + +```jsx +// Using ethers +erc20 = new ethers.Contract(ERC20_ADDRESS, ERC20_ABI, signer); +replaced = await erc20.functions.transfer(beneficiary, 1e18.toString(), + nonce: 42 +); + +// Using web3.js +erc20 = new web3.eth.Contract(ERC20_ABI, ERC20_ADDRESS, from ); +replaced = await erc20.methods.transfer(beneficiary, (1e18).toString()).send( + nonce: 42 +); +``` + + +You can ***only*** replace transactions of the same type. For example, if you’re trying to replace an EIP1559 transaction, it ***can’t be replaced*** with a legacy transaction. Also, if `speed` is provided instead, the transaction will be repriced as its original type requires with the given speed. + + +### Webhooks Notifications + +Listening to transaction status changes can be efficiently achieved using a push-based approach through webhooks. This method allows your application to receive real-time notifications whenever there are updates to the status of a transaction. + +***Steps to Configure Webhook Notifications***: + +1. ***Access the Relayers Page*** +2. ***Select Transaction Statuses***: + On the Relayers page, you will find options to select the specific transaction statuses for which you want to receive notifications. Available statuses: + * ***Pending***: Transaction received by the Defender. + * ***Sent***: Transaction prepared for sending(priced and signed). + * ***Submitted***: Transaction submitted to the network. + * ***InMemPool***: Transaction found in the mempool. + * ***Mined***: Transaction mined. + * ***Confirmed***: Transaction confirmed(a minimum of 12 confirmations). +3. ***Choose Notification Channel***: + After selecting the desired statuses, you need to choose webhook notification channels. This is where the webhook notifications will be sent. +4. ***Save Your Configuration***: + Once you have selected the statuses and configured the notification channel, save your settings. This will register your webhook and start the process of receiving notifications for the selected events. + +Example of Webhook Notification: +```json + + "event": "transaction_status_change", + "timestamp": "2024-06-13T12:29:41.254Z", + "transaction": { + "signature": { + "r": "0xee81d58c53c1d3432c95847c71a525417bad6e8fa711007137b2e11155ba8f94", + "s": "0x362ce1f75d660504e22590f678c243bc24954ccfbf17864f4aab05fd8b1d6ca3", + "v": "0x1b" + , + "maxPriorityFeePerGas": 7556907973, + "maxFeePerGas": 39004490874, + "chainId": 11155111, + "hash": "0xea04c34422295ef60b57fea50790b4f9396d852274fa46ee5cf8d0407d7cc32b", + "transactionId": "1eece8bb-05d6-493f-903a-12c750700b81", + "value": "0x5af3107a4000", + "gasLimit": 25200, + "to": "0x5e87fD270D40C47266B7E3c822f4a9d21043012D", + "from": "0xf87921a0999d522383afa2b41db2538231a647f0", + "data": "0x", + "nonce": 19, + "status": "mined", + "speed": "fast", + "validUntil": "2024-06-13T20:29:01.051Z", + "createdAt": "2024-06-13T12:29:01.546Z", + "sentAt": "2024-06-13T12:29:01.546Z", + "pricedAt": "2024-06-13T12:29:01.546Z", + "isPrivate": false + } +} +``` + +### List Transactions + +You can also list the latest transactions sent via your Relayer, optionally filtering by status (pending, mined, or failed). This can be particularly useful to prevent your Actions scripts from re-sending a transaction already in-flight: before sending a transaction, you can use the list method filtered by `pending` status to see if there is a transaction in the queue with the same destination and calldata as the one you are about to send. + +```jsx +const txs = await relayer.list( + since: new Date(Date.now() - 60 * 1000), + status: 'pending', // can be 'pending', 'mined', or 'failed' + limit: 5, // newest txs will be returned first + usePagination: true, + next: '' // optional next cursor for pagination + sort: 'desc' +) +``` + +### Delete Pending Transaction + +In situations where a relayer is stuck and unable to process transactions, the system provides a functionality to delete pending transactions. This action is designed as a last resort to address issues with transactions that have not been mined for at least 30 minutes. This feature can be activated from the Relayer Drawer, under the Pending Transactions tab, when the relayer has pending transactions. + +Key Points: + +* ***Intended Use***: This feature is specifically aimed at resolving issues with relayers that are stuck due to unmined transactions. It is recommended to use this only after confirming that there have been no transactions mined for at least 30 minutes. +* ***Operation Overview***: Upon initiating the delete operation, the relayer will enter a paused state. During this pause, the system will either send NOOPs (No-Operation Instructions) to clear certain transactions or remove them entirely from the database. This distinction is made based on the specific characteristics of each pending transaction. +* ***Duration***: The entire process of deleting pending transactions and resuming normal operations can take up to 30 minutes. This includes the time taken to assess each transaction, apply the necessary actions, and ensure the relayer is ready to resume its functions. +* ***Resumption of Operations***: After the completion of the delete operation, the relayer will automatically resume its standard activities. Users do not need to take any further action to reactivate the relayer. +* ***Notification***: Users will receive an email notification once the process is complete and the relayer has resumed its operations. + +### The Intent Mechanism + +An "intent" is a concept introduced to improve the efficiency and reliability of transaction submissions. Instead of immediately submitting a transaction, an intent is a placeholder that indicates a transaction is ready to be sent but is temporarily held back. This mechanism helps manage situations where the transaction queue becomes too large or when the network or relayer is processing transactions slowly. + +Intents are used to maintain the order of transactions and prevent overloading the system. There are two primary scenarios where intents come into play: + +* ***High Transaction Volume:*** When the number of pending transactions exceeds the maximum allowed in-flight, new transactions are stored as intents. This prevents the system from being overwhelmed and ensures that transactions are submitted in the correct order. +* ***Slow Processing:*** If the network or relayer is processing transactions slowly (e.g., no transaction has been mined in the last 30 minutes), new transactions are stored as intents to avoid adding to the congestion. + +#### Intent Response + +Intent response is similar to the response of a normal transaction, but with the following differences: + +* `hash` is `null` +* `isIntent` is `true` and indicates the transaction was sent as an intent, this will not be set for normal transactions. This field will not be removed or changed when the intent is processed. +* `pendingIntentSince` is the timestamp when the intent was created and indicates the intent is still pending. This field will be removed once the intent is processed. +* `relayerId` is the same ID of the relayer group upon creation. This will change to the ID of the relayer that will process the intent once the assignment is made. +* No gas-related fields are set, the transaction will be priced when it is processed. + +```json + + "hash": null, + "transactionId": "transaction123", + "value": "0x1", + "gasLimit": 21000, + "to": "0x123...", + "data": "0x", + "status": "pending", + "speed": "fast", + "validUntil": "2000-01-01T20:00:00.000Z" + "isIntent": true, + "relayerId": "relayer123", + "tenantRelayerGroupId": "tenant123|relayerGroup123", + "createdAt": "2000-01-01T12:00:00.000Z" + +``` + +#### Cancel Intents + +Relayer transaction intents can be canceled and permanently removed from our end. This feature is particularly useful when you need to prevent a specific intent from being submitted or want to free up space in the queue for other, more urgent transactions. + +To do this, use the `cancelTransactionById` method of the `@openzeppelin/defender-sdk-relay-client`: + +```jsx +tx = await relayer.cancelTransactionById('5fcb8a6d-8d3e-403a-b33d-ade27ce0f85a'); +``` + +## Signing + +In addition to sending transactions, a Relayer can also sign arbitrary messages according to the [EIP-191 Standard](https://eips.ethereum.org/EIPS/eip-191) (prefixed by `\x19Ethereum Signed Message:\n`) using its private key. You can access this feature via the `sign` method of the client or the equivalent ethers.js method. + +```jsx +const signResponse = await relayer.sign( message ); +``` + + +As opposed to most libraries, Relayers use non-deterministic ECDSA signatures. This means that if you request a Relayer to sign the same message multiple times, you will get multiple different signatures, which may differ to the result you get by signing using ethersjs or web3js. All those different signatures are valid. See [RFC6979](https://datatracker.ietf.org/doc/html/rfc6979#section-3) more information. + + + +For relayers that are part of a relayer group this method is not available. + + +## Signing Typed Data + +Along with the sign api method, Relayers also implement a `signTypedData`, which you can use to sign messages according to the [EIP712 Standard](https://eips.ethereum.org/EIPS/eip-712) for typed data signatures. +You can either provide the `domainSeparator` and `hashStruct(message)` or use the equivalent ethers.js method + +```jsx +const signTypedDataResponse = await relayer.signTypedData( + domainSeparator, + hashStructMessage +); +``` + + +For relayers that are part of a relayer group this method is not available. + + +## Relayer Info + +A relayer’s address can be retrieved using the `getAddress` method of the `DefenderRelaySigner` class. + +```jsx +const address = await signer.getAddress(); +``` + +If you need more info about a Relayer then checkout the `getRelayer` method of the client. It returns the following data: + +```jsx +const info = await relayer.getRelayer(); +console.log('Relayer info', info); + +export interface RelayerModel + relayerId: string; + name: string; + address: string; + network: string; + paused: boolean; + createdAt: string; + pendingTxCost: string; + +``` + + +For relayers that are part of a relayer group this method will return the relayer group information. + + +## Relayer Status + +To gain better insight into the current status of a relayer, one can use the `getRelayerStatus` method from the `DefenderRelaySigner` class. This method provides real-time information about a relayer, such as its nonce, transaction quota, and the number of pending transactions. +```jsx +const address = await signer.getRelayerStatus(); +``` + +If you need info about a Relayer then checkout the `getRelayer` method of the client. It returns the following data: + +```jsx +export interface RelayerStatus + relayerId: string; + name: string; + nonce: number; + address: string; + numberOfPendingTransactions: number; + paused: boolean; + pendingTxCost?: string; + txsQuotaUsage: number; + rpcQuotaUsage: number; + lastConfirmedTransaction?: { + hash: string; + status: string; + minedAt: string; + sentAt: string; + nonce: number; + ; +} +``` + + +For relayers that are part of a relayer group this method will return an array of status responses for the group. + + +## Network calls + +Defender also provides an easy way to make arbitrary JSON RPC calls to the network. You can use the low-level `relayer.call` method to send any JSON RPC HTTP request: + +```jsx +const balance = await relayer.call('eth_getBalance', ['0x6b175474e89094c44da98b954eedeac495271d0f', 'latest']); +``` + +If you are using ethers.js, this is supported via a custom `DefenderRelayProvider` [provider](https://docs.ethers.org/v6/api/providers/) object: + +```jsx +const provider = new DefenderRelayProvider(credentials); +const balance = await provider.getBalance('0x6b175474e89094c44da98b954eedeac495271d0f'); +``` + +## Withdrawing funds + +You can withdraw funds from a Relayer on the [Relayers page](https://defender.openzeppelin.com/v2/#/relayers), selecting the Relayer, and clicking on **Withdraw**. + +![Relayer Withdraw Button](/defender/relayer-withdraw.png) + +At the **Withdraw** screen, you can choose to send funds in ETH or pick from a built-in list of ERC20 tokens. + +![Relayer Withdraw Funds Screen](/defender/relayer-withdraw-screen.png) + +## Under the hood + +Each Relayer is associated to a private key. When a request to send a transaction is received, the Relayer validates the request, atomically assigns it a nonce, reserves balance for paying for its gas fees, resolves its speed to a `gasPrice` or `maxFeePerGas`/`maxPriorityFeePerGas` depending on its EIP1559 pricing policy, signs it with its private key, and enqueues it for submission to the blockchain. The response is sent back to the client only after this process has finished. Then, the transaction is broadcasted through multiple node providers for redundancy and retried up to three times in case APIs are down. + +Every minute, all in-flight transactions are checked by the system. If they have not been mined and more than a certain time has passed (which depends on the transaction speed), they are resubmitted with a 10% increase in their respective transaction type pricing (or the latest pricing for their speed, if it’s greater), which could be up to a **150% of the reported gas pricing for their speed**. This process causes the transaction hash to change, but their ID is preserved. On the other hand, if the transaction has been mined, it is still monitored for several blocks until we consider it to be confirmed. + +### Insufficient Funds + +Defender carefully tracks the costs of transactions sent to a relayer. In instances where the relayer cannot immediately process a transaction, Defender accumulates the costs of all pending transactions. Before allowing any new transactions to be submitted, Defender will ensure that the balance of the relayer is sufficient to cover the costs of all pending transactions including the new one. Consequently, you may encounter an "insufficient funds" error for a particular transaction during such occurrences. + +* The cost of a transaction is calculated as: `txCost = gasLimit * maxFeePerGas + value`. +* The balance of a relayer is calculated as: `predictedBalance = balance - pendingTxCosts`. + +An "insufficient funds" error will throw when: `txCost > predictedBalance`. + +## Transaction Throughput and Load Balancing + + +We recommend using relayer groups for increased throughput and redundancy. However, if that is not an option, you could use the method below to optimize your setup. + + +Relayers assign nonces atomically which allows them to handle many concurrent transactions. However, there do exist limits to optimize the infrastructure (all numbers below are cumulative of all Relayers in an account) + +By default, when you create an api key for a specific relayer it’s automatically assigned with the following rate limits. + +* 100 requests/second with a burst of 300 requests. + +These rate limits are for both reads ( e.g. - getting transaction status ) and writes ( e.g. - sending transactions ). + +If you need additional throughput for your use case please reach out `defender-support@openzeppelin.com`. You need to be on **enterprise tier** for throughput increases. + +For better reliability of the relayers, we recommend sending no more than **50 transactions/min** on a single relayer especially on fast moving chains like Polygon, Optimism, Arbitrum etc.. For example, if you want 250 transactions/min throughput, you would need to load balance across 5 relayers. These 5 relayers can be part of the same account. + +You may use the [`Defender SDK`](https://www.npmjs.com/package/@openzeppelin/defender-sdk) package to load balance across multiple relayers. Here is a simple example on how you can do this: + +```ts +require('dotenv').config(); + +const Defender = require('@openzeppelin/defender-sdk'); + +async function loadbalance() + const LOAD_BALANCE_THRESHOLD = 50; + const relayerCredsForMainNet = [ + { + relayerApiKey: process.env.RELAYER_API_KEY_1, + relayerApiSecret: process.env.RELAYER_API_SECRET_1, + , + + relayerApiKey: process.env.RELAYER_API_KEY_2, + relayerApiSecret: process.env.RELAYER_API_SECRET_2, + , + ]; + const relayerClientsForMainNet = relayerCredsForMainNet.map((creds) => new Defender(creds)); + + const getNextAvailableRelayer = async () => + for (const client of relayerClientsForMainNet) { + const relayerStatus = await client.relaySigner.getRelayerStatus(); + if (relayerStatus.numberOfPendingTransactions < LOAD_BALANCE_THRESHOLD) { + return client; + + console.log( + `$relayerStatus.relayerId is busy. Pending transactions: $relayerStatus.numberOfPendingTransactions/$LOAD_BALANCE_THRESHOLD`, + ); + } + return undefined; + }; + + const executeTransaction = async () => + const client = await getNextAvailableRelayer(); + if (!client) throw new Error('Unable to load balance. All relayers are operating above the suggested threshold.'); + + const txResponse = await client.relaySigner.sendTransaction({ + to: '0x179810822f56b0e79469189741a3fa5f2f9a7631', + value: 1, + speed: 'fast', + gasLimit: '21000', + ); + console.log('txResponse', JSON.stringify(txResponse, null, 2)); + + await executeTransaction(); +} + +async function main() + try { + return await loadbalance(); + catch (e) + console.log(`Unexpected error:`, e); + process.exit(1); + +} + +if (require.main === module) + main().catch(console.error); + +``` + +## Security considerations + +All private keys are stored in the AWS Key Management Service. Keys are generated within the KMS and never leave it, i.e., all sign operations are executed within the KMS. Furthermore, we rely on dynamically generated AWS Identity and Access Management policies to isolate access to the private keys among tenants. + +As for API secrets, these are only kept in memory during creation when they are sent to the client. After that, they are hashed and stored securely in AWS Cognito, which is used behind the scenes for authenticating Relayer requests. This makes API keys easy to rotate while preserving the same private key on the KMS. + +### Rollups + +When sending transactions to a rollup chain, such as Arbitrum or Optimism, Relayers currently depend on the chain’s sequencer/aggregator. This means that, if the sequencer goes down or censors transactions, Relayers will not bypass it and commit directly to layer 1. + +## Inactivity + +Testnet relayers are considered inactive if they haven’t sent any transactions in more than 60 days. When a testnet relayer is inactive, we provide a 14-day grace period to mark the relayer as active. If users don’t take any action, the relayer will be automatically deleted once the period is over. diff --git a/docs/content/defender/module/transaction-proposals.mdx b/docs/content/defender/module/transaction-proposals.mdx new file mode 100644 index 00000000..54f5717f --- /dev/null +++ b/docs/content/defender/module/transaction-proposals.mdx @@ -0,0 +1,23 @@ +--- +title: Transaction Proposals +--- + +Transaction Proposals are very similar to actions, but instead of having to write the javascript code, you can use a form-based editor to define the transaction parameters.\ +This low-code format is very useful for non technical users and simple scenarios, but lacks the flexibility of the Actions. If you need to invoke external APIs or contracts, or perform more complex logic, you should use the [Actions](/defender/module/actions) instead. + +## General Information +To create a Transaction Proposal from Defender, you need to define a few parameters: + +* Title: A descriptive name for the proposal. This will be latter shown in the proposal list. +* Description(optional): A longer description of the proposal. This will be shown in the proposal details. +* Target Contract: The smart contract that you want to run the transaction on. + + +If you have trouble connecting your wallet to Defender, there may be a conflict between wallet extensions if multiple are installed. + + +## Function +Define the function that you want to call on the target contract. You can select from a list of functions that are available on the contract interface. If the function has parameters, you can define them here. + +## Approval Process +Define how you want the transaction to be executed. You can choose from any of the [transaction approval processes](/defender/settings#approval-processes) available in Defender that you have previously configured or you can optionally create a new one. diff --git a/docs/content/defender/remix-plugin.mdx b/docs/content/defender/remix-plugin.mdx new file mode 100644 index 00000000..498cf024 --- /dev/null +++ b/docs/content/defender/remix-plugin.mdx @@ -0,0 +1,86 @@ +--- +title: Remix Plugin +--- + +When coding and compiling contracts from [Remix IDE](https://remix.ethereum.org/), you can use Defender Plugin to deploy your contracts by configuring a [Deployment Environment](/defender/module/deploy) and using an Approval Process as deployer. + +## Installation + +1. Go to [Remix IDE](https://remix.ethereum.org/) and click on Plugin manager (bottom left corner). +2. Search **Defender Deploy** from the Modules list, and click "Activate". +3. A new tab in the left nav bar with the OpenZeppelin icon should be displayed. + +![Install Defender Remix Plugin](/defender/remix-plugin-install.png) + +## Usage + +### API Key generation +In your Defender dashboard, go to **Settings -> API Keys** and click **Create API Key**, you only need _Manage Deployments_ permission. + + +We also recommend to set an expiration for the API Key, considering that is going to be used from an external site. + + +![Defender Remix Plugin Api Key](/defender/remix-plugin-api-key.png) + +### Deployment from Remix + +Go to Remix IDE site, and open Defender plugin (see Installation step). + +#### Setup +Set your **API Key** and **API Secret** and press "Authenticate". If the keys are valid, you should see a green tick in at the right indicating that you were succesfully authenticated, also a message in the Remix terminal. + +![Defender Remix Plugin Setup](/defender/remix-plugin-setup.png) + +#### Network +Select any of the supported networks. This also includes private and fork networks configured in your tenant. + +![Defender Remix Plugin Network](/defender/remix-plugin-network.png) + +#### Approval Process +Here you have 3 options: + +* Select an existing approval process from your **Deployment Environment** configured for the selected network. + + +If you have an existing deployment environment in the selected network, this is the only option allowed. + + +* If the **Deployment Envoronment** does not exist for the selected network, then you can create a new one. + + +If the Approval Process to be created is a Relayer, the API Key must include _Manage Relayers_ permission. + + +* Additionally, you can use the **injected provider** from Remix (a browser wallet) to deploy the contract, this will create a Defender **Deployment Environment** under the hood after deploying the contract. + +![Defender Remix Plugin Approval Process](/defender/remix-plugin-approval-process.png) + +#### Deploy +You should see the latest compiled contract along with the constructor inputs. + + +In case you don’t see it, compile the target contract again, the Defender plug-in should detect the compilation and display the contructor inputs. + + + +Upgradable contracts are not yet fully supported. This action will only deploy the implementation contract without initializing. For safe upgrades, we strongly recommend usign [Upgrades Package](https://github.com/OpenZeppelin/openzeppelin-upgrades). + + +![Defender Remix Plugin Deploy](/defender/remix-plugin-deploy.png) + +#### Deterministic Deployments + +Defender Deploy supports a `salt` value to create deployments to deterministic addresses using `create2`. Click on `Deterministic` checkbox and set the salt field to any arbitrary value. + +![Defender Remix Plugin Deploy Deterministic](/defender/remix-plugin-deploy-deterministic.png) + +#### Further Steps + +Once the contract deployment was submitted to Defender, in some cases you will need to complete the deployment from Defender Dashboard, you should see a green banner indicating that the contract was submitted and a link to your Deployment in Defender. + +![Defender Remix Plugin Deploy Completed](/defender/remix-plugin-deploy-completed.png) + +## Feedback + +The Defender Remix Plugin is open source, for feedback related to the plugin, please submit an issue in the [Github Repository](https://github.com/OpenZeppelin/defender-deploy-plugin) or send an email to `defender-support@openzeppelin.com`. diff --git a/docs/content/defender/sdk.mdx b/docs/content/defender/sdk.mdx new file mode 100644 index 00000000..2104ca76 --- /dev/null +++ b/docs/content/defender/sdk.mdx @@ -0,0 +1,78 @@ +--- +title: Defender SDK and API +--- + +[Defender SDK](https://www.npmjs.com/package/@openzeppelin/defender-sdk) (Formerly defender-client packages) is a node package that allows developers to interact with Defender programatically using Javascript/Typescript. + +See [sdk repository](https://github.com/OpenZeppelin/defender-sdk) or [SDK and API documentation](https://www.api-docs.defender.openzeppelin.com/) for more detailed information. + +## Installation + +You can install the whole package using NPM or any of your favorite package managers + +``` +npm install @openzeppelin/defender-sdk +``` + +or you can install single subpackages + +``` +npm install @openzeppelin/defender-sdk-deploy-client +``` + + +For more information about setup, examples and usage, please visit [Defender SDK](https://github.com/OpenZeppelin/defender-sdk) README file in Github. + + +## API Keys + +In order to operate your Defender account using the SDK or API, Defender requires API keys and secrets generated in the dashboard to authenticate requests. + +When creating API keys, you can also specify the expiration in days, hours and minutes. + +![API Key expiration configuration in Defender](/defender/api-key-expiration-config.png) + +Defender notifies ***3 days*** before and ***at expiration time*** about the API keys expiration. + + +Once the key is expired, any request sent to Defender API will throw `API Key is either expired or invalid` error. + + +### Relayer API Keys + +Relayers API Keys are generated in Relayer details page, and are exclusively used for managing the signer operations for that Relayer, e.g. send or query transactions, get the nonce or sign data. + +```js +const creds = + relayerApiKey: , + relayerApiSecret: , +; +const client = new Defender(creds); + +const txResponse = await client.relaySigner.sendTransaction( + to: '0x179810822f56b0e79469189741a3fa5f2f9a7631', + value: 1, + speed: 'fast', + gasLimit: '21000', +); +``` + + +Only `client.relaySigner` package is available when authenticating using `relayerApiKey` and `relayerApiSecret`. + + +### Admin API Keys + +Admin API Keys are generated in ***Manage -> API Keys***, and they are used to operate all other resources in Defender, including relayers CRUD operations. +```js +const creds = + apiKey: , + apiSecret: , +; +const client = new Defender(creds); + +const proposals = await client.proposal.list( + limit: 10, + next: undefined, +); +``` diff --git a/docs/content/defender/settings.mdx b/docs/content/defender/settings.mdx new file mode 100644 index 00000000..01fdbfdd --- /dev/null +++ b/docs/content/defender/settings.mdx @@ -0,0 +1,311 @@ +--- +title: Settings +--- + +Manage everything related to the configuration of Defender, including notifications, addresses, team members, API keys, and more. + +## Approval Processes + +Approval processes act as a wrapper for transactions to be executed on-chain. They currently wrap the following: + +* Multisigs +* EOAs (outside Defender) +* Relayers (inside Defender) +* Governor contracts +* Timelock contracts +* Fireblocks + +They are defined per network, and are used throughout Defender to execute transactions, such as in the [Deploy](/defender/module/deploy) wizard, [Actions](/defender/module/actions) and [Workflows](/defender/module/actions#workflows). + +![Manage Approval Processes](/defender/manage-approvals.png) + +## Notifications + +When triggered, Defender can deliver notifications to one or more configured notification channels which can include any of Slack, email, Telegram, Discord, Datadog, PagerDuty, OpsGenie, or custom webhooks. These notification channels can be used in monitors, actions, and workflows. + +![Manage Notifications](/defender/manage-notify-channels.png) + +### Slack Configuration + +Please see [Slack webhook documentation](https://api.slack.com/messaging/webhooks) to configure a Slack webhook. Once Slack is configured, enter the webhook URL in Defender. + +* **Alias** is the name for this Slack configuration. For instance, you might name it after the name of the channel. +* **Webhook URL** is the URL from your Slack management console to use for notification. + +### Email Configuration + +* **Alias** is the name for this email list. (e.g., Developers) +* **Emails** is the list of emails you wish to notify. These can be comma or semicolon-delimited. + +### Discord Configuration + +Please see [Discord webhook documentation](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) to configure a webhook for your Discord channel. + +* **Alias** is the name for this Discord configuration. +* **Webhook URL** is the URL from your Discord channel to use for notification. + +### Telegram Configuration + +Please see [Telegram bot documentation](https://core.telegram.org/bots#6-botfather) to configure a Telegram Bot using the BotFather. + + +The Telegram Bot must be added to your channel and have the rights to post messages. + + +To find the Chat ID of the channel, execute the following curl (with your bot token value) and extract the `id` value of the chat. If you do not receive any entries in the response, send a test message to your chat first. + +```shell +$ curl https://api.telegram.org/bot$BOT_TOKEN/getUpdates + + "ok": true, + "result": [ + { + "update_id": 98xxxx98, + "channel_post": { + "message_id": 26, + "sender_chat": { + "id": -100xxxxxx5976, + "title": "Monitor Test", + "type": "channel" + , + "chat": + "id": -100xxxxxx5976, // <--- This is your chat ID + "title": "Monitor Test", + "type": "channel" + , + "date": 1612809138, + "text": "test" + } + } + ] +} +``` + +* **Alias** is the name for this Telegram configuration. +* **Chat ID** is the ID of the Telegram Chat. +* **Bot Token** is the token you receive from the BotFather when creating the Telegram Bot. + +### Datadog Configuration + +Datadog configurations let Defender forward custom metrics to your Datadog account. For more information about custom metrics, please see [Datadog metrics documentation](https://docs.datadoghq.com/developers/metrics/) + +The metric we send is a COUNT metric, which represents the number of transactions that triggered the notification. We do not send zeros, so a lack of data should be expected if there is no trigger. With each metric, we send two tags: `network` (rinkeby, mainnet,...) and when a monitor has triggered the notification then `monitor` (name of the monitor) + + +It can take several minutes for a new custom metric to show up in the Datadog console + + +* **Alias** is the name for this Datadog configuration. +* **Api Key** is the API key from your Datadog management. +* **Metric Prefix** will precede all metric names. For instance, with a prefix of `openzeppelin.`, monitors will send a metric called `openzeppelin.monitor`. + +### Webhook Configuration + +To configure a custom webhook notification channel, you just need to provide the webhook endpoint URL and an alias for display purposes. + +* **Alias** is the name for this webhook endpoint. +* **Webhook URL** is the URL where notifications will be sent. + +To avoid overwhelming the receiving webhook with many concurrent requests under a high number of matches, Defender sends a JSON object with an `events` containing an array with all the matching events found in a block. + +```js + + events: [...] // See Event Schema info in Action or Monitor docs + +``` + +For more information on the event schema see the documentation on [Monitor](/defender/module/monitor) or [Actions](/defender/module/actions). + +### OpsGenie Configuration + +Please see [OpsGenie integration documentation](https://support.atlassian.com/opsgenie/docs/create-a-default-api-integration/) to configure an OpsGenie API integration that can create alerts. + +* **API Key** Integration API key that can be found in the integration settings +* **Instance Location** Location where the OpsGenie instance server is located +* **Responders** Teams, users, escalations and schedules that the alert will be routed to send notifications. The type field is mandatory for each item, where possible values are team, user, escalation and schedule. If the API Key belongs to a team integration, this field will be overwritten with the owner team. Either id or name of each responder should be provided. You can refer below for example values (50 teams, users, escalations or schedules) +* **Visible To** Teams and users that the alert will become visible to without sending any notification. The type field is mandatory for each item, where possible values are team and user. In addition to the type field, either id or name should be given for teams and either id or username should be given for users. Please note: that alert will be visible to the teams that are specified within responders field by default, so there is no need to re-specify them within visibleTo field. You can refer below for example values (50 teams or users in total) +* **Alias** Client-defined identifier of the alert, that is also the key element of [Alert De-Duplication](https://support.atlassian.com/opsgenie/docs/what-is-alert-de-duplication/) (512 max characters) +* **Priority** Priority level of the alert. Possible values are P1, P2, P3, P4 and P5. Default value is P3 +* **Entity** Entity field of the alert that is generally used to specify which domain alert is related to (512 max characters) +* **Actions** Custom actions that will be available for the alert (10 x 50 max characters) +* **Note** Additional note that will be added while creating the alert (25000 max characters) +* **Details** Map of key-value pairs to use as custom properties of the alert (8000 max characters) +* **Tags** Tags of the alert (20 x 50 max characters) + +### PagerDuty Configuration + +Please see [PagerDuty integration documentation](https://support.pagerduty.com/docs/services-and-integrations) to configure an PagerDuty API v2 integration that can create change and alert events. + +* **Event Type** Event type for PagerDuty categorization (alert or change) +* **Routing Key** Integration Key for an integration on a service or on a global ruleset (32 characters) +* **Event Action** The action type of event (trigger, acknowledge or resolve) +* **Dedup Key** Deduplication key for correlating triggers and resolves (255 max characters) +* **Severity** The perceived severity of the status the event is describing with respect to the affected system (critical, error, warning or info) +* **Component** Component of the source machine that is responsible for the event +* **Group** Logical grouping of components of a service +* **Class** The class/type of the event +* **Custom_detail** Map of key-value pairs to provide additional details about the event and affected system + +## Team Members + +You can invite, manage access for, and remove team members from your Defender account under the _Team Members_ section. + +![Manage Team Members](/defender/manage-team-invite.png) + + +If you want to add a user to your team, make sure to invite them from the _Team Members_ section. If they sign up directly to the application, they will be added to a new team of their own instead. If this happens, consider having your teammate delete their account, so you can re-send the invitation for your team. Alternatively, they can join your team using a different email address. + + +### Roles + +Every team member has an assigned role. You can manage authorization to access, modify and execute across all Defender products through the role-based access control system. + +When you invite a new user to your team, you will assign a role to them, determining their access permissions. + +To create a new role, click on the _Create Role_ button. You will be asked to enter a role name and description, and to specify the level of access users in that role will get for each product. You can also specify which administrative powers the role will give access to: manage users and roles, manage team API keys, manage Fireblocks API keys, manage Address Book, and configure log forwarding. + +![Manage Role Creation](/defender/manage-role-create.png) + +After saving, the new role will be available to assign to new or existing team members. Naturally, if in the future you decide to modify the access level of a given role, all users who have that role will as a consequence see their access level change. + + +Be careful when granting administrative permissions. A user with the rights to modify roles but not to access any other component can modify their own role to grant them access to any other parts of the application. + + +### Two factor authentication (2FA) + +We strongly suggest that you enable 2FA to improve your Defender account security. As a second authentication factor, Defender relies on the [Time-based One-Time Password standard (TOTP)](https://en.wikipedia.org/wiki/Time-based_One-time_Password_algorithm). To enable 2FA on Defender, you need a TOTP compliant application, such as [Authy](https://authy.com/) or Google Authenticator. Each user can enable 2FA under their profile, accessible from the top-right user menu. Defender will guide users through the necessary steps. + +#### Enforcing 2FA + +As an admin user, you can enforce 2FA for all users in your team. To do so, go to the settings under _Team Members_ section, and click on the _Enforce 2FA_ toggle. This will require all users to setup 2FA before they can access Defender again. + + +If you have users that are still accessing Defender 1.0, they will have to setup 2FA as well. + + +### Password reset + +To change your user password for Defender, follow the steps below. + +* If you are logged in, sign out by opening the upper right corner menu and clicking on **Sign out**. You will be redirected to the landing page. +* From Defender landing page, click on **Sign in**. You will be redirected to the sign in page. +* From Defender sign in page, click on **Forgot your password?**. +* Enter your email address and click on **Reset my password**. You will shortly receive an email with instructions on how to continue with the password reset process. + +## Secrets +Secrets are key-value case-sensitive pairs of strings, that can be accessed from any Action using the `event.secrets` object. You can define as many secrets as you need to be used by your Actions. Secrets are shared across all your Actions, and not specific to a single one. + +```jsx +exports.handler = async function(event) + const { mySecret, anApiKey = event.secrets; +} +``` + +Secrets are encrypted and stored in a secure vault, only decrypted for injection in your actions runs. Once written, a secret can only be deleted or overwritten from the user interface, but not read. + + +An action may log the value of a secret, accidentally leaking it. + + +![Defender Secrets](/defender/manage-secrets.png) + +You can use secrets for storing secure keys to access external APIs, or any other secret value that you do not want to expose in the Actions code. + + +While you can also use actions secrets to store private keys for signing messages or transactions, we recommend you use [Relayers](#Relayers) instead. Signing operations for relayers are executed within a secure vault, providing an extra level of security than loading the private key in an action run and signing there. + + +## API Keys + +In API Keys you can manage the keys used by clients to access the Defender API for your account, and also enter integration API keys if you are using Fireblocks for approvals. Note that relayers have their own API keys that are separate from these API keys and are configured directly in Manage Relayers. + +To add an API key, click on the Create API Key button. + +![Manage Create Team API Key](/defender/manage-new-api-key-v2.png) + +Select the API capabilities that you want associated with the API key: + +* **Manage Transaciton Proposals and Contract** for creating and issuing actions and managing contracts. +* **Manage Relayers** for creating relayers and changing relayer policies. +* **Manage Automatic Actions** for creating and modifying automatic actions and their configurations. +* **Manage Monitors** for creating and managing monitors and their configurations. + +Optionally, select the API Key expiration. You can specify the expiration time in minutes, hours or days. + +Once the API key is created, Defender will show you the details. + +![Manage Team API Key](/defender/manage-api-key-v2.png) + +Be sure to copy the secret key, it will be required for access and it will not be accessible again after the form is dismissed. + +## Custom Networks + +### Forked Networks + +In the "Forked Networks" section, you can manage and oversee your forked networks. These networks let you test the efficiency of your security setup and offer a vital chance to identify and fix any problems before launching on testnets and mainnets. + +![Manage Forked Networks](/defender/manage-forked-networks-create.png) + +Setting up a forked network is accomplished by clicking the "Create Forked Network" button. This action prompts you to provide a name for the forked network and select the base network you intend to fork from. Your choice of forking can be made from any of the networks supported by Defender. The network’s currency symbol will be automatically populated based on the network you select. Additionally, you will need to input the RPC URL for the forked network and, optionally, an API key if it is required for access. + +For an improved user experience, you also have the option to include the block explorer URL. + +Once created, the network becomes accessible for utilization in any Defender module that necessitates network selection. This is particularly valuable when engaging in tasks such as establishing an approval process, configuring a relayer, or deploying a contract. + +![Select Forked Network](/defender/manage-forked-networks-selection.png) + + +Once you have created a Forked Network you cannot edit its name or RPC URL. If you need to change these settings you will need to delete and recreate the Forked Network. When a forked network is deleted, **all** associated resources will also be deleted. This includes approval processes, relayers, contracts, address book entries, etc. + + +### Private Networks + +Navigate through the "Private Networks" section to effectively manage and oversee your private networks. These networks establish a restricted and controlled environment tailored for testing and validating network configurations. This controlled space empowers users to identify and resolve potential issues before deploying configurations to production environments, providing a secure venue to evaluate system functionality and security measures in isolation. + +![Manage Private Networks](/defender/manage-private-networks-create.png) + +To set up a private network, simply click the "Create Private Network" button. This action prompts you to define a name for the private network and select the currency symbol ("ETH") for your network. Additionally, provide the RPC URL, and optionally, an API key if access requires it. + + +The chain ID of a private network must not conflict with the chain ID of an [officially supported Defender network](#networks). + + +For an enhanced user experience, customize your setup by including the block explorer URL, Safe contract deployment addresses, and a subgraph URL. + +[Safe Contracts](https://github.com/safe-global/safe-contracts) form a comprehensive collection of smart contracts designed for deploying, managing, and interacting with multi-signature wallets. Defender utilizes the following Safe contract deployments to enrich the user experience: + +* ***Master***: Facilitates a Safe multisignature wallet deployment with support for confirmations using signed messages based on EIP-712. +* ***Proxy Factory***: Enables a Safe smart contract deployment to create a new proxy contract and execute a message call to the new proxy within a single transaction. +* ***Multi Send Call Only***: Allows a Safe smart contract deployment to batch multiple transactions into one, specifically for calls. +* ***Create Call***: Facilitates a Safe smart contract deployment to utilize different create opcodes for deploying a contract. + +You can [deploy these contracts](https://github.com/safe-global/safe-deployments) on your private network, providing the contract addresses in the creation form to leverage them in Defender, especially when deploying using `CREATE2`. + +Defender utilizes Subgraph for GraphQL-based querying of blockchain data, primarily for the Access Control module. Create your own [Subgraph](https://thegraph.com/docs/en/developing/creating-a-subgraph/), and input the endpoint in the creation form to activate this functionality in Defender. You can find an example configuration for the Access Control subgraph [here](). + +Once created, the network becomes accessible for utilization in any Defender module requiring network selection. This proves invaluable when engaging in tasks such as establishing an approval process, configuring a relayer, or deploying a contract. + +![Select Private Network](/defender/manage-private-networks-selection.png) + + +After creating a Private Network, you cannot edit its name, RPC URL, or symbol. To make changes, you must delete and recreate the Private Network. Deleting a private network will also delete **all** associated resources, including approval processes, relayers, contracts, address book entries, etc. + + +### Limitations + +While custom networks serve as a great way to integrate EVM compatible networks into Defender or to test unique network conditions, they come with a some notable drawbacks that set them apart from officially supported Defender networks: + +* ***RPC Bottlenecks***: Custom networks can only have a single RPC provider which becomes a single point of failure during periods of high traffic. +* ***Limited Reliability***: Custom networks may return non-standard or unexpected responses which will not be handled by Defender. Thus, some services may not behave as expected. +* ***Slower Transaction Processing***: Custom networks require more RPC calls with every transaction request leading to slower transaction processing. + + +High transaction volumes are only advised for custom networks with highly reliable RPC endpoints. + + +## Advanced + +In the Advanced tab, you can export the serverless configuration file from the current configuration for your Defender account. + +This can be used to setup automated resource management for your account with configuration as code. Also, in Advanced, you can delete your Defender account. This action is non-reversible, all Defender configurations will be deleted, and all product functions will be canceled and removed. diff --git a/docs/content/defender/settings/notifications.mdx b/docs/content/defender/settings/notifications.mdx new file mode 100644 index 00000000..c90dd6ee --- /dev/null +++ b/docs/content/defender/settings/notifications.mdx @@ -0,0 +1,120 @@ +--- +title: Notification Channels +--- + +Use Notification Channels to get notified about events across different Defender Modules, like Monitor Triggers, Workflows or Relayer Transactions lifecycle events. + +## Supported Channels + +### From Defender +* **Email**: Receive emails from the Defender trusted address: noreply@defender.openzeppelin.com +* **Webhooks**: Configure your own endpoints to receive signed notifications. + +### Third Party Services +* [**Slack**](https://slack.com/). +* [**Telegram**](https://telegram.org/). +* [**Discord**](https://discord.com/). +* [**Datadog**](https://www.datadoghq.com/). +* [**PagerDuty**](https://www.pagerduty.com/). +* [**Opsgenie**](https://www.atlassian.com/software/opsgenie). + +## Setup + +Go to **Settings -> Notification Channels** section and select and configure your preferred channel. + +![Notification channel creation in Defender](/defender/notification-channel-setup-1.0.png) + +## Usage +The Notification Channel can be linked to any Defender module to get notified about events. + +![Use notification channel in Defender module](/defender/notification-channel-setup-2.0.png) + +Also, it is possible to customize the notification template, [see how](/defender/module/monitor#customizing-notification). + +## Additional configurations + +### Webhook Secrets + +As an additional security measure, Defender implements a Hash-based Message Authentication Code ([HMAC](https://en.wikipedia.org/wiki/HMAC)), by adding a `Defender-Signature` and `Defender-Timestamp` request headers to the notification sent to webhook endpoints. Therefore, the endpoint receiving the notification can verify the authenticity of the request. + +Each webhook notification has a secret key associated that can be accessed under **Settings -> Notification Channnels -> Webhook details**. + +The `Defender-Signature` is generated using [SHA256 algorithm](https://en.wikipedia.org/wiki/SHA-2) and `webhook secret` to sign the payload and the timestamp. + + +Only Admin users in the Account have permission to see the webhook secret. + + +#### Signature Validation + +##### Using Defender SDK + +The authenticity of the signature can be validated using `verifySignature` utility function in [Defender SDK](/defender/sdk). + +```js +function webhookHandler(req, res) + const signature = req.headers['Defender-Signature']; + const timestamp = req.headers['Defender-Timestamp']; + + const defender = new Defender({ + apiKey: process.env.API_KEY, + apiSecret: process.env.API_SECRET, + ); + + const result = client.notificationChannel.verifySignature( + body: req.body, + signature, + timestamp, + secret: process.env.WEBHOOK_SECRET, + validityInMs: 1000 * 60 * 10, // 10 mins + ); + + if (!result.valid) throw new Error(result.error); + + // your handler code +} +``` + +##### Manual Verification + +The signature is generated using HMAC with `SHA256` algorithm, so it can be verified in any programming language using the right `Webhook Secret`. + +##### Python example + + +This code example was tested in Python 3.12. For different versions, the code might be slightly different. + + +```py +from datetime import datetime, timedelta, UTC +import hmac +import hashlib + +def verify_signature(body_object: dict, timestamp: str, signature: str secret: str) -> bool: + # Parse the timestamp + try: + timestamp_dt = datetime.fromisoformat(timestamp) + except ValueError: + return False # Invalid timestamp format + + # Get the current time and calculate the time difference + current_time = datetime.now(UTC) + time_difference = current_time - timestamp_dt + + # Check if the time difference is within the allowed range (10 minutes) + if time_difference > timedelta(minutes=10): + return False + + # Merge timestamp with body_object + payload_to_verify = **body_object, 'timestamp': timestamp + payload_to_verify_str = json.dumps(payload_to_verify, separators=(',', ':')) + + # Create a new HMAC object using the secret and the SHA256 hash algorithm + hmac_obj = hmac.new(secret.encode(), payload_to_verify_str.encode(), hashlib.sha256) + + # Generate signature + generated_signature = hmac_obj.hexdigest() + + # Compare the generated signature with the provided signature + return hmac.compare_digest(generated_signature, signature) +``` diff --git a/docs/content/defender/tutorial/access-control.mdx b/docs/content/defender/tutorial/access-control.mdx new file mode 100644 index 00000000..47893b79 --- /dev/null +++ b/docs/content/defender/tutorial/access-control.mdx @@ -0,0 +1,104 @@ +--- +title: Modify and assign roles in a role-based access control smart contract +--- + +Defender allows you to seamlessly oversee and command contract permissions on a grand scale, with the power to view and control access at a granular level. This tutorial shows how to add a smart contract to see and manage its roles, including assigning and removing roles. + +## Pre-requisites + +* OpenZeppelin Defender account. +* Any external wallet (like Metamask) with an EOA that holds funds in Sepolia. + +## 1. Add contract + + +For this tutorial, you will create a contract that implements the role-based Access Control library using [this](https://sepolia.etherscan.io/address/0xF909B3dBB525fDe7C3e8cd59FbECF3D42c217454) factory deployed to Sepolia. Your created contract will automatically assign you the admin role to manage its roles. + + +1. Open the Defender [Address Book](https://defender.openzeppelin.com/v2/#/address-book/new) in a web browser. +2. Fill the form with the following values and click `Create`: + + * Name: `Access Control Factory` + * Network: `Sepolia` + * Address: `0xF909B3dBB525fDe7C3e8cd59FbECF3D42c217454` + ++ +image::tutorial-access-control-factory.png[Address Book for factory] + +1. Navigate to [Transaction Proposals](https://defender.openzeppelin.com/v2/#/transaction-proposals/new?). +2. Fill the ***General Information*** section with the following values: + + * Name: `Create Access Control contract` + * Target contract: `Access Control Factory` + ++ +image::tutorial-access-control-tx-general.png[Transaction Proposal general information] + +1. For the ***Function*** section, select the `create` function. +2. Open the ***Approval Process*** section, click the input field and select `Create Approval Process`. +3. Fill the approval process form with the following values and click `Save Changes`: + + * Name: `Access Control Admin` + * Type: `EOA` + * Address: _Your wallet EOA address_ +4. Connect your wallet with the EOA address of the approval process created and click `Submit Transaction Proposal`. + + ![Transaction Proposal submit proposal](/defender/tutorial-access-control-submit-proposal.gif) +5. Click on the `Create Access Control contract` transaction proposal. +6. Click the top-right button `Approve and Execute` and confirm the transaction on your wallet. + + ![Transaction Proposal submit tx](/defender/tutorial-access-control-submit-tx.gif) +7. Scroll down and under ***Execution Result***, hover over the first contract to copy its address. + + ![Transaction Proposal copy address](/defender/tutorial-access-control-copy-address.png) +8. Navigate to the Defender https://defender.openzeppelin.com/v2/#/address-book/new Address Book] to add your newly created contract. +9. Fill the form with the following values and click `Create`: + + * Name: `Access Control Contract` + * Network: `Sepolia` + * Address: _Contract address copied from the previous steps_ + * ABI: _Copy and paste the following_ + ++ +```json +["inputs": [],"stateMutability": "nonpayable","type": "constructor","inputs": [],"name": "AccessControlBadConfirmation","type": "error","inputs": [{"internalType": "address","name": "account","type": "address","internalType": "bytes32","name": "neededRole","type": "bytes32"],"name": "AccessControlUnauthorizedAccount","type": "error"},"anonymous": false,"inputs": [{"indexed": true,"internalType": "bytes32","name": "role","type": "bytes32","indexed": true,"internalType": "bytes32","name": "previousAdminRole","type": "bytes32","indexed": true,"internalType": "bytes32","name": "newAdminRole","type": "bytes32"],"name": "RoleAdminChanged","type": "event"},"anonymous": false,"inputs": [{"indexed": true,"internalType": "bytes32","name": "role","type": "bytes32","indexed": true,"internalType": "address","name": "account","type": "address","indexed": true,"internalType": "address","name": "sender","type": "address"],"name": "RoleGranted","type": "event"},"anonymous": false,"inputs": [{"indexed": true,"internalType": "bytes32","name": "role","type": "bytes32","indexed": true,"internalType": "address","name": "account","type": "address","indexed": true,"internalType": "address","name": "sender","type": "address"],"name": "RoleRevoked","type": "event"},"inputs": [],"name": "DEFAULT_ADMIN_ROLE","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"],"stateMutability": "view","type": "function"},"inputs": [],"name": "RANDOM_ROLE","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"],"stateMutability": "view","type": "function"},"inputs": [{"internalType": "bytes32","name": "role","type": "bytes32"],"name": "getRoleAdmin","outputs": ["internalType": "bytes32","name": "","type": "bytes32"],"stateMutability": "view","type": "function"},"inputs": [{"internalType": "bytes32","name": "role","type": "bytes32","internalType": "address","name": "account","type": "address"],"name": "grantRole","outputs": [],"stateMutability": "nonpayable","type": "function"},"inputs": [{"internalType": "bytes32","name": "role","type": "bytes32","internalType": "address","name": "account","type": "address"],"name": "hasRole","outputs": ["internalType": "bool","name": "","type": "bool"],"stateMutability": "view","type": "function"},"inputs": [{"internalType": "bytes32","name": "role","type": "bytes32","internalType": "address","name": "callerConfirmation","type": "address"],"name": "renounceRole","outputs": [],"stateMutability": "nonpayable","type": "function"},"inputs": [{"internalType": "bytes32","name": "role","type": "bytes32","internalType": "address","name": "account","type": "address"],"name": "revokeRole","outputs": [],"stateMutability": "nonpayable","type": "function"},"inputs": [{"internalType": "bytes4","name": "interfaceId","type": "bytes4"],"name": "supportsInterface","outputs": ["internalType": "bool","name": "","type": "bool"],"stateMutability": "view","type": "function"}] +``` + +1. Navigate to the [Access Control page](https://defender.openzeppelin.com/v2/#/access-control/contracts). +2. Observe your newly added contract with the number addresses that hold the admin role. + + ![Access Control page with contract](/defender/tutorial-access-control-page.gif) +3. Click on the contract card. + +## 2. View and modify roles + +In your contract-specific page, you can see the addresses that hold the `DEFAULT_ADMIN_ROLE` role, which is the EOA address from the approval process you used to deploy the contract. To make a change, click on the role and input the new address (or remove one address if you want to remove it from the role). Follow these steps to add a new address to the `DEFAULT_ADMIN_ROLE`: + +1. Click on the `DEFAULT_ADMIN_ROLE` role. +2. Select any address from the dropdown menu or add a new one. +3. Scroll down and click on `Select an Approval Process`. +4. Select your `Access Control Admin` approval process. +5. Check that your wallet is connected with the right EOA address. If not, click on the button below the field to connect your wallet. +6. Click on `Save Changes` and confirm the transaction on your wallet. +7. Wait for the transaction to get executed and check that the new address holds the `DEFAULT_ADMIN_ROLE` role. + ++ +image::tutorial-access-control-add.gif[Access Control page of contract add role] + +For ownable contracts, you can only make changes to the `Owner` role using an approval process that matches the current owner’s address. When using a multisig as approval process, you will see the pending proposals on the right side of the page. + +The page sync every minute, and updates when modifying a role. + +## Next steps + +Congratulations! You can import other contracts and modify their roles. + + +After configuring Access Control, we recommend seting up Workflows. Learn how to use Workflows with its tutorial [here](/defender/tutorial/workflows). + + +## References + +* [Access Control Documentation](/defender/module/access-control) +* [Access Control Factory](https://sepolia.etherscan.io/address/0xF909B3dBB525fDe7C3e8cd59FbECF3D42c217454) +* [Access Control Contract](https://sepolia.etherscan.io/address/0x1b073085c60ace585c4179984b3be5bf9ef53176) diff --git a/docs/content/defender/tutorial/actions.mdx b/docs/content/defender/tutorial/actions.mdx new file mode 100644 index 00000000..8b190c52 --- /dev/null +++ b/docs/content/defender/tutorial/actions.mdx @@ -0,0 +1,77 @@ +--- +title: Automate smart contract transaction for hourly activity +--- + +Defender allows you to automate smart contract operational tasks with easy integration with the rest of Defender. This tutorial shows how to build an action that sends an on-chain transaction every hour that adds an object to a box and increases the number of objects inside. + +## Pre-requisites + +* OpenZeppelin Defender account. + + +Learn to [deploy](/defender/tutorial/deploy) and [monitor](/defender/tutorial/monitor) contracts through Defender! + + +## 1. Set up action + +You will configure an action that sends an hourly transaction with the `addObject()` function to the `0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074` contract in Sepolia, which is an example of a Box contract deployed in the [Deploy](/defender/tutorial/deploy) tutorial. To do so, follow these steps: + +1. Run the following command in your terminal: +2. Open [Defender Relayers](https://defender.openzeppelin.com/v2/#/relayers) in a web browser. +3. Click on **Create Relayer** with the name `Actions Relayer` and Sepolia network. +4. Fund it with some Sepolia ETH. This relayer will send and pay for the automated transactions. +5. Open [Defender Actions](https://defender.openzeppelin.com/v2/#/actions). +6. Click on **Create Action**. +7. Name the action `Hourly object add`. +8. Select **Schedule** as trigger, and a **Timespan** of 1 hour as schedule. +9. Select the **Actions Relayer** as the connected relayer. +10. Paste the following code in the **Code** field: + + ```jsx + const Defender = require('@openzeppelin/defender-sdk'); + + exports.handler = async function(credentials) + const client = new Defender(credentials); + + const txRes = await client.relaySigner.sendTransaction({ + to: '0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074', + speed: 'fast', + data: '0x62029d2a', + gasLimit: '80000', + ); + + console.log(txRes); + return txRes.hash; + } + ``` + The contract address is the target, which is `0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074`, and data is `0x62029d2a`, the encoded version of the `addObject()` function of the contract. + +11. Click on **Save Action**. + +After saving, the Actions page should look like this: + +![Actions action card](/defender/tutorial-actions-action.png) + +Your action will now be running every hour! You can check the [Defender Logs](https://defender.openzeppelin.com/v2/#/logs) for more detailed information about the activity. + +## 2. Verify activity + +After the action has run for a while, you can verify that the transaction is being sent every hour. To do so, open the [Etherscan contract page](https://sepolia.etherscan.io/address/0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074) and look for transactions from the configured Relayer. An alternative is to search your Relayer in Etherscan and look for the transactions sent to the contract. + +You will also receive alerts in case the action fails (for example, if the Relayer runs out of gas). They look like this: + +![Actions alert](/defender/tutorial-actions-alert.png) + +## Next steps + +Congratulations! You can modify the action to automate other contracts and build more complex transactions. In case you are interested in advanced use cases, we are working on actions-related guides. + + +Alongisde actions, we recommend using Access Control to manage a contract’s permissions through Defender. Learn how to use Access Control with its tutorial [here](/defender/tutorial/access-control). + + +## References + +* [Actions Documentation](/defender/module/actions) +* [More information on Action’s Javascript code](/defender/module/actions#defining-code) +* [BoxV2 Sepolia contract](https://sepolia.etherscan.io/address/0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074) diff --git a/docs/content/defender/tutorial/deploy.mdx b/docs/content/defender/tutorial/deploy.mdx new file mode 100644 index 00000000..243ee1f0 --- /dev/null +++ b/docs/content/defender/tutorial/deploy.mdx @@ -0,0 +1,420 @@ +--- +title: Securely deploy and upgrade a smart contract +--- + +Defender allows you to easily deploy and upgrade smart contracts across chains while maintaining the best security practices. This tutorial shows how to use a [Relayer](/defender/module/relayers) to deploy a contract called Box and upgrade it with the UUPS proxy pattern via a [Safe wallet](https://safe.global/) (multisig). + +## Pre-requisites + +* OpenZeppelin Defender account. +* [NodeJS and NPM](https://nodejs.org/en) +* Any IDE or text editor +* Web browser with Metamask (or any other compatible wallet), funded with Sepolia ETH. + +## 1. Configure + +### Safe wallet + +First, you need to create Safe wallet to manage the upgrade process. To do so, follow these steps: + +1. Open the [Safe app](https://app.safe.global/welcome) in a web browser and connect your wallet (make sure you are connected to the [Sepolia testnet](https://sepolia.etherscan.io/)). +2. Click on **Create new Account** and follow the steps. +3. Note the address of the Safe wallet you created, you will need it later. + + ![Deploy safe copy address](/defender/tutorial-deploy-safe.png) + +### Environment setup + +Now, you will create a Defender test environment with the Sepolia testnet, where you will deploy and upgrade the smart contracts. To do so, follow these steps: + +1. Open [Defender Deploy](https://defender.openzeppelin.com/v2/#/deploy). +2. Click on **Setup**. + + ![Deploy environments page](/defender/tutorial-deploy-environments.png) +3. Pick **Sepolia** from the dropdown. + + ![Deploy networks wizard](/defender/tutorial-deploy-step1-wizard.png) +4. Select the approval process associated with your funded relayer that will execute the deployments for the test environment. In case you don’t already have an approval process, Defender will allow you to create one within the wizard flow. Relayers automate the payment of gas fees and take care of private key secure storage, transaction signing, nonce management, gas pricing estimation, and resubmissions. However, you may also choose to deploy using an EOA ("Externally Owned Account") or Safe wallet. + + + Read more about relayers and how to manage them [here](/defender/module/relayers). + + ++ +image::tutorial-deploy-step2-wizard.png[Deploy block deploy wizard] + +1. Click on the Approval Process field to expand the dropdown and click on **Create an Approval Process**. Enter "Safe Wallet Approval Process" as the name and expand the contract field to click on **Add contract**. Enter "Safe Wallet" as the name, paste the address of your Safe wallet you copied before and click on **Create**. Select "Safe Wallet" in the contract dropdown and click on **Continue**. + ++ +image::tutorial-deploy-step3-wizard.png[Deploy block upgrade wizard] + +1. Defender will generate an API Key and Secret for this environment, so copy and store them safely. Click on **Let’s Deploy** to visit the environment page. + ++ +image::tutorial-deploy-step4-wizard.png[Deploy block end wizard] + + +You configured the test environment to learn without the risk of losing actual funds. The steps are the same to set up a production environment. + + +Defender supports both Hardhat and Foundry integrations. Pick the one that suits your project! + +### Foundry setup + +First, make sure you have [Foundry](https://book.getfoundry.sh/getting-started/installation) installed. Follow these steps to create a new directory and project: + +1. Run the this command in your terminal: + + ``` + forge init deploy-tutorial && cd deploy-tutorial && forge install foundry-rs/forge-std && forge install OpenZeppelin/openzeppelin-foundry-upgrades && forge install OpenZeppelin/openzeppelin-contracts-upgradeable + ``` +2. Now, configure the `foundry.toml` file to enable ffi, ast, build info and storage layout: + + ```json + [profile.default] + ffi = true + ast = true + build_info = true + extra_output = ["storageLayout"] + ``` +3. Create a new file called `.env` in the project root directory and add the following content with the keys you received after creating the Defender environment: + + ```json + DEFENDER_KEY = "<>" + DEFENDER_SECRET = "<>" + ``` + +### Hardhat setup + +First, make sure you have [Hardhat](https://hardhat.org/hardhat-runner/docs/getting-started#installation) installed with ethers v6. Follow these steps to create a new directory and project: + +1. Run the this command in your terminal: + + ``` + mkdir deploy-tutorial && cd deploy-tutorial && npx hardhat init + ``` +2. Hardhat will ask some questions to setup the configuration, so answer the following: ++ + * What do you want to do: Create a **Typescript** project + * Hardhat project root: _Leave it as it is_ + * Do you want to use .gitignore: Yes + * Do you want to install this sample project’s dependencies with npm: Yes +3. Hardhat will now install the tooling libraries, and create the project files for you. Afterwards, install the OpenZeppelin packages with the following command: + + ``` + npm i @openzeppelin/hardhat-upgrades @openzeppelin/contracts-upgradeable dotenv --save-dev + ``` + ++ +Once everything has been installed, your initial directory structure should look something like this: + ++ +image::tutorial-deploy-directory.png[Deploy directory structure,185,300] + +1. You now need to edit your Hardhat configuration to add the Defender keys and Sepolia network. Open the `hardhat.config.ts` file, and replace its content with the following code: + + ```jsx + import HardhatUserConfig from "hardhat/config"; + import "@nomicfoundation/hardhat-toolbox"; + import "@openzeppelin/hardhat-upgrades"; + + require("dotenv").config(); + + const config: HardhatUserConfig = + solidity: "0.8.20", + defender: { + apiKey: process.env.DEFENDER_KEY as string, + apiSecret: process.env.DEFENDER_SECRET as string, + , + networks: + sepolia: { + url: "https://ethereum-sepolia.publicnode.com", + chainId: 11155111 + , + }, + }; + + export default config; + ``` +2. Create a new file called `.env` in the project root directory and add the following content with the keys you received after creating the Defender environment: + + ```json + DEFENDER_KEY = "<>" + DEFENDER_SECRET = "<>" + ``` + +## 2. Deploy + +1. Create a new file called `Box.sol` inside the `contracts` or `src` directory and add the following code: + + ```jsx + // SPDX-License-Identifier: Unlicense + pragma solidity ^0.8.20; + + import Initializable from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + import UUPSUpgradeable from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + import OwnableUpgradeable from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + + /// @title Box + /// @notice A box with objects inside. + contract Box is Initializable, UUPSUpgradeable, OwnableUpgradeable + /*////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /// @notice Number of objects inside the box. + uint256 public numberOfObjects; + + /*////////////////////////////////////////////////////////////// + FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice No constructor in upgradable contracts, so initialized with this function. + function initialize(uint256 objects, address multisig) public initializer { + __UUPSUpgradeable_init(); + __Ownable_init(multisig); + + numberOfObjects = objects; + + + /// @notice Remove an object from the box. + function removeObject() external + require(numberOfObjects > 1, "Nothing inside"); + numberOfObjects -= 1; + + + /// @dev Upgrades the implementation of the proxy to new address. + function _authorizeUpgrade(address) internal override onlyOwner {} + } + ``` + + This is a contract that replicates a box, with three functions: + + * `initialize()`: Initializes the upgradeable proxy with its initial implementation and sets the multisig as the owner. + * `removeObject()`: Decreases the number of objects in the box by removing one. + * `_authorizeUpgrade()`: Points the proxy to a new implementation address. + +### Foundry + +1. Create a file named `Deploy.s.sol` inside the `script` directory. This script will deploy the upgradeable Box contract through Defender with an initial amount of 5 objects inside and the owner as the multisig address configured in the environment setup. The `initializer` option is used to call the `initialize()` function after the contract is deployed. Copy and paste the code below into `Deploy.s.sol`: + + ```jsx + // SPDX-License-Identifier: Unlicense + pragma solidity ^0.8.20; + + import Script from "forge-std/Script.sol"; + import console from "forge-std/console.sol"; + + import Defender, ApprovalProcessResponse from "openzeppelin-foundry-upgrades/Defender.sol"; + import Upgrades, Options from "openzeppelin-foundry-upgrades/Upgrades.sol"; + + import Box from "src/Box.sol"; + + contract DefenderScript is Script + function setUp() public { + + function run() public + ApprovalProcessResponse memory upgradeApprovalProcess = Defender.getUpgradeApprovalProcess(); + + if (upgradeApprovalProcess.via == address(0)) { + revert( + string.concat( + "Upgrade approval process with id ", + upgradeApprovalProcess.approvalProcessId, + " has no assigned address" + ) + ); + + + Options memory opts; + opts.defender.useDefenderDeploy = true; + + address proxy = + Upgrades.deployUUPSProxy("Box.sol", abi.encodeCall(Box.initialize, (5, upgradeApprovalProcess.via)), opts); + + console.log("Deployed proxy to address", proxy); + } + } + ``` +2. Deploy by running the following command which executes your deployment script: + + ``` + forge script script/Deploy.s.sol --force --rpc-url https://ethereum-sepolia.publicnode.com + ``` + +### Hardhat + +1. Open the file `deploy.ts` inside the `scripts` directory. This script will deploy the upgradeable Box contract through Defender with an initial amount of 5 objects inside and the owner as the multisig address configured in the environment setup. The `initializer` option is used to call the `initialize()` function after the contract is deployed. Copy and paste the code below into `deploy.ts`: + + ```jsx + import ethers, defender from "hardhat"; + + async function main() + const Box = await ethers.getContractFactory("Box"); + + const upgradeApprovalProcess = await defender.getUpgradeApprovalProcess(); + + if (upgradeApprovalProcess.address === undefined) { + throw new Error(`Upgrade approval process with id ${upgradeApprovalProcess.approvalProcessId has no assigned address`); + } + + const deployment = await defender.deployProxy(Box, [5, upgradeApprovalProcess.address], initializer: "initialize" ); + + await deployment.waitForDeployment(); + + console.log(`Contract deployed to $await deployment.getAddress()`); + } + + // We recommend this pattern to be able to use async/await everywhere + // and properly handle errors. + main().catch((error) => + console.error(error); + process.exitCode = 1; + ); + ``` + + + You should use `deployProxy()`, `deployBeacon()` and `deployImplementation()` for upgradeable contracts, and `deployContract()` for non-upgradeable contracts. To forcefully use `deployContract()`, set the `unsafeAllowDeployContract` option to `true`. More information [here](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/docs/modules/ROOT/pages/defender-deploy). + +2. Deploy your box by running the following command which executes your deployment script: + + ``` + npx hardhat run --network sepolia scripts/deploy.ts + ``` + +Success! Your contracts should have been deployed in the Sepolia testnet. Navigate to Deploy in Defender and check that the proxy and implementation have been deployed inside the test environment. All Box transactions should be sent to the proxy address as it will store the state and point to the given implementation. Copy the address of the proxy to upgrade it next. + +![Deployed contract](/defender/tutorial-deploy-contract.png) + +### Caveats + +By default, Defender utilizes the `CREATE` opcode to deploy contracts. This method creates a new contract instance and assigns it a unique address. This address is determined by the transaction’s nonce and sender’s address. + +Defender also offers an advanced deployment option using the `CREATE2` opcode. When a deployment request includes a `salt`, Defender switches to using the `CREATE2` opcode. This opcode allows you to deploy contracts to a deterministic address based on a combination of the sender’s `address`, `salt`, and contract `bytecode`. + + +While `CREATE2` offers deterministic contract addresses, it alters `msg.sender` behavior. In `CREATE2` deployments, `msg.sender` in the constructor or initialization code refers to the factory address, not the deploying address as in standard `CREATE` deployments. This distinction can impact contract logic, so careful testing and consideration are advised when opting for `CREATE2` + + +## 3. Upgrade + +Upgrading a smart contract allows changing its logic while maintaining the same address and storage. + +1. Create a file called `BoxV2.sol` inside the `contracts` or `src` directory and add the following code: + + ```jsx + // SPDX-License-Identifier: Unlicense + pragma solidity ^0.8.20; + + import Box from "./Box.sol"; + + /// @title BoxV2 + /// @notice An improved box with objects inside. + /// @custom:oz-upgrades-from Box + contract BoxV2 is Box + /*////////////////////////////////////////////////////////////// + FUNCTIONS + //////////////////////////////////////////////////////////////*/ + + /// @notice Add an object to the box. + function addObject() external { + numberOfObjects += 1; + + + /// @notice Returns the box version. + function boxVersion() external pure returns (uint256) + return 2; + + } + ``` + + This is a contract adds two new functions to your box: + + * `addObject()`: Increases the number of objects in the box by adding one. + * `boxVersion()`: Returns the version of the box implementation. + +### Foundry + +1. Create a file called `Upgrade.s.sol` inside the `script` directory and paste the following code. Make sure to replace the `` with the address of the proxy you copied before. + + ```jsx + // SPDX-License-Identifier: Unlicense + pragma solidity ^0.8.20; + + import Script from "forge-std/Script.sol"; + import console from "forge-std/console.sol"; + + import ProposeUpgradeResponse, Defender, Options from "openzeppelin-foundry-upgrades/Defender.sol"; + + contract DefenderScript is Script + function setUp() public { + + function run() public + Options memory opts; + ProposeUpgradeResponse memory response = Defender.proposeUpgrade( + , + "BoxV2.sol", + opts + ); + console.log("Proposal id", response.proposalId); + console.log("Url", response.url); + + } + ``` +2. Create the upgrade proposal using the upgrade script with the the following command: + + ``` + forge script script/Upgrade.s.sol --force --rpc-url https://ethereum-sepolia.publicnode.com + ``` + +### Hardhat + +1. Create a file called `upgrade.ts` inside the `scripts` directory and paste the following code. Make sure to replace the `` with the address of the proxy you copied before. + + ```jsx + import ethers, defender from "hardhat"; + + async function main() + const BoxV2 = await ethers.getContractFactory("BoxV2"); + + const proposal = await defender.proposeUpgradeWithApproval('', BoxV2); + + console.log(`Upgrade proposed with URL: ${proposal.url`); + } + + // We recommend this pattern to be able to use async/await everywhere + // and properly handle errors. + main().catch((error) => + console.error(error); + process.exitCode = 1; + ); + ``` +2. Create the upgrade proposal using the upgrade script with the the following command: + + ``` + npx hardhat run --network sepolia scripts/upgrade.ts + ``` + +### Approve + +1. Navigate to the [Defender test environment](https://defender.openzeppelin.com/v2/#/deploy/environment/test) and click on the upgrade proposal, which expands a modal on the right side of the screen. +2. Click on **View Transaction Proposal** and click on **Approve and Execute** on the top right corner of the page. Sign and execute the transaction with your wallet that you used to create the Safe Wallet. + +Your box should now be upgraded to the new version! The upgrade proposal in your test environment page shold now be marked as **Executed**. + +![Uprade proposal executed](/defender/tutorial-deploy-executed-upgrade.png) + +## Next steps + +Congratulations! You can now deploy and upgrade other contracts using the same environment. In case you are interested in advanced use cases, we are working on deploy-related guides. + + +After deploying a contract, we recommended using Defender to monitor its state and transactions. Learn how to use Monitor [here](/defender/tutorial/monitor). + + +## References + +* [Deploy Documentation](/defender/module/deploy) +* [Foundry Upgrades Package](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades) +* [Hardhat Upgrades Package](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades) +* [Upgrades Core Package](https://www.npmjs.com/package/@openzeppelin/upgrades-core) diff --git a/docs/content/defender/tutorial/monitor.mdx b/docs/content/defender/tutorial/monitor.mdx new file mode 100644 index 00000000..01ad4fed --- /dev/null +++ b/docs/content/defender/tutorial/monitor.mdx @@ -0,0 +1,81 @@ +--- +title: Monitor a smart contract for on-chain activity +--- + +Defender allows you to monitor smart contract transactions and events across chains. This tutorial shows how to build a customized Monitor template and use it in a real-world context to monitor a [Uniswap V2](https://uniswap.org/) pool. + +## Pre-requisites + +* OpenZeppelin Defender account. + + +Learn to deploy contracts to monitor using Defender [here](/defender/tutorial/deploy)! + + +## 1. Configure the monitor + +You will monitor the `0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc` contract in the Ethereum mainnet, which is the [Uniswap V2 USDC-ETH pool](https://etherscan.io/address/0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc). This contract has constant activity, making it a good candidate to see how quick monitors are. To configure a monitor, follow these steps: + +1. Open [Defender Monitor](https://defender.openzeppelin.com/v2/#/monitor) in a web browser. +2. Click on **Create Monitor**. + + ![Monitor landing page](/defender/tutorial-monitor-landing.png) +3. Name this monitor as `Uniswap V2: USDC-ETH Monitor`. +4. Select the `Financial` risk category. +5. Click the **Contracts** field and select to add a new address. +6. Fill the form with the following parameters and select it as contract to monitor: + ++ +* Name: `Uniswap V2: USDC-ETH Pool` +* Network: `Mainnet` +* Address: `0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc` + +1. Select `1 confirmation block`. Defender will automatically pick up the ABI, so we can select the transaction filters next. + ++ +image::tutorial-monitor-first.png[Monitor added contract] + +1. Add the `status == "success"` parameter to **Transaction Properties** to filter by transaction-level data and confirm transactions are successfully confirmed and not reverted. + ++ +image::tutorial-monitor-transaction-filters.png[Monitor transaction filters] + +1. Select the `Swap` event from the dropdown menu. This event is emitted every time a swap is made in the pool. + ++ +image::tutorial-monitor-event-filter.png[Monitor event filter] + +1. Skip function-level filters as you are already tracking all `Swap` events emitted from the contract. +2. Select a notification channel of your choice (such as email). +3. Click on **Save Monitor**. + ++ +image::tutorial-monitor-alerts.png[Monitor alerts] + +Your monitor is now running! + +![Monitor card](/defender/tutorial-monitor-card.png) + +## 2. Receive alerts + +Alerts will start rolling in as long as the monitor is active. Your notifications should look like this if you selected email as the notification channel: + +![Monitor Telegram alert](/defender/tutorial-monitor-receive.png) + +You can pause or delete monitors on the [Defender Monitor](https://defender.openzeppelin.com/v2/#/monitor) page. This one will trigger frequently so you will likely want to pause it using the toggle on the right after you receive a couple of alerts. You can also save a monitor as a template by clicking on the dotted icon of its card and `Save as Template`. + +![Monitor save template](/defender/tutorial-monitor-save-template.png) + +## Next steps + +Congratulations! You can modify the monitor to filter for specific `Swap` data or target another pool. In case you are interested in advanced use cases, we are working on monitor-related guides. + + +After setting up a monitor, we recommend creating Actions on Defender. Learn how to use Actions with its tutorial [here](/defender/tutorial/actions). + + +## References + +* [Actions Documentation](/defender/module/actions) +* [Manage Notificacion Channels Documentation](/defender/settings#notifications) +* [Uniswap V2 USDC-ETH Pool](https://etherscan.io/address/0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc) diff --git a/docs/content/defender/tutorial/relayer.mdx b/docs/content/defender/tutorial/relayer.mdx new file mode 100644 index 00000000..96f161bf --- /dev/null +++ b/docs/content/defender/tutorial/relayer.mdx @@ -0,0 +1,153 @@ +--- +title: Using Relayer for Sending Transactions +--- + +In this tutorial, we will explore how to use the [Relayers](/defender/module/relayers) to send transactions. We will cover: + +* Checking relayer information. +* Sending a transaction. +* Checking the relayer status. + +By the end of this tutorial, you will have a basic understanding of how to use the Relayer to interact with smart contracts. + +## Pre-requisites + +* OpenZeppelin Defender account. +* [NodeJS and NPM](https://nodejs.org/en) +* [Typescript for Node js](https://www.npmjs.com/package/ts-node) +* Any IDE or text editor + +## 1. Set Up Relayer + +Let’s start by creating a Relayer: + +* Open [Defender Operate & Automate](https://defender.openzeppelin.com/#/relayers) in a web browser. + + ![Manage Relayer](/defender/tutorial-relayer-step1.png) +* Click on **Create Relayer** with the name ETH Sepolia Relayer and Sepolia network. + + ![Create Relayer](/defender/tutorial-relayer-step2.png) +* Fund it with some Sepolia ETH. This relayer will send and pay for the automated transactions. +* To create an API key for a Relayer, click on the Relayer and then on the **More** button to expand the dropdown and select **Create API Key** + + ![Create Relayer API](/defender/tutorial-relayer-step3.png) +* Now you can set API key expiration in minutes, hours or days. + + ![Save Relayer API](/defender/tutorial-relayer-step3-1.png) +* Once the API Key is created, make sure to write down the secret key. The API secret is only visible once during the creation — if you don’t write it down, it’s lost forever. + + ![Save Relayer API](/defender/tutorial-relayer-step4.png) + +## 2. Check Relayer Information + +* Let’s start by checking the information of our relayer. +* Add Relayer in `.env` File +* Edit `.env` file in your project root directory and add your Relayer API Key and Secret: + + ```jsx + RELAYER_API_KEY=your_api_key + RELAYER_SECRET_KEY=your_api_secret + ``` +* Create a file named `storeObject.ts` in the project. +* Now let’s add the following code: + + ```jsx + const Defender = require('@openzeppelin/defender-sdk'); + + const dotenv = require('dotenv'); + + dotenv.config(); + + async function main() + + const client = new Defender({ + + relayerApiKey: process.env.RELAYER_API_KEY, + relayerApiSecret: process.env.RELAYER_SECRET_KEY, + ); + + const info = await client.relaySigner.getRelayer(); + console.log('Relayer Info:', JSON.stringify(info, null, 2)); + + } + + main().catch((error) => + console.error(error); + process.exitCode = 1; + ); + + ``` +* Execute the script to check the relayer information: + + ```jsx + ts-node storeObject.ts + ``` + + Alternatively, if you want to use Node directly: +```jsx +node storeObject.js +``` + +## 3. Send Transaction + +Next, we will send a transaction using the relayer. + +* Let’s edit the same file and add the following code: + + ```jsx + const tx = await client.relaySigner.sendTransaction( + to: '0x1B9ec5Cc45977927fe6707f2A02F51e1415f2052', + speed: 'fast', + data: '0x6057361d000000000000000000000000000000000000000000000000000000000000000a', + gasLimit: '80000', + ); + console.log('Transaction sent! Hash:', tx.hash); + ``` + +Here we are using the Sepolia [Box contract](https://sepolia.etherscan.io/address/0x1B9ec5Cc45977927fe6707f2A02F51e1415f2052) as the target, which is: +```jsx +0x1B9ec5Cc45977927fe6707f2A02F51e1415f2052 +``` + +and data is the encoded version of the store() function with ‘10’ as input parameter. +```jsx +0x6057361d000000000000000000000000000000000000000000000000000000000000000a +``` + +* Execute the script to send a transaction: + + ```jsx + ts-node storeObject.ts + ``` + + Alternatively, if you want to use Node directly: +```jsx +node storeObject.js +``` + +## 4. Check Transaction Status + +Finally, let’s check the status of our transaction status. + +* Edit the file again and add the following code: + + ```jsx + const txUpdate = await client.relaySigner.getTransaction(tx.transactionId); + console.log('Tx Status', JSON.stringify(txUpdate, null, 2)); + ``` +* Execute the script to check the relayer status: + + ```jsx + ts-node storeObject.ts + ``` + + Alternatively, if you want to use Node directly: +```jsx +node storeObject.js +``` + +## 5. Next Steps +Congratulations! You have successfully used the Relayer to check information, send transactions, and verify the transaction status. By following this tutorial, you have gained a fundamental understanding of how to interact with smart contracts using the Relayer. + +* For more information on using Relayer, refer to the [Relayers](/defender/module/relayers) documentation. +* Explore the [Actions](/defender/tutorial/actions) to automate your smart contract operational tasks with easy integration with the rest of Defender. diff --git a/docs/content/defender/tutorial/workflows.mdx b/docs/content/defender/tutorial/workflows.mdx new file mode 100644 index 00000000..06256081 --- /dev/null +++ b/docs/content/defender/tutorial/workflows.mdx @@ -0,0 +1,115 @@ +--- +title: Create an Action Workflow to decrease the number of objects in a Box contract +--- + +Defender allows you to target and activate on-chain activity using Action Workflow quickly. This tutorial shows how to create a workflow that monitors the number of objects in a Box contract and executes an action when an object is added to it. + +## Pre-requisites + +* OpenZeppelin Defender account. + +## 1. Action setup + +In this tutorial, you will monitor [this](https://sepolia.etherscan.io/address/0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074) contract in Sepolia, which stores a number of objects while allowing anyone to add or remove objects using the `addObject()` and `removeObject()` functions respectively. For every object added, your workflow will execute an action that removes an object and decreases the total by one. To set up the action, follow these steps: + +1. Open [Defender Relayers](https://defender.openzeppelin.com/v2/#/relayers/new) in a web browser. +2. Fill the form with the following parameters and click on **Create**: + + * **Name**: `Relayer Sepolia` + * **Network**: `Sepolia` +3. Transfer some Sepolia ETH to the relayer address created in the previous step. +4. Navigate to [Defender Address Book](https://defender.openzeppelin.com/v2/#/address-book/new) to import the `BoxV2` contract. +5. Fill the form with the following parameters and click on **Create**: + + * **Name**: `BoxV2` + * **Network**: `Sepolia` + * **Address**: `0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074` +6. Navigate to [Defender Workflows Transaction Template creation page](https://defender.openzeppelin.com/v2/#/actions/workflows/transaction-template/new?). +7. Fill the ***General Information*** section with the following parameters: + + * **Name**: `Remove object` + * **Contract**: `BoxV2` +8. Select the `removeObject` function from the dropdown menu in the ***Function*** section. +9. Expand the dropdown on the ***Approval Process*** section and click on `Create Approval Process`. +10. Fill the form with the following parameters and click on **Save Changes**: + + * **Name**: `BoxV2 IR Sender` + * **Relayer**: `Relayer Sepolia` (created in the first step) +11. Select `BoxV2 IR Sender` as the approval process and click on `Save Transaction Template` + ++ +image::tutorial-workflow-first-action.png[Workflow page with Transaction Template] + +## 2. Workflow setup + +With the action configured, you now need to create the workflow. To do so, follow these steps: + +1. Open the [Defender Workflows creation page](https://defender.openzeppelin.com/v2/#/actions/workflows/new). +2. Rename the workflow `Remove from BoxV2 if Object is Added`. +3. Drag the `Remove object` action to the first row. +4. Click on **Save**. + ++ +image::tutorial-workflow-scenario.png[BoxV2 workflow] + +## 3. Monitor setup + +After creating the workflow, you need to configure a monitor that keeps track of the number of objects in the BoxV2 contract to trigger the workflow. To do so, follow these steps: + +1. Open the [Defender Monitor creation page](https://defender.openzeppelin.com/v2/#/monitor/new/custom). +2. Fill the ***General Information*** section with the following parameters: + + * **Name**: `BoxV2 Objects Monitor` + * **Risk Category**: `Suspicious Activity` + * **Contract**: `BoxV2` + * **Confirmation Blocks**: `Confirmed (1 blocks)` + ++ +image::tutorial-ir-first-monitor.png[Workflow Monitor General Information] + +1. In the ***Transaction Filters*** section, add `status == "success"` for the `Transaction properties` field. +2. In the ***Function*** section, select `addObject()` +3. Within the ***Alerts*** section, select the `Remove from BoxV2 if Object is Added` workflow for the `Execute a Workflow` option. + ++ +image::tutorial-ir-monitor.png[Workflow BoxV2 Objects monitor] + +1. Click on **Save Monitor**, which will start running. + +## 4. Seeing it in action + +While the monitor runs, it will detect any transaction that matches the `addObject()` function to trigger the workflow. To manually execute such a transaction, follow these steps: + +1. Open the [Defender Transaction Proposal creation page](https://defender.openzeppelin.com/v2/#/transaction-proposals/new?). +2. Fill the form with the following parameters: + + * **Name**: `BoxV2 Add Object Trigger` + * **Contract**: `BoxV2` + * **Function**: `addObject` + * **Approval Process**: `BoxV2 IR Sender` +3. Click on **Submit Transaction Proposal**. + ++ +image::tutorial-ir-proposal-action.png[Transaction Proposal Trigger] + +1. Click on the transaction proposal to open its page. +2. Click on the top-right button **Approve and Execute** to execute the transaction, which will trigger the workflow through the monitor. +3. Wait for the transaction to be executed and open the [Defender Workflows page](https://defender.openzeppelin.com/v2/#/actions/workflows). + ++ +image::tutorial-workflow-active-scenario.png[Active Workflow] + +1. Click on **View Active Run** and check the details of your workflow response. +2. After the run is executed successfully, you can verify the response by checking the activity of the contract on [Etherscan](https://sepolia.etherscan.io/address/0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074). It should look like this: + ++ +image::tutorial-ir-etherscan.png[Workflow Etherscan Response] + +## Next steps + +Congratulations! You now have a complete workflow that will be running and checking every confirmed block. Workflows can be expanded with parallel actions for more technical combinations. In case you are interested in advanced use cases, we are working on Workflow-related guides. + +## References + +* [Workflow Documentation](/defender/module/actions#workflows) +* [BoxV2](https://sepolia.etherscan.io/address/0xC64f7ace6127bc7B0bAb23bD1871aC81e6AEC074) diff --git a/docs/content/defender/wizard-plugin.mdx b/docs/content/defender/wizard-plugin.mdx new file mode 100644 index 00000000..477f59bf --- /dev/null +++ b/docs/content/defender/wizard-plugin.mdx @@ -0,0 +1,85 @@ +--- +title: Contracts Wizard Deploy Plugin +--- + +When configuring contracts from [Contracts Wizard](https://wizard.openzeppelin.com/), you can directly deploy the configured Smart Contract using your Defender account. + +## Usage + +### API Key generation +In your Defender dashboard, go to **Settings -> API Keys** and click **Create API Key**, you only need _Manage Deployments_ permission. + + +We also recommend to set an expiration for the API Key, considering that is going to be used from an external site. + + +![Defender Remix Plugin Api Key](/defender/remix-plugin-api-key.png) + +### Deployment from Contracts Wizard + +Go to [Contracts Wizard](https://wizard.openzeppelin.com/) site, and after editing your contract, click on "Deploy with Defender". + +![Defender Wizard Plugin Getting Started](/defender/wizard-plugin-start.png) + +#### Configure +Set your **API Key** and **API Secret** and press "Authenticate". You should see a message below the button indicating that the credentials are valid. + +![Defender Wizard Plugin Configure](/defender/wizard-plugin-configure.png) +![Defender Wizard Plugin API keys](/defender/wizard-plugin-configure-2.png) + +#### Network +Select any of the supported networks. This also includes private and forked networks configured in your tenant. + +![Defender Wizard Plugin Network](/defender/wizard-plugin-network-2.png) + +#### Approval Process +Here you have 3 options: + +* Select an existing approval process from your **Deployment Environment** configured for the selected network. + + +If you have an existing deployment environment in the selected network, this is the only option allowed. + + +* If the **Deployment Envoronment** does not exist for the selected network, then you can create a new one. + + +If the Approval Process to be created is a Relayer, the API Key must include _Manage Relayers_ permission. + + +* Additionally, you can use the **injected provider** from Remix (a browser wallet) to deploy the contract, this will create a Defender **Deployment Environment** under the hood after deploying the contract. + +![Defender Wizard Plugin Approval Process](/defender/wizard-plugin-approval-process.png) + +#### Deploy +In this step, you should see the constructor inputs of your configured contract (if any), and the option to create a deterministic deployment. + + +This step is reactive, if you modify the contract you will see the new constructor arguments updated right after. + + + +Upgradable contracts are not yet fully supported. This action will only deploy the implementation contract without initializing. For safe upgrades, we strongly recommend usign [Upgrades Package](https://github.com/OpenZeppelin/openzeppelin-upgrades). + + +![Defender Wizard Plugin Deploy](/defender/wizard-plugin-deploy.png) + +#### Deterministic Deployments + +Defender Deploy supports a `salt` value to create deployments to deterministic addresses using `CREATE2`. Click on `Deterministic` checkbox and set the salt field to any arbitrary value. + + +If the approval process selected is a Multisig, the `salt` is required as Defender only support deterministic deployments when using Multisigs. + + +![Defender Wizard Plugin Deterministic Deployments](/defender/wizard-plugin-deterministic.png) + +#### Further Steps + +Once the contract deployment is submitted to Defender, in some cases you may need to complete the deployment from Defender Dashboard. You will see message indicating that the contract was submitted and a button that redirects to your Deployment in Defender. + +![Defender Wizard Plugin Further Steps](/defender/wizard-deploy-further-steps.png) + +## Feedback + +The Defender Deploy Plugin is open source, for feedback related to the plugin, please submit an issue in the [Github Repository](https://github.com/OpenZeppelin/defender-deploy-plugin) or send an email to `defender-support@openzeppelin.com`. diff --git a/docs/content/impact/chain-abstraction.mdx b/docs/content/impact/chain-abstraction.mdx new file mode 100644 index 00000000..dcb55680 --- /dev/null +++ b/docs/content/impact/chain-abstraction.mdx @@ -0,0 +1,117 @@ +--- +title: Chain Abstraction at OpenZeppelin +--- + +Our work focuses on building the secure primitives for cross-chain messaging, intent-based execution, and unified account experiences, creating a world where users and assets move freely across ecosystems with complexity abstracted away from users. + +## Ecosystem Contributions + +We have partnered with ecosystems and projects to deliver contracts libraries and tooling for cross-chain coordination and smart accounts. + + + + Contracts libraries for modular smart accounts in Solidity + + + + Contracts libraries for cross-chain messaging in Solidity + + + + Contracts libraries in Solidity and solvers for cross-chain intents + + + + Contracts libraries for smart accounts in Cairo + + + + Contracts libraries for smart accounts in Soroban + + + + Cryptography contracts libraries in Solidity for transactions and social recovery using email + + + +## Cross-Chain Intents + +Universal format for expressing and fulfilling user actions across chains. + +### Use Cases + +- **Simplified Onboarding to Chains and Apps**: Onboard users by automating gas, approvals, and cross chain setup, allowing users to interact with any app or network instantly without prior configuration. + +- **Abstracted Gas and Settlement**: Allows operation across multiple chains without managing native gas tokens or complex bridge flows. + +- **Token Bridging and Swapping**: Enable seamless asset transfers and swaps across chains, abstracting away bridge risks and fragmented liquidity. + +- **Universal Balances**: Users can maintain a single cross chain balance, allowing applications to fetch, display, and transact from unified liquidity sources. + +### Standards + +- **ERC-7683: Cross Chain Intents** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7683) that enables cross chain value transfer using a standard api. OpenZeppelin is a contributor to and leading the redesign of the ERC through the [Open Intents Framework](https://www.openintents.xyz) and is building contracts libraries in the [Open Intents Framework Contracts Repo](https://github.com/openintentsframework/oif-contracts). + +- **ERC-7888: Cross Chain Broadcaster** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7888) that enables cross chain messaging using storage proofs. OpenZeppelin is a contributor to the ERC through the [Open Intents Framework](https://www.openintents.xyz) and is building contracts libraries in the [Open Intents Framework Broadcaster Repo](https://github.com/openintentsframework/broadcaster). + +### Associations + +- **Open Intents Framework**: The [group](https://www.openintents.xyz/) is building a modular, open source framework for building and deploying intent product experiences by providing ready-to-use, protocol-agnostic features for solvers, interop providers, and cross-chain builders. OpenZeppelin is a [member](https://www.openintents.xyz/#1976d35200d68051bfe5f0bdb3ced9bc) with many major organizations. + +## Cross-Chain Messaging + +Common interface for sending and receiving messages across chains. + +### Use Cases + +- **State Synchronization for Multi-Chain Apps**: Use single controller contract or governance action to coordinate updates on multiple chains. + +- **Token Bridges**: Enables verifiable message passing between bridge contracts, ensuring consistent state and transfer logic across multiple chains without relying on trusted intermediaries. + +- **Intent Settlement**: Facilitates secure delivery and confirmation of intent execution results across chains, allowing applications to finalize actions and synchronize states. + +### Standards + +- **ERC-7786: Cross Chain Messaging Gateway** (last call): A [standard](https://eips.ethereum.org/EIPS/eip-7786) that enables cross-chain messaging via a universal gateway interface. OpenZeppelin team members co-authored the standard with Axelar and is building contracts libraries in the [OpenZeppelin Solidity Community Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-community-contracts/tree/master/contracts/crosschain). + +## Smart Accounts + +Composable architecture that enables customizable modules to support secure and extensible account functionality. + +### Use Cases + +- **Gasless Transactions**: Allow users to interact with apps without holding native tokens, enabling seamless onboarding. + +- **Social Recovery**: Allow trusted guardians to securely restore access to accounts without centralized intermediaries. + +- **Keyless Signatures**: Authenticate and approve transactions using biometrics, hardware modules, or passkeys instead of a traditional private key. + +### Standards + +- **ERC-4337: Account Abstraction Using Alt Mempool** (final): A [standard](https://eips.ethereum.org/EIPS/eip-4337) that enables account abstraction using an alternative mempool. OpenZeppelin has built contracts libraries for modular smart accounts in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/account) and [Wizard](https://wizard.openzeppelin.com/#account) integration. + +- **ERC-7579: Minimal Modular Smart Accounts** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7579) that enables interoperability between accounts and modules. OpenZeppelin has built contracts libraries for modules in the [OpenZeppelin Solidity Community Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-community-contracts/tree/master/contracts/account/modules) and [Wizard](https://wizard.openzeppelin.com/#account) integration. + +- **EIP-7702: Set Code for EOAs** (complete): A [protocol standard](https://eips.ethereum.org/EIPS/eip-7702) that enables EOA’s to adopt smart contract capabilities using a new transaction type to set code in their account. OpenZeppelin has built contracts libraries for modules in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/signers/SignerEIP7702.sol) and [Wizard](https://wizard.openzeppelin.com/#account) integration. + +- **ERC-7739: Readable Typed Signatures for Smart Accounts** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7739) for a defensive rehashing scheme which prevents signature replays across smart accounts and preserves the readability of the signed contents. OpenZeppelin team members co-authored the standard and is building contracts libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/signers/draft-ERC7739.sol). + +- **ERC-7821: Minimal Batch Executor Interface** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7821) for a minimal batch executor interface for delegations. OpenZeppelin team members co-authored the standard and is building contracts libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/account/extensions/draft-ERC7821.sol). + +- **ERC-7913: Signature Verifiers** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7913) that enables signature verification for address-less keys (e.g. email, non-ethereum cryptographic curves). OpenZeppelin team members authored the standard and is building contracts libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/utils/cryptography/verifiers). + +- **SNIP-6: Standard Account Interface** (review): A [standard](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md) for a standard interface for accounts. OpenZeppelin team members co-authored the standard and is building contracts libraries in the [OpenZeppelin Staknet Cairo Contracts Repo](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/account). + +## Private Email Verification + +Ownership of accounts using email. + +### Use Cases + +- **Send Crypto Using Only Email Address**: Users can authorize transactions (e.g. send money, DAO voting, any blockchain transaction) by proving control of their email address with no private key management required. Email never revealed! + +- **Recover Account Using Email Gaurdians**: Lost keys can be restored by proving control of an email account, enabling user-friendly recovery. + +### Standards + +- **ERC-7969: DomainKeys Identified Mail (DKIM) Registry** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7969) that enables trustless email ownership verification using a DKIM restistry. OpenZeppelin team members co-authored with OKX and is building contracts libraries in the [OpenZeppelin Solidity Community Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/utils/cryptography/DKIMRegistry.sol). diff --git a/docs/content/impact/contracts-for-financial-institutions.mdx b/docs/content/impact/contracts-for-financial-institutions.mdx new file mode 100644 index 00000000..a2802489 --- /dev/null +++ b/docs/content/impact/contracts-for-financial-institutions.mdx @@ -0,0 +1,125 @@ +--- +title: OpenZeppelin Contracts for Financial Institutions +--- + +OpenZeppelin Contracts is the trusted foundation powering the on-chain economy. + +Used by the world’s leading stablecoin issuers, asset managers, and on-chain funds, OpenZeppelin provides the security-audited, production-proven contracts trusted to secure billions in value. + +| **Category** | **Coverage** | **Value Secured** | **Examples** | +| --- | --- | --- | --- | +| Tokenized Funds | 10 of the top 10 by market capitalization | Over $5 billion | [BlackRock BUIDL-I](https://etherscan.io/token/0x6a9da2d710bb9b700acde7cb81f10f1ff8c89041#code), [Circle USYC](https://etherscan.io/token/0x136471a34f6ef19fe571effc1ca711fdb8e49f2b#code), [Franklin BENJI](https://etherscan.io/token/0x3ddc84940ab509c11b20b76b466933f40b750dc9#code) | +| Stablecoins | 8 of the top 10 by market capitalization | Over $55 billion | [Circle USDC](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#code), [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code), [Sky USDS](https://etherscan.io/token/0xdc035d45d973e3ec169d2276ddab16f1e407384f#code) | + +## Powering Top Institutions + +### Tokenized Funds + +Trusted by 10 of the top 10 Tokenized Funds by Market Capitalization. + +The standard for compliant, programmable representation of the world’s most trusted asset class. + +| **Tokenized Fund** | **Tokens** | **Permissions** | **Upgradeability** | **Utilities** | +| --- | --- | --- | --- | --- | +| [BlackRock BUIDL-I](https://etherscan.io/token/0x6a9da2d710bb9b700acde7cb81f10f1ff8c89041#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [BlackRock BUIDL](https://etherscan.io/token/0x7712c34205737192402172409a8f7ccef8aa2aec#code) | 🪙 | | | 🧰 | +| [Centrifuge JTRSY](https://etherscan.io/token/0x8c213ee79581ff4984583c6a801e5263418c4b86#code) | 🪙 | | | 🧰 | +| [Circle USYC](https://etherscan.io/token/0x136471a34f6ef19fe571effc1ca711fdb8e49f2b#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [Franklin BENJI](https://etherscan.io/token/0x3ddc84940ab509c11b20b76b466933f40b750dc9#code) | | 🔐 | ♻️ | 🧰 | +| [Ondo OUSG](https://etherscan.io/token/0x1b19c19393e2d034d8ff31ff34c81252fcbbee92#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [Ondo USDY](https://etherscan.io/token/0x96f6ef951840721adbf46ac996b59e0235cb985c#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [OpenEden TBILL](https://etherscan.io/token/0xdd50c053c096cb04a3e3362e2b622529ec5f2e8a#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [Superstate USTB](https://etherscan.io/token/0x43415eb6ff9db7e26a15b704e7a3edce97d31c4e#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [WisdomTree WTGXX](https://etherscan.io/token/0x1fecf3d9d4fee7f2c02917a66028a48c6706c179#code) | | 🔐 | | 🧰 | + +### Stablecoins + +Trusted by 8 of the top 10 Stablecoins by Market Capitalization. + +The secure foundation behind the digital assets that power payments, settlement, and global liquidity. + +| **Stablecoin** | **Tokens** | **Permissions** | **Upgradeability** | **Utilities** | +| --- | --- | --- | --- | --- | +| [Circle USDC](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code) | 🪙 | 🔐 | | 🧰 | +| [Ethena USDtb](https://etherscan.io/token/0xc139190f447e929f090edeb554d95abb8b18ac1c#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [First Digital USD](https://etherscan.io/token/0xc5f0f7b66764f6ec8c8dff7ba683102295e16409#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [PayPal USD](https://etherscan.io/token/0x6c3ea9036406852006290770bedfcaba0e23a0e8#code) | | 🔐 | ♻️ | 🧰 | +| [Ripple RLUSD](https://etherscan.io/token/0x8292bb45bf1ee4d140127049757c2e0ff06317ed#code) | 🪙 | 🔐 | ♻️ | 🧰 | +| [Sky USDS](https://etherscan.io/token/0xdc035d45d973e3ec169d2276ddab16f1e407384f#code) | | | ♻️ | 🧰 | +| [Usual USD0](https://etherscan.io/token/0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5#code) | 🪙 | 🔐 | ♻️ | 🧰 | + +## Next Evolution of Smart Contracts + +The next era of smart contracts will be defined by compliance and privacy. + +OpenZeppelin is shaping that future by co-developing open standards and contract frameworks that enable regulated, confidential, and yield-bearing financial systems. Learn more about [tokenization and real world assets at OpenZeppelin](/impact/tokenization-and-real-world-assets) + +| Focus Area | Purpose | Standards | Associations | Networks Supported | +| --- | --- | --- | --- | --- | +| Permissioned Tokens | Enforce compliant issuance and management of institutional-grade digital assets. | [ERC-3643: T-REX – Token for Regulated Exchanges](https://eips.ethereum.org/EIPS/eip-3643) | [3643 Association](https://www.erc3643.org/) (member) | [EVM](https://github.com/ERC-3643/ERC-3643), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa), [EVM confidential (Zama)](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token/ERC7984/extensions) | +| Confidential Tokens | Confidential value transfers with full auditability using encrypted pointers | [ERC-7984: Confidential Fungible Token](https://github.com/ethereum/ERCs/pull/1113/files#diff-4c22c7cb34e9622c46198c95e8810399c8bf545126ef800b3a464083629aa6e5) | [Confidential Token Association](https://www.confidentialtoken.org/) (founding member) | [EVM confidential (Zama)](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token/ERC7984) | +| Yield Bearing Vaults | Yield generation and tokenized deposits | [ERC-4626: Tokenized Vaults](https://eips.ethereum.org/EIPS/eip-4626) | | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol), [EVM (with fees)](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626Fees.sol), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/vault), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20/extensions/erc4626) | + +## Battle Tested Contracts + +Every OpenZeppelin library represents over a decade of security expertise, community validation, and production use. + +### Tokens + +Define and manage on-chain assets. + +Our libraries provide the secure, extensible foundation for issuing, controlling, and auditing digital assets. + +| Standard / Extension | Purpose | Example Use (Stablecoin, Tokenized Fund, Defi) | Number of Deployments | Networks Supported | +| --- | --- | --- | --- | --- | +| Fungible Tokens (ERC-20) | Base standard for digital assets | [Circle USDC](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code#F17#L23), [Ondo OUSG](https://etherscan.io/address/0x1ceb44b6e515abf009e0ccb6ddafd723886cf3ff#code#F12#L2), [Lido](https://github.com/lidofinance/core/blob/005b0876d6594b7f7864e0577cdaa44eff115b73/contracts/0.8.9/WithdrawalVault.sol#L9) | 150,000+ ($30 trillion+ in total value transferred!) | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token/ERC20), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/token/erc20) | +| Permit (ERC-2612) | Gasless transfer approvals through signatures to streamline user experience | [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code#F4#L2), [BlackRock BUIDL-I](https://etherscan.io/address/0x9e2693f54831f6f52b0bb952c2935d26919a3626#code#F10#L2), [Optimism](https://github.com/ethereum-optimism/optimism/blob/3a34b538a190547e99a5571f3d02fc72d9ccb4ca/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol#L6) | 35,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Permit.sol), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/blob/main/packages/token/src/erc20/snip12_utils/permit.cairo), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/contracts/src/token/erc20/extensions/permit.rs) | +| Metadata | Provide information about the token, including name, symbol, and decimals | [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code#F8#L2), [Ondo OUSG](https://etherscan.io/address/0x1ceb44b6e515abf009e0ccb6ddafd723886cf3ff#code#F15#L2), [Uniswap](https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/SafeCurrencyMetadata.sol#L4) | 30,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/IERC20Metadata.sol), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/contracts/src/token/erc20/extensions/metadata.rs) | +| Pausable | Pause contract or transfers during emergencies or upgrades to reduce operational and systemic risk | [First Digital Labs FDUSD](https://etherscan.io/address/0xda1814d75ef1c42d0a4e6abe0d43d49a1d300c8d#code#F5#L2), [Ondo USDY](https://etherscan.io/address/0xea0f7eebdc2ae40edfe33bf03d332f8a7f617528#code#F17#L2), [Morpho](https://github.com/morpho-org/idle-tranches-morpho/blob/43e6e0fb44e2b61f1429c3e23d1221179e8ba108/contracts/StakingRewards.sol#L6) | 17,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Pausable.sol), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/pausable), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/blob/main/packages/security/src/pausable.cairo) | +| Burnable | Destroy tokens to support supply control, redemptions, or error recovery | [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code#F3#L2), [Ondo OUSG](https://etherscan.io/address/0x1ceb44b6e515abf009e0ccb6ddafd723886cf3ff#code#F10#L2), [Aave](https://github.com/aave/ccip/blob/0ddce1bbc784cbdc808d0fe9672129f168097607/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol#L11) | 2,000+ | [EVM,](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Burnable.sol) [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/extensions/burnable), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/contracts/src/token/erc20/extensions/burnable.rs) | +| Freezable | Freeze specific accounts or tokens to help enforce sanctions, compliance holds, or fraud mitigation | - | - | [EVM](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Freezable.sol) | +| Restricted | Transfer restrictions, including blacklisting and/or whitelisting, to ensure only approved entities can interact with the token as defined by compliance policy | - | - | [EVM](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/token/ERC20/extensions/ERC20Restricted.sol) | + +### Permissions + +Define who can perform specific actions, when they can do so, and under what authority. + +Our libraries provide flexible, auditable permissions for enforcing operational, compliance, and governance policies on-chain. + +| Implementation | Purpose | Example Use (Stablecoin, Tokenized Fund, Defi) | Number of Deployments | Networks Supported | +| --- | --- | --- | --- | --- | +| Ownable | Minimal governance model providing a single administrative authority | [Circle USDC](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code#F13#L31), [BlackRock BUIDL-I](https://etherscan.io/address/0x9e2693f54831f6f52b0bb952c2935d26919a3626#code#F2#L2), [Aave](https://github.com/aave/aave-v3-periphery/blob/master/contracts/treasury/AaveEcosystemReserveController.sol#L4) | 80,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/access/src/ownable), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/access/src/ownable), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/contracts/src/access/ownable.rs) | +| Access Control | Role-based governance which supports structured permissioning and multiple roles for operational teams | [Ethena USDtb](https://etherscan.io/address/0x9d6d77a21702b9afcf924983fbfb84aaaae79589#code#F17#L2), [Ondo OUSG](https://etherscan.io/address/0x1ceb44b6e515abf009e0ccb6ddafd723886cf3ff#code#F11#L2), [Aerodrome](https://github.com/aerodrome-finance/relay/blob/main/src/Relay.sol#L16) | 35,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/access/src/access_control), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/access/src/accesscontrol), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/access/control) | + +### Upgradability + +Securely upgrade contract logic without disrupting state or user trust. + +Our libraries implement proven proxy patterns that support controlled evolution under defined governance rules. + +| Implementation | Purpose | Example Use (Stablecoin, Tokenized Fund, Defi) | Number of Deployments | Networks Supported | +| --- | --- | --- | --- | --- | +| Beacon Proxy | Coordinated upgrades across multiple contracts through a shared beacon, allowing system-wide upgrades in a single transaction. | [Sky USDS](https://etherscan.io/token/0xdc035d45d973e3ec169d2276ddab16f1e407384f#code#F4#L2), [BlackRock BUIDL-I](https://etherscan.io/token/0x6a9da2d710bb9b700acde7cb81f10f1ff8c89041#code#F4#L2) | 75,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/beacon), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/proxy/beacon) | +| Transparent Proxy | Administrator-managed upgrades with strict separation between users and governance | [Ethena USDtb](https://etherscan.io/token/0xc139190f447e929f090edeb554d95abb8b18ac1c#code#F1#L2), [Ondo OUSG](https://etherscan.io/token/0x1b19c19393e2d034d8ff31ff34c81252fcbbee92#code#F2#L2), [Lido](https://github.com/lidofinance/core/blob/master/contracts/0.8.4/WithdrawalsManagerProxy.sol#L317) | 9,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) | +| UUPS Proxy | Lightweight upgrade pattern where governance controls upgrade logic directly | [PayPal PYUSD](https://etherscan.io/address/0x94d0f384d839a7cef8bb6a8be3e2541ec9355343#code#F12#L2), [Circle USYC](https://etherscan.io/address/0xe6b0c4f8766abf8f77ad00c27fb00cef81ccc9af#code#F6#L2), [Morpho](https://github.com/morpho-org/morpho-token/blob/main/src/DelegationToken.sol#L12) | 4,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/blob/main/contracts/src/proxy/utils/uups_upgradeable.rs) | + +### Utilities + +Libraries for precision, reliability, and data integrity across all operations. + +Our libraries provide functions for cryptography, math, and data integrity, safeguarding every calculation and transaction on-chain. + +| Implementation | Purpose | Example Use (Stablecoin, Tokenized Fund, Defi) | Number of Deployments | Networks Supported | +| --- | --- | --- | --- | --- | +| Data Integrity | Safe primitives such as storage, context, and verification to prevent data corruption or manipulation | [Ethena USDtb](https://etherscan.io/address/0x9d6d77a21702b9afcf924983fbfb84aaaae79589#code#F13#L2), [BlackRock BUIDL-I](https://etherscan.io/address/0x9e2693f54831f6f52b0bb952c2935d26919a3626#code#F16#L2), [Graph Protocol](https://github.com/graphprotocol/contracts/blob/109e84c71fd01f2cb51f8582bb43603881380072/packages/token-distribution/contracts/MinimalProxyFactory.sol#L5) | 150,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/utils), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/utils/src) | +| Math | Ensures precision arithmetic and overflow protection for on-chain calculations | [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code#F19#L2), [BlackRock BUIDL-I](https://etherscan.io/address/0x9e2693f54831f6f52b0bb952c2935d26919a3626#code#F14#L2), [Morpho](https://github.com/morpho-org/metamorpho/blob/main/src/MetaMorpho.sol#L20) | 65,000+ | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/utils/math), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/math), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/utils/src) | +| Cryptography | Safe primitives such as ECDSA, Merkle proofs, and signature verification for secure identity and transaction validation | [Circle USDC](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code#F19#L28), [Superstate USTB](https://etherscan.io/address/0x1f50a1ee0ec8275d0c83b7bb08896b4b47d6e8c4#code#L301), [Graph Protocol](https://github.com/graphprotocol/contracts/blob/109e84c71fd01f2cb51f8582bb43603881380072/packages/horizon/contracts/utilities/Authorizable.sol#L6) | 45,000+ | [EVM,](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/utils/cryptography) [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/crypto), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/utils/src/cryptography) | + +## Talk to an Expert + +Whether you’re launching a stablecoin, tokenizing assets, or building institutional-grade infrastructure, our team can help you design with security, compliance, and scalability from day one. + +Connect with OpenZeppelin experts to discuss your project, evaluate architectures, and access the libraries and audits trusted by the world’s leading financial institutions. + +[Talk to an Expert](https://www.openzeppelin.com/financial-institutions/request) diff --git a/docs/content/impact/contracts-future.mdx b/docs/content/impact/contracts-future.mdx new file mode 100644 index 00000000..a3263321 --- /dev/null +++ b/docs/content/impact/contracts-future.mdx @@ -0,0 +1,21 @@ +--- +title: The Future of OpenZeppelin Contracts +--- + +The next decade of on-chain innovation will bring regulated finance, privacy-preserving systems, and programmable capital to the same foundation. Building on a [decade of impact](/impact/contracts-impact), OpenZeppelin is extending its standards, contracts libraries, and tooling to support this evolution. + +## Focus Areas + + + + Cross-chain intents, cross-chain messaging, smart accounts, and zkEmail + + + + Confidential tokens, private shared state, and private email verifiction + + + + Permissioned tokens, confidential tokens, and on-chain yield + + diff --git a/docs/content/impact/contracts-impact.mdx b/docs/content/impact/contracts-impact.mdx new file mode 100644 index 00000000..36865a62 --- /dev/null +++ b/docs/content/impact/contracts-impact.mdx @@ -0,0 +1,141 @@ +--- +title: The Impact of OpenZeppelin Contracts +--- + +For over ten years, OpenZeppelin Contracts has been the trusted foundation for the on-chain economy. + +## Broad Ecosystem Coverage + +OpenZeppelin Contracts powers secure smart contract development across the leading blockchain ecosystems and continues to be selected to develop contracts libraries for emerging ecosystems. + + + + Smart contracts in Solidity + + + + High-performance smart contracts in Rust on the EVM + + + + Privacy-preserving smart contracts in Rust + + + + Privacy-preserving smart contracts in Compact + + + + Smart contracts and parachain runtimes for Polkadot and Substrate in Solidity + + + + Smart contracts in Cairo + + + + Smart contracts in Soroban + + + + Smart contracts in Move + + + + Customize Uniswap V4 hooks in Solidity + + + + Confidential smart contracts using fully homomorphic encryption in Solidity + + + +## Proven Adoption + +### On-Chain Usage and Economic Impact + +OpenZeppelin Contracts is the most deployed and economically significant smart contract framework in the EVM ecosystem. Explore our [Dune Data Dashboard](https://dune.com/openzeppelin/openzeppelin-contracts-metrics) for more data. + + + + In total value transferred through OpenZeppelin based ERC-20 and native tokens + + + + In total value locked, representing 88% market share across top EVM assets + + + + Verified contract deployments across Ethereum and major Layer 2 networks + + + +### Adopted by the Industry’s Most Trusted Protocols + +Used by the world’s leading financial institutions, DeFi protocols, and blockchain networks, OpenZeppelin Contracts secures trillions in value and remains the most widely adopted smart contract framework in the world. + + + + Including [Lido](https://github.com/lidofinance/core/blob/005b0876d6594b7f7864e0577cdaa44eff115b73/contracts/0.8.9/WithdrawalVault.sol#L9), [Uniswap](https://github.com/Uniswap/v4-periphery/blob/main/src/libraries/SafeCurrencyMetadata.sol#L4), [Aave](https://github.com/aave/ccip/blob/0ddce1bbc784cbdc808d0fe9672129f168097607/contracts/src/v0.8/shared/token/ERC20/BurnMintERC20.sol#L11), [Optimism](https://github.com/ethereum-optimism/optimism/blob/3a34b538a190547e99a5571f3d02fc72d9ccb4ca/packages/contracts-bedrock/src/universal/OptimismMintableERC20.sol#L6), [Morpho](https://github.com/morpho-org/idle-tranches-morpho/blob/43e6e0fb44e2b61f1429c3e23d1221179e8ba108/contracts/StakingRewards.sol#L6), [The Graph](https://github.com/graphprotocol/contracts/blob/109e84c71fd01f2cb51f8582bb43603881380072/packages/horizon/contracts/utilities/Authorizable.sol#L6), [Aerodrome](https://github.com/aerodrome-finance/relay/blob/main/src/Relay.sol#L16) + + + + [BlackRock BUIDL-I](https://etherscan.io/token/0x6a9da2d710bb9b700acde7cb81f10f1ff8c89041#code), [BlackRock BUIDL](https://etherscan.io/token/0x7712c34205737192402172409a8f7ccef8aa2aec#code), [Centrifuge JTRSY](https://etherscan.io/token/0x8c213ee79581ff4984583c6a801e5263418c4b86#code), [Circle USYC](https://etherscan.io/token/0x136471a34f6ef19fe571effc1ca711fdb8e49f2b#code), [Franklin BENJI](https://etherscan.io/token/0x3ddc84940ab509c11b20b76b466933f40b750dc9#code), [Ondo OUSG](https://etherscan.io/token/0x1b19c19393e2d034d8ff31ff34c81252fcbbee92#code), [Ondo USDY](https://etherscan.io/token/0x96f6ef951840721adbf46ac996b59e0235cb985c#code), [OpenEden TBILL](https://etherscan.io/token/0xdd50c053c096cb04a3e3362e2b622529ec5f2e8a#code), [Superstate USTB](https://etherscan.io/token/0x43415eb6ff9db7e26a15b704e7a3edce97d31c4e#code), [WisdomTree WTGXX](https://etherscan.io/token/0x1fecf3d9d4fee7f2c02917a66028a48c6706c179#code) + + + + [Circle USDC](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#code), [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code), [Ethena USDtb](https://etherscan.io/token/0xc139190f447e929f090edeb554d95abb8b18ac1c#code), [First Digital USD](https://etherscan.io/token/0xc5f0f7b66764f6ec8c8dff7ba683102295e16409#code), [PayPal USD](https://etherscan.io/token/0x6c3ea9036406852006290770bedfcaba0e23a0e8#code), [Ripple RLUSD](https://etherscan.io/token/0x8292bb45bf1ee4d140127049757c2e0ff06317ed#code), [Sky USDS](https://etherscan.io/token/0xdc035d45d973e3ec169d2276ddab16f1e407384f#code), [Usual USD0](https://etherscan.io/token/0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5#code) + + + +### The Industry’s Developer Standard + +OpenZeppelin has become the benchmark for how modern smart contracts are written, tested, and maintained. + + + + Average weekly NPM downloads + + + + Across all Github repositories + + + +## Standards Contributions + +OpenZeppelin has helped drive standardization in the industry by authoring standards across ecosystems. + +### Ethereum + +- **ERC-1271: Standard Signature Validation Method for Contracts**: A [standard](https://eips.ethereum.org/EIPS/eip-1271) to verify a signature when the account is a smart contract. OpenZeppelin team members co-authored the standard and built contracts libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC1271.sol). +- **ERC-1967: Proxy Storage Slots**: A [standard](https://eips.ethereum.org/EIPS/eip-1967) for defining a consistent location where proxies store the address of the logic contract they delegate to, as well as other proxy-specific information. OpenZeppelin team members authored the standard and built contracts libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy/ERC1967). +- **ERC-2771: Secure Protocol for Native Meta Transactions**: A [standard](https://eips.ethereum.org/EIPS/eip-2771) for a contract interface for receiving meta transactions through a trusted forwarder. OpenZeppelin team members co-authored the standard and built contracts libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/metatx). + +### Starknet + +- **SNIP-5: Standard Interface Detection**: A [standard](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-5.md) for a standard method to publish and detect what interfaces a smart contract implements. OpenZeppelin team members co-authored the standard and built contracts libraries in the [OpenZeppelin Starknet Cairo Contracts Repo](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/introspection). + +- **SNIP-12: Off-Chain Signatures**: A [standard](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-12.md) for hashing and signing typed structured data as opposed to just hexadecimal (or felt) values in Starknet. OpenZeppelin provided feedback on the standard and built contracts libraries in the [OpenZeppelin Starknet Cairo Contracts Repo](https://github.com/OpenZeppelin/cairo-contracts/blob/main/packages/utils/src/cryptography/snip12.cairo). + +### Stellar + +- **SEP-0049: Upgradeable Contracts**: A [standard](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0049.md) that provides community guidelines and recommendations for safely upgrading the WASM bytecode of Soroban smart contracts. OpenZeppelin team members authored that standard and built contracts libraries in the [OpenZeppelin Stellar Soroban Contracts Repo](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/upgradeable). + +- **SEP-0050: Non-Fungible Tokens**: A [standard](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0050.md) that defines a standard contract interface for non-fungible tokens. OpenZeppelin team members authored that standard and built contracts libraries in the [OpenZeppelin Stellar Soroban Contracts Repo](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non_fungible). + +## Battle Tested Libraries + +Every OpenZeppelin library represents over a decade of security expertise, community validation, and real-world production use. + +| Library | Purpose | Networks Supported | Number of Deployments | Popular Implementations | Example Use | +| --- | --- | --- | --- | --- | --- | +| Tokens | Issue and manage digital assets | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/token), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/token), [Midnight](https://github.com/OpenZeppelin/compact-contracts/tree/main/contracts/src/token), [Zama](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token) | 150,000+ (over $30 trillion in total value transferred!) | ERC-20, ERC-721, ERC-1155, ERC-4626, ERC-6909 | [Circle USDC](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code#F17#L23), [Ondo OUSG](https://etherscan.io/address/0x1ceb44b6e515abf009e0ccb6ddafd723886cf3ff#code#F12#L2), [Lido](https://github.com/lidofinance/core/blob/005b0876d6594b7f7864e0577cdaa44eff115b73/contracts/0.8.9/WithdrawalVault.sol#L9) | +| Utilities | Improve security, work with new data types, or safely use low-level primitives | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/utils), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/utils), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/utils), [Midnight](https://github.com/OpenZeppelin/compact-contracts/tree/main/contracts/src/utils), [Zama](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/utils), [Uniswap Hooks](https://github.com/OpenZeppelin/uniswap-hooks/tree/master/src/utils) | 150,000+ | Cryptography, math, data integrity | [Ethena USDe](https://etherscan.io/token/0x4c9edd5852cd905f086c759e8383e09bff1e68b3#code#F19#L2), [Pallas Fund USDtb](https://etherscan.io/address/0xea8a763b5b1f9c9c7aea64f33947448d9e39e475#code#F14#L2), [Graph Protocol](https://github.com/graphprotocol/contracts/blob/109e84c71fd01f2cb51f8582bb43603881380072/packages/horizon/contracts/utilities/Authorizable.sol#L6) | +| Access Control | Manage who can perform specific actions, when they can do so, and under what authority | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/access), [Stellar](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/access), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/access), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/access), [Midnight](https://github.com/OpenZeppelin/compact-contracts/tree/main/contracts/src/access) | 115,000+ | Access Control, Ownable | [Lido](https://github.com/lidofinance/core/blob/master/contracts/0.8.9/utils/access/AccessControlEnumerable.sol#L5), [Circle USDC](https://etherscan.io/address/0x43506849d7c04f9138d1a2050bbf3a0c054402dd#code#F13#L31), [BlockRock BUIDL-I](https://etherscan.io/address/0x9e2693f54831f6f52b0bb952c2935d26919a3626#code#F2#L2) | +| Proxies & Upgradability | Secure contract upgrades without disrupting state | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/proxy), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/upgrades), [Arbitrum Stylus](https://github.com/OpenZeppelin/rust-contracts-stylus/tree/main/contracts/src/proxy) | 75,000+ | Transparent Proxy, UUPS Proxy, Beacon Proxy | [Lido](https://github.com/lidofinance/core/blob/master/contracts/0.8.4/WithdrawalsManagerProxy.sol#L317), [Sky USDS](https://etherscan.io/address/0x1923dfee706a8e78157416c29cbccfde7cdf4102#code#F2#L2), [BlackRock BUIDL-I](https://etherscan.io/token/0x6a9da2d710bb9b700acde7cb81f10f1ff8c89041#code#F4#L2) | +| Governance | Decentralized decision-making and controlled execution | [EVM](https://github.com/OpenZeppelin/openzeppelin-contracts/tree/master/contracts/governance), [Starknet](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/governance), [Zama](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/governance). | 1,000+ | Governor | [Arbitrum DAO](https://github.com/ArbitrumFoundation/governance/blob/main/src/L2ArbitrumGovernor.sol) | + +## What’s Next? + +Learn more about the [future of OpenZeppelin Contracts](/impact/contracts-future). diff --git a/docs/content/impact/index.mdx b/docs/content/impact/index.mdx new file mode 100644 index 00000000..5ea1d43b --- /dev/null +++ b/docs/content/impact/index.mdx @@ -0,0 +1,39 @@ +--- +title: OpenZeppelin's Impact and Contributions +--- + +For over a decade, OpenZeppelin has set the foundation for how smart contracts are built, secured, and maintained. Our standards contributions, libraries, tooling, and security services power the protocols, layer 1s and layer 2s, and financial institutions driving the on-chain economy. + +## Contracts + +OpenZeppelin Contracts is the most adopted smart contract framework in the world, securing trillions in on-chain value. + + + + + + + + + + + + +## Innovation and Research + +The next decade of on-chain innovation will bring regulated finance, privacy-preserving systems, and programmable capital to the same foundation. Building on a [decade of impact](/impact/contracts-impact), OpenZeppelin is extending its standards, contracts libraries, and tooling to support this evolution. + + + + + Cross-chain intents, cross-chain messaging, smart accounts, and zkEmail + + + + Confidential tokens, private shared state, and private email verifiction + + + + Permissioned tokens, confidential tokens, and on-chain yield + + \ No newline at end of file diff --git a/docs/content/impact/privacy.mdx b/docs/content/impact/privacy.mdx new file mode 100644 index 00000000..da0c0b85 --- /dev/null +++ b/docs/content/impact/privacy.mdx @@ -0,0 +1,87 @@ +--- +title: Privacy at OpenZeppelin +--- + +Our work focuses on enabling confidentiality, selective disclosure, and private coordination across on-chain applications. + +## Ecosystem Contributions + +We have partnered with ecosystems and projects to deliver confidential and privacy-preserving contracts libraries, tooling, and starter applications. + + + + Token standards (ERC-20 equivalent) in Rust, shared state model, and private multisig + + + + Privacy preserving contracts libraries in Compact, developer tooling, and starter decentralized applications + + + + Confidential contracts libraries in Solidity and privacy relayer + + + + Confidential real world asset extensions contracts libraries in Solidity + + + + Cryptography contracts libraries in Solidity + + + +## Confidential Tokens + +Extends standards such as ERC-20 to support encrypted balances and confidential transfers, allowing assets to move privately while remaining verifiable on-chain. + +### Use Cases + +- **Confidential Payments**: Settle supplier/vendor payments without leaking volume or terms. + +- **Confidential Fund Allocations**: Generate yield without exposing portfolio weights and without signaling size or intent. + +- **Confidential Governance**: Prevent manipulation, bribery, and collusion by concealing voting weights, thresholds, and execution time delays. + +### Standards + +- **ERC-7984: Confidential Fungible Token** (draft): A [standard](https://github.com/ethereum/ERCs/pull/1113/files#diff-4c22c7cb34e9622c46198c95e8810399c8bf545126ef800b3a464083629aa6e5) that enables confidential value transfer using a pointer based system. OpenZeppelin team members co-authored the standard with Zama and is building contracts libraries in the [OpenZeppelin Solidity Confidential Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token/ERC7984). + +- **ERC-3643: T-REX - Token for Regulated Exchanges** (final): A [standard](https://eips.ethereum.org/EIPS/eip-3643) that enables the management and compliant transfer of institutional grade security tokens using on chain compliance and verification systems. OpenZeppelin is building contracts libraries for confidential real world asset extensions in the [OpenZeppelin Solidity Confidential Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token/ERC7984/extensions). + +### Associations + +- **Confidential Token Association**: The goal of the [association](https://www.confidentialtoken.org/) is to push forward onchain confidentiality by leveraging recent advances in encryption to build a common token standard. OpenZeppelin is a [founding member](https://www.confidentialtoken.org/#members) with Zama and Inco. + +## Private Email Verification + +On-chain email based identity using zero knowledge proofs. + +### Use Cases + +- **Send Crypto Using Only Email Address**: Users can authorize transactions (e.g. send money, DAO voting, any blockchain transaction) by proving control of their email address with no private key management required. Email never revealed! + +- **Recover Account Using Email Gaurdians**: Lost keys can be restored by proving control of an email account, enabling user-friendly recovery. + +### Standards + +- **ERC-7969: DomainKeys Identified Mail (DKIM) Registry** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7969) that enables trustless email ownership verification using a DKIM restistry. OpenZeppelin team members co-authored with OKX and is building contracts libraries in the [OpenZeppelin Solidity Community Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/utils/cryptography/DKIMRegistry.sol). + +## Private Shared State + +Private coordination of state between parties using off-chain state storage and synchronization to prevent users from losing their state. + +### Use Cases + +- **Private Coordination**: Allows parties to coordinate information that must remain private, such as borrowing/lending, OTC trading, and transfer of funds between privileged entities. + +- **Confidential Multisigs**: Multisignature accounts with features such as encrypted balances and confidential transfers. + +## Confidential Multisig + +Extends multsignature accounts to support encrypted balances, confidential transfers, and shared private state. + +### Use Cases + +- **Enhanced Security**: Users and organizations can keep the exact threshold a secret to prevents attackers from knowing the signatures scheme and/or how many signatures they need to compromise. + +- **Private Governance Structures**: Sensitive organizations can enable on chain regulatory compliance. diff --git a/docs/content/impact/tokenization-and-real-world-assets.mdx b/docs/content/impact/tokenization-and-real-world-assets.mdx new file mode 100644 index 00000000..bae7abe3 --- /dev/null +++ b/docs/content/impact/tokenization-and-real-world-assets.mdx @@ -0,0 +1,76 @@ +--- +title: Tokenization and Real World Assets at OpenZeppelin +--- + +OpenZeppelin Contracts is the trusted foundation for the on-chain economy, [powering top financial institutions](/impact/contracts-for-financial-institutions). Used by the world’s leading stablecoin issuers, asset managers, and on-chain funds, OpenZeppelin provides the security-audited, production-proven contracts trusted to secure billions in value. + +Our ongoing work focuses on continuously improving compliant issuance and management of institutional digital assets. + +## Ecosystem Contributions + +We have partnered with ecosystems and projects to deliver tokenization, real world asset, and tokenized vaults contracts libraries. + + + + Tokenized vaults contracts libraries + + + + Tokenized vaults contracts libraries + + + + Real world asset contracts libraries in Soroban for Stellar + + + + Confidential real world asset extensions contracts libraries in Solidity for Ethereum & EVM + + + +## Permissioned Tokens + +Compliant issuance and management of institutional-grade digital assets. + +### Use Cases + +- **Tokenized Money Market Funds**: Enable on-chain representation of yield-bearing traditional assets, allowing users and protocols to access stable, regulated returns. + +- **Global Payments**: Facilitate instant, programmable settlement of real-world transactions using tokenized fiat or asset-backed instruments. + +### Standards + +- **ERC-3643: T-REX - Token for Regulated Exchanges** (final): A [standard](https://eips.ethereum.org/EIPS/eip-3643) that enables the management and compliant transfer of institutional grade security tokens using on chain compliance and verification systems. OpenZeppelin is building contracts libraries in the [OpenZeppelin Stellar Soroban Repo](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa) and confidential real world asset extensions in the [OpenZeppelin Solidity Confidential Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token/ERC7984/extensions). + +- **ERC-7984: Confidential Fungible Token** (draft): A [standard](https://github.com/ethereum/ERCs/pull/1113/files#diff-4c22c7cb34e9622c46198c95e8810399c8bf545126ef800b3a464083629aa6e5) that enables confidential value transfer using a pointer based system. OpenZeppelin team members co-authored the standard with Zama and is building contracts libraries in the [OpenZeppelin Solidity Confidential Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-confidential-contracts/tree/master/contracts/token/ERC7984). + +- **ERC-7943: Universal Real World Asset Interface** (draft): A [standard](https://eips.ethereum.org/EIPS/eip-7943) that enables compliance checks, transfer controls, and enforcement actions for Real World Assets (RWAs). OpenZeppelin team members authored the standard and is building contracts libraries in the [OpenZeppelin Solidity Community Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/token/ERC20/extensions/ERC20uRWA.sol). + +### Associations + +- **3643 Association**: The goal of the [association](https://www.erc3643.org/) is to increase the adoption of the 3643 token standard for real-world asset tokenization and on-chain compliance. OpenZeppelin is a [member](https://www.erc3643.org/members) with many major organization. + +## Tokenized Vaults + +Management of yield-bearing assets and tokenized deposits. + +### Use Cases + +- **Real World Asset Collateralization**: Protocols can tokenize and collateralize underlying assets within vaults, supporting stablecoin pegs and capital efficiency through verifiable on-chain reserves. + +- **Lending and Borrowing**: Deposited tokens can be supplied as liquidity to decentralized lending markets, enabling seamless borrowing and yield generation across interoperable vaults. + +- **Yield Aggregators**: Aggregator protocols can integrate standardized vaults through a unified interface, allowing users to access diverse yield strategies and optimize returns. + +### Standards + +- **ERC-4626: Tokenized Vaults** (final): A [standard](https://eips.ethereum.org/EIPS/eip-4626) that enables protocols and users to generate and manage yield for ERC-20 tokens. + OpenZeppelin has built libraries in the [OpenZeppelin Solidity Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626.sol), [OpenZeppelin Solidity Community Contracts Repo](https://github.com/OpenZeppelin/openzeppelin-community-contracts/blob/master/contracts/token/ERC20/extensions/ERC4626Fees.sol), [OpenZeppelin Starknet Cairo Contracts Repo](https://github.com/OpenZeppelin/cairo-contracts/tree/main/packages/token/src/erc20/extensions/erc4626), and [OpenZeppelin Stellar Soroban Contracts Repo](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/vault). + +## Talk to an Expert + +Whether you’re launching a stablecoin, tokenizing assets, or building institutional-grade infrastructure, our team can help you design with security, compliance, and scalability from day one. + +Connect with OpenZeppelin experts to discuss your project, evaluate architectures, and access the libraries and audits trusted by the world’s leading financial institutions. + +[Talk to an Expert](https://www.openzeppelin.com/financial-institutions/request) diff --git a/docs/content/monitor/1.0.x/architecture.mdx b/docs/content/monitor/1.0.x/architecture.mdx new file mode 100644 index 00000000..94d0d6a0 --- /dev/null +++ b/docs/content/monitor/1.0.x/architecture.mdx @@ -0,0 +1,362 @@ +--- +title: Architecture Guide +--- + +This document describes the high-level architecture of OpenZeppelin Monitor, including the core components, their interactions, and the overall system design. It provides a technical overview of how the service processes blockchain data and triggers notifications based on configurable conditions. + +## System Overview + +OpenZeppelin Monitor is organized as a data processing pipeline that spans from blockchain data collection to notification delivery. The system follows a modular architecture with distinct components for each step in the monitoring process, designed for scalability and extensibility. + +### High-Level Architecture + +The diagram below shows the core processing pipeline of OpenZeppelin Monitor, from blockchain networks and configuration through to notification channels: + +```mermaid +flowchart LR + subgraph "Blockchain Networks" + EVMN["EVM Networks"] + STLN["Stellar Networks"] + end + subgraph "Configuration" + NETCFG["Network Configs"] + MONCFG["Monitor Configs"] + TRICFG["Trigger Configs"] + end + subgraph "Core Processing Pipeline" + BWS["BlockWatcherService"] + FS["FilterService"] + TS["TriggerService"] + NS["NotificationService"] + end + subgraph "Notification Channels" + Slack + Email + Discord + Telegram + Webhook + Scripts["Custom Scripts"] + end + + %% Data and config flow + EVMN --> BWS + STLN --> BWS + NETCFG --> BWS + MONCFG --> FS + TRICFG --> TS + BWS --> FS + FS --> TS + TS --> NS + NS --> Slack + NS --> Email + NS --> Discord + NS --> Telegram + NS --> Webhook + NS --> Scripts + + %% Styling for major blocks and notification channels + classDef blockchain fill:#fff3e0,stroke:#ef6c00,stroke-width:2px; + classDef config fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px; + classDef mainBlock fill:#e1f5fe,stroke:#01579b,stroke-width:2px; + classDef notification fill:#fce4ec,stroke:#c2185b,stroke-width:2px; + class EVMN,STLN blockchain; + class NETCFG,MONCFG,TRICFG config; + class BWS,FS,TS,NS mainBlock; + class Slack,Email,Discord,Telegram,Webhook,Scripts notification; +``` + +## Component Architecture + +The system consists of several core services that are initialized at startup and work together to process blockchain data and trigger notifications. The service initialization and dependencies are managed through the bootstrap module. + +### Service Initialization Flow + +```mermaid +graph TD + subgraph Entry Point + MAIN[main.rs] + end + + subgraph Bootstrap + BOOTSTRAP[Bootstrap::initialize_service] + end + + subgraph Block Processing + BT[BlockTracker] + BS[BlockStorage] + BWS[BlockWatcherService] + BH[create_block_handler] + end + + subgraph Core Services + MS[MonitorService] + NS[NetworkService] + TS[TriggerService] + FS[FilterService] + TES[TriggerExecutionService] + NOTS[NotificationService] + end + + subgraph Client Layer + CP[ClientPool] + EVMC[EVMClient] + SC[StellarClient] + end + + %% Initialization Flow + MAIN --> BOOTSTRAP + BOOTSTRAP --> CP + BOOTSTRAP --> NS + BOOTSTRAP --> MS + BOOTSTRAP --> TS + BOOTSTRAP --> FS + BOOTSTRAP --> TES + BOOTSTRAP --> NOTS + + %% Block Processing Setup + BOOTSTRAP --> BT + BOOTSTRAP --> BS + BOOTSTRAP --> BWS + BOOTSTRAP --> BH + + %% Client Dependencies + CP --> EVMC + CP --> SC + BWS --> CP + + %% Service Dependencies + BWS --> BS + BWS --> BT + MS --> NS + MS --> TS + FS --> TES + TES --> NOTS + + %% Block Handler Connection + BH --> FS + BWS --> BH + + style MAIN fill:#e1f5fe,stroke:#01579b + style BOOTSTRAP fill:#fff3e0,stroke:#ef6c00 + classDef blockProcessing fill:#e8f5e9,stroke:#2e7d32 + classDef coreServices fill:#f3e5f5,stroke:#7b1fa2 + classDef clients fill:#fce4ec,stroke:#c2185b + + class BT,BS,BWS,BH blockProcessing + class MS,NS,TS,FS,TES,NOTS coreServices + class CP,EVMC,SC clients +``` + +### Core Components + +#### Block Processing Components + +* ***BlockWatcherService***: Orchestrates the block monitoring process by polling blockchain networks for new blocks and coordinating the processing pipeline. +* ***BlockTracker***: Tracks processed block numbers to prevent duplicate processing and ensure data consistency across service restarts. +* ***BlockStorage***: Persists block processing state for recovery and maintains the last processed block number for each network. + +#### Client Layer Components + +* ***ClientPool***: Manages blockchain client instances and provides network connectivity with connection pooling and failover capabilities. +* ***EVMClient***: Handles communication with Ethereum Virtual Machine compatible networks (Ethereum, Polygon, BSC, etc.). +* ***StellarClient***: Manages connections to Stellar blockchain networks with protocol-specific optimizations. + +#### Processing Pipeline Components + +* ***FilterService***: Applies monitor filters to blockchain data, evaluating conditions and match expressions to identify relevant transactions and events. +* ***TriggerExecutionService***: Executes triggers based on matched monitor conditions, evaluating trigger logic and preparing notification payloads. +* ***NotificationService***: Delivers notifications through configured channels (Slack, Email, Discord, Telegram, Webhooks, Scripts). + +#### Configuration Management Components + +* ***MonitorService***: Manages monitor configurations and provides access to active monitors with validation and lifecycle management. +* ***NetworkService***: Manages network configurations and provides network details for client connections and monitoring operations. +* ***TriggerService***: Manages trigger configurations and provides trigger details for notification execution. + +### Service Responsibilities + +The following table describes the key responsibilities of each service in the OpenZeppelin Monitor architecture: + +| Service | Responsibility | +| --- | --- | +| **MonitorService** | Manages monitor configurations and provides access to active monitors | +| **NetworkService** | Manages network configurations and provides network details | +| **TriggerService** | Manages trigger configurations and provides trigger details | +| **FilterService** | Filters blockchain data based on monitor conditions and match expressions | +| **TriggerExecutionService** | Executes triggers based on matched monitor conditions | +| **NotificationService** | Delivers notifications through configured channels | +| **BlockWatcherService** | Polls blockchain networks for new blocks and coordinates processing | +| **BlockTracker** | Tracks processed block numbers to prevent duplicate processing | +| **BlockStorage** | Persists block processing state for recovery | +| **ClientPool** | Manages blockchain client instances and provides network connectivity | + +### Block Processing Workflow + +The following _runtime flow_ illustrates how data moves through the system, from blockchain networks to notification channels. This sequence represents the core monitoring loop that executes for each configured network. + +```mermaid +sequenceDiagram + participant BWS as BlockWatcherService + participant BS as BlockStorage + participant BC as BlockchainClient + participant FS as FilterService + participant TES as TriggerExecutionService + participant NS as NotificationService + + rect rgb(232, 245, 233) + Note over BWS: Orchestrates block processing + BWS->>BS: Get last processed block + end + + rect rgb(225, 245, 254) + Note over BC: Blockchain interface + BWS->>BC: Get latest block number + BWS->>BC: Get blocks (last+1 to latest) + end + + loop For each block + rect rgb(243, 229, 245) + Note over FS: Applies monitor filters + BWS->>FS: filter_block(block) + FS->>FS: Apply monitor filters + FS-->>BWS: Monitor matches + end + + rect rgb(255, 248, 225) + Note over TES: Evaluates trigger conditions + BWS->>TES: Process monitor matches + TES->>TES: Run trigger conditions + end + + rect rgb(252, 228, 236) + Note over NS: Delivers notifications + TES->>NS: Execute notifications + end + end + + rect rgb(255, 243, 224) + Note over BS: Persists processing state + BWS->>BS: Store latest processed block + end +``` + +### Data Flow Architecture + +#### 1. Block Discovery Phase +The `BlockWatcherService` initiates the monitoring cycle by: + +* Retrieving the last processed block number from `BlockStorage` +* Querying the blockchain for the latest block number +* Calculating the range of new blocks to process + +#### 2. Data Retrieval Phase +The `BlockchainClient` fetches block data: + +* Connects to the appropriate blockchain network via RPC +* Retrieves full block data including transactions and events +* Handles network-specific data formats and protocols + +#### 3. Filtering Phase +The `FilterService` processes each block: + +* Applies monitor-specific filters to transactions and events +* Evaluates match expressions and conditions +* Identifies relevant data that matches monitoring criteria + +#### 4. Trigger Evaluation Phase +The `TriggerExecutionService` processes matches: + +* Evaluates trigger conditions for matched data +* Prepares notification payloads with relevant context +* Determines which notification channels to activate + +#### 5. Notification Delivery Phase +The `NotificationService` delivers alerts: + +* Formats messages for each notification channel +* Handles channel-specific delivery protocols +* Manages delivery retries and error handling + +#### 6. State Persistence Phase +The `BlockStorage` updates processing state: + +* Records the latest processed block number +* Ensures data consistency for recovery scenarios +* Maintains processing history for debugging + +### Error Handling and Resilience + +The architecture includes several resilience mechanisms: + +* ***Connection Pooling***: The `ClientPool` manages multiple connections to prevent single points of failure +* ***State Recovery***: `BlockStorage` enables the service to resume from the last known good state after restarts +* ***Retry Logic***: Notification delivery includes configurable retry mechanisms for transient failures +* ***Graceful Degradation***: Individual component failures don’t cascade to the entire system + +For detailed information about RPC logic and network communication, see the [RPC section](/monitor/1.0.x/rpc). + +## Configuration Architecture + +The system uses a JSON-based configuration system organized into distinct categories: + +### Configuration Categories + +* ***Network Configurations***: Define blockchain network connections, RPC endpoints, and network parameters +* ***Monitor Configurations***: Specify monitoring rules, conditions, and network/trigger references +* ***Trigger Configurations***: Define notification settings and script definitions +* ***Filter Configurations***: Contain match filter scripts for data filtering + +### Configuration Validation + +The system implements comprehensive validation: + +* Cross-reference validation between monitors, networks, and triggers +* Schema validation for all configuration files +* Runtime validation of configuration references during service startup + + + +For configuration examples and best practices, see the [Configuration Guidelines](/monitor/1.0.x#configuration-guidelines) section in the user documentation. + + +## Extensibility Points + +The architecture is designed for extensibility in several key areas: + +### Blockchain Support +* ***Client Layer***: New blockchain protocols can be added by implementing the `BlockchainClient` trait +* ***Transport Layer***: Protocol-specific transport clients handle network communication details +* ***Filter Layer***: Chain-specific filters process protocol-dependent data formats + +### Notification Channels +* ***Channel Plugins***: New notification channels can be added by implementing the notification interface +* ***Script Support***: Custom notification logic can be implemented using Python, JavaScript, or Bash scripts + +### Monitoring Logic +* ***Expression Engine***: Flexible expression evaluation for complex monitoring conditions +* ***Script Triggers***: Custom trigger logic can be implemented using supported scripting languages + +## Performance Considerations + +The architecture is optimized for: + +* ***Concurrent Processing***: Multiple networks can be monitored simultaneously +* ***Efficient Block Processing***: Batch processing of blocks to minimize RPC calls +* ***Memory Management***: Streaming processing of large blocks to prevent memory issues +* ***Connection Reuse***: Client pooling reduces connection overhead + +## Security Architecture + +The system implements several security measures: + +* ***Secure Protocols***: Support for HTTPS/WSS +* ***Secret Management***: Secure handling of API keys and sensitive configuration data +* ***Input Validation***: Comprehensive validation of all external inputs and configurations + +## Related Documentation + +For detailed information about the project structure, source code organization, and development resources, see the [Project Structure](/monitor/1.0.x/project-structure) guide. + +For information about RPC logic and network communication, see the [RPC section](/monitor/1.0.x/rpc). + +For configuration examples and best practices, see the [Configuration Guidelines](/monitor/1.0.x#configuration-guidelines) section in the user documentation. diff --git a/docs/content/monitor/1.0.x/changelog.mdx b/docs/content/monitor/1.0.x/changelog.mdx new file mode 100644 index 00000000..276fa8ad --- /dev/null +++ b/docs/content/monitor/1.0.x/changelog.mdx @@ -0,0 +1,300 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v1.1.0) - 2025-10-22 + +## [1.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v1.0.0...v1.1.0) (2025-10-22) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Add events and functions summary in notifications ([#339](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/339)) ([000ae24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/000ae24e896cd0867c6252111a71151942d820bc)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Enhance filter expression parsing and evaluation ([#222](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/222)) ([3cb0849](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3cb084919b3d477f329a85fbafce1ce6d696b16d)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Handle Stellar JSON-RPC outside of retention window error for `getTransactions` and `getEvents` ([#270](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/270)) ([ae116ff](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ae116ff10f393a04c19d3b845df656027c6be4b9)) +* Implement client pooling for Webhook-based notifiers ([#281](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/281)) ([4f480c6](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4f480c6a05aeb949cfd8e227c5c08f19a5e60180)) +* Introduce `TransportError` ([#259](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/259)) ([0e04cfb](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0e04cfb57109251095ef8ee526fb5e05f5792792)) +* Introduce centralized retryable HTTP client creation ([#273](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/273)) ([5f6edaf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5f6edaf5deb77a5d9dfead52a162e923aad6a2ab)) +* Introduce retry mechanism for Email notifier ([#282](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/282)) ([b6301aa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b6301aaac963ae904d93e07674d9d01543ecfcd0)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) +* Update payload builder to support formatted titles ([#336](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/336)) ([12213b3](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/12213b32d609bf6a1ba69ce548f70809971f9fb3)) +* Upgrade stellar crates and read events from specs ([#371](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/371)) ([7273a3f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7273a3f8d9249692db6b6ca53f4d8b28b21670f4)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* Bump MSRV ([#291](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/291)) ([f2d7953](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f2d795310cd1417ad2fac854ea5f80cf6296b761)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* correct env var value in semgrep.yml ([#317](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/317)) ([7a8253f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7a8253fd23ae27c73b3971e2a688c39051c08a84)) +* Deprecate Stellar `paging_token` in `GetEvents` response ([#344](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/344)) ([68d20f9](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/68d20f91b643ef3a7c85ee897308d4f92d43698b)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Duplicate name in triggers config ([#274](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/274)) ([00f58f4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/00f58f4be3f9452792f9fdcf5dd8696947a274cb)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Generate SBOM step in release pipeline ([#294](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/294)) ([327269d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/327269d1ce2a16e9c8419e872ca02503c318c480)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* Multi arch. docker images and binary mismatch ([#382](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/382)) ([a61701e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a61701e11a13af03cdf86689b58e670b7d984a38)) +* Pagination logic in stellar getEvents relies only on cursor data ([#265](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/265)) ([fca4057](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fca4057ff5847e04981e5903eebe6ccf3931726c)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* Release binaries and enable nightly workflows to create binary artifacts and images ([#313](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/313)) ([43a0091](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/43a0091ed7b57a4ca33ca25a73423a73929802f7)) +* Remove deprecated reviewers field from dependabot.yml ([#316](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/316)) ([152843d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/152843df396b089e1c6054221206097339502f1b)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* Replace automatic minor version bumps ([#285](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/285)) ([0c9e14a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0c9e14a542cae2d2c7ff580ff7de28b0d9aab22a)) +* Risk of key collision for monitor custom scripts ([#258](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/258)) ([2aa4cd7](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2aa4cd730dbcbbd1cf0892394cedc4ea06332375)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) +* Semgrep CI integration ([#315](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/315)) ([a2bc23b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a2bc23baa27630ba914fca12ac40b191cbbad525)) +* Stellar ledgers are deterministic ([#257](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/257)) ([56a9f9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/56a9f9e10e533ea96c01cb1f0f67024600ad89df)) +* syntax error in codeql.yml ([#322](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/322)) ([7068e9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7068e9ee3845a007ed9d6c80157cbe86555ad14e)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Update the Semgrep config ([#306](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/306)) ([d4ed740](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d4ed7405e790098a0b1a0df3701feccb1908c56c)) +* Use unicode character for emoji ([#295](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/295)) ([bdccda5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bdccda5f2ca72612a4455a293c30647618476f95)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v1.0.0) - 2025-06-30 + +## [1.0.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.2.0...v1.0.0) (2025-06-30) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Enhance filter expression parsing and evaluation ([#222](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/222)) ([3cb0849](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3cb084919b3d477f329a85fbafce1ce6d696b16d)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Handle Stellar JSON-RPC outside of retention window error for `getTransactions` and `getEvents` ([#270](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/270)) ([ae116ff](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ae116ff10f393a04c19d3b845df656027c6be4b9)) +* Implement client pooling for Webhook-based notifiers ([#281](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/281)) ([4f480c6](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4f480c6a05aeb949cfd8e227c5c08f19a5e60180)) +* Introduce `TransportError` ([#259](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/259)) ([0e04cfb](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0e04cfb57109251095ef8ee526fb5e05f5792792)) +* Introduce centralized retryable HTTP client creation ([#273](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/273)) ([5f6edaf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5f6edaf5deb77a5d9dfead52a162e923aad6a2ab)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Duplicate name in triggers config ([#274](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/274)) ([00f58f4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/00f58f4be3f9452792f9fdcf5dd8696947a274cb)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* Pagination logic in stellar getEvents relies only on cursor data ([#265](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/265)) ([fca4057](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fca4057ff5847e04981e5903eebe6ccf3931726c)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* Replace automatic minor version bumps ([#285](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/285)) ([0c9e14a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0c9e14a542cae2d2c7ff580ff7de28b0d9aab22a)) +* Risk of key collision for monitor custom scripts ([#258](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/258)) ([2aa4cd7](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2aa4cd730dbcbbd1cf0892394cedc4ea06332375)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) +* Stellar ledgers are deterministic ([#257](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/257)) ([56a9f9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/56a9f9e10e533ea96c01cb1f0f67024600ad89df)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + +[Changes][v1.0.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v0.2.0) - 2025-05-14 + +## [0.2.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.1.0...v0.2.0) (2025-05-14) + + +## ⚠️ ⚠️ Breaking Changes in v0.2.0 + +* Renamed abi to contract_spec in monitor configurations. +* Stellar function expressions now use named parameters instead of positional indexes, for example; + + ``` + (Transfer(address,address,amount)): + 2 > 1000 → amount > 1000 + ``` +* Template variables now follow dot notation rather than underscores, for example: + * monitor_name → monitor.name + * transaction_hash → transaction.hash + * function_0_amount → functions.0.args.amount + * event_0_signature → events.0.signature +* Sensitive configuration values (e.g., URLs, usernames, passwords, tokens) must now be defined using the SecretValue object structure, for example: + + * RPC URLs: + + ``` + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "https://eth.drpc.org" + }, + "weight": 100 + } + ] + ``` + + * Webhook URLs: + + ``` + "discord_url": { + "type": "plain", + "value": "https://discord.com/api/webhooks/123-456-789" + } + ``` + + +### 🚀 Features + +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) + + +### 🐛 Bug Fixes + +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) + +[Changes][v0.2.0] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v0.1.0) - 2025-04-07 + +## 0.1.0 (2025-04-07) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Netlify integration & Release workflow doc ([#162](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/162)) ([3b77025](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3b7702569e7c5828ca55fb67f7eec2672bf768b2)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + + +### 📚 Documentation + +* Add Antora documentation ([#48](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/48)) ([2f737c4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2f737c4c040090bd3acd0af90d3f24045b8ff173)) +* add link to contributing in README ([#33](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/33)) ([5abb548](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5abb548c199f3a033860b027461e5fb3cd60e565)) +* Add list of RPC calls ([#67](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/67)) ([aae9577](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/aae9577f4e011eaca12adb7997bf5fd28a558f83)) +* Add quickstart guide ([#56](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/56)) ([e422353](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e422353873335540afce5a9a5702c786c71eea75)) +* add readme documentation ([#8](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/8)) ([357006d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/357006d98f6cc8d160920e702dc78662008d39a3)) +* add rust documentation ([#5](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/5)) ([3832570](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3832570adf4854279fcda215fbbba5eb0d5396a1)) +* Adding node to docker images - custom scripts ([#76](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/76)) ([da6516c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/da6516c6f3afccb297cb1c1251f673e02ceaeaa5)) +* Custom scripts documentation to antora and readme ([#91](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/91)) ([2b81058](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2b81058f810e6b4d18a2c79e96002fb77890e9e0)) +* Fix quickstart closing tag ([#118](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/118)) ([d360379](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d3603796f39c15ed5247efab90ab95c5537c76d2)) +* Fix telegram channel ([9899259](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/98992599ab8998113b6202781787a48ce0aab3db)) +* Implement README feedback ([#50](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/50)) ([5b6ba64](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5b6ba6419a06b9abd60412fa02b09da2a416e38c)) +* Improve docs ([#100](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/100)) ([9586a25](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/9586a253f2a76993bbf82d4834b37863edabab60)) +* improve readme section and examples ([#9](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/9)) ([009db37](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/009db3719e1be03120733755ade3c1c45e13f8a5)) +* Improvements to custom scripts ([#98](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/98)) ([69047d9](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/69047d90a2fe057446f7c1b3f3526ab31bc6afcb)) +* Re-order example and fix test flag ([#52](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/52)) ([f90b6df](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f90b6df73ef7a6040eab59d71402b34877c88fc5)) +* Readability improvements ([#109](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/109)) ([8e23389](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/8e23389ea0dcb3b221227a6cddd17de39603acbb)) +* Update project structure ([#101](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/101)) ([207edd2](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/207edd28f3fb0a805d40d6ba9109abe9e6553d23)) +* Update README and antora docs ([#57](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/57)) ([6a2299e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/6a2299e0c41052ef9523aec1aa6f5852990e9179)) +* Update RPC documentation after client pool feature ([#96](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/96)) ([ade2811](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ade2811431c07c6b46730cbce5e357934df14cd5)) +* Update telegram channel in docs ([#99](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/99)) ([9899259](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/98992599ab8998113b6202781787a48ce0aab3db)) +* Updated Quickstart guide ([#108](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/108)) ([b81c7cd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b81c7cd22143a7d2854ef496ab59e114d70c360f)) + +[Changes][v0.1.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.2.0...v1.0.0 +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.1.0...v0.2.0 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/tree/v0.1.0 diff --git a/docs/content/monitor/1.0.x/contribution.mdx b/docs/content/monitor/1.0.x/contribution.mdx new file mode 100644 index 00000000..5cbd528b --- /dev/null +++ b/docs/content/monitor/1.0.x/contribution.mdx @@ -0,0 +1,303 @@ +--- +title: Contribution Guidelines +--- + +Welcome to the OpenZeppelin Monitor project! We appreciate your interest in contributing. This guide outlines the requirements and processes for contributing to the project. + +## Getting Started + +The OpenZeppelin Monitor project has comprehensive contribution guidelines documented in the [`CONTRIBUTING.md`](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) file. This documentation provides a summary of key requirements, but for complete details including GitHub workflow, labeling guidelines, and advanced topics, please refer to the full CONTRIBUTING.md file. + + + +For the most up-to-date and comprehensive contribution guidelines, always refer to the [CONTRIBUTING.md](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) file in the repository. + + +## Key Requirements + +### Contributor License Agreement (CLA) + +***You must sign the CLA before contributing.*** The CLA process is automated through GitHub workflows that check and label PRs accordingly. + +* All contributors must complete the CLA process +* The CLA bot will automatically check your PR status +* PRs cannot be merged without a signed CLA + +### Signed Commits + +***All commits must be GPG-signed*** as a security requirement. + +* Configure GPG signing for your commits +* Unsigned commits will not be accepted +* This helps ensure code integrity and authenticity + +## Development Environment Setup + +### Prerequisites + +Before contributing, ensure you have: + +* ***Rust 2021 edition*** - Required for development +* ***Git*** - For version control +* ***Python/pip*** - For pre-commit hooks + +### Initial Setup + +```bash +# Clone and set up the repository +git clone https://github.com/openzeppelin/openzeppelin-monitor +cd openzeppelin-monitor + +# Build the project +cargo build + +# Set up environment variables +cp .env.example .env +``` + +### Running Tests + +```bash +# All tests +RUST_TEST_THREADS=1 cargo test + +# Integration tests +RUST_TEST_THREADS=1 cargo test integration + +# Property-based tests +RUST_TEST_THREADS=1 cargo test properties +``` + +## Development Workflow + +### 1. Pre-commit Hooks + +***Required for code quality checks*** including `rustfmt`, `clippy`, and commit message validation. + +* Install and configure pre-commit hooks +* Automatic formatting and linting checks +* Commit message format validation + +#### Installing Pre-commit Hooks + +Install and configure pre-commit hooks to ensure code quality: + +```bash +# Install pre-commit (use pipx for global installation if preferred) +pip install pre-commit + +# Install and configure hooks for commit-msg, pre-commit, and pre-push +pre-commit install --install-hooks -t commit-msg -t pre-commit -t pre-push +``` + + + +If you encounter issues with pip install, you may need to install [pipx](https://github.com/pypa/pipx) for global installation. Use `pipx install pre-commit` instead. + + +The pre-commit hooks will automatically run on every commit and push, checking for: +* Code formatting with `rustfmt` +* Linting with `clippy` +* Commit message format validation +* Other code quality checks + +### 2. GitHub Workflow + +#### Fork and Clone + +1. ***Fork the repository*** on GitHub +2. ***Clone your fork*** locally: + +```bash +# Set up your working directory +export working_dir="$HOME/repos" +export user= + +# Clone your fork +mkdir -p $working_dir +cd $working_dir +git clone https://github.com/$user/openzeppelin-monitor.git + +# Add upstream remote +cd openzeppelin-monitor +git remote add upstream https://github.com/openzeppelin/openzeppelin-monitor.git +git remote set-url --push upstream no_push +``` + +#### Branch Management + +* Create feature branches from an up-to-date main branch +* Regularly sync with upstream +* Use descriptive branch names + +```bash +# Keep main updated +git fetch upstream +git checkout main +git rebase upstream/main + +# Create feature branch +git checkout -b feature/your-feature-name + +# Keep branch updated +git fetch upstream +git rebase upstream/main +``` + + + +Use `git rebase` instead of `git pull` to avoid merge commits and maintain a clean history. + + +### 3. Pull Request Process + +#### Creating a Pull Request + +1. ***Push your changes*** to your fork: + + ```bash + git push -f origin feature/your-feature-name + ``` +2. ***Create a Pull Request*** on GitHub +3. ***Add appropriate labels*** (see Labeling Guidelines below) +4. ***Include a clear description*** of your changes + +#### Best Practices for PRs + +* Write clear and meaningful commit messages +* Include `fixes #123` in PR body (not commit messages) to auto-close issues +* Break large changes into smaller, logical commits +* Ensure all tests pass +* Include sufficient information for reviewers + +## Code Standards + +### Rust Standards + +Rust API Guidelines: + +* Format code with `rustfmt` +* Pass all `clippy` linting checks +* Follow Rust naming conventions + +```bash +# Format code +cargo fmt + +# Check linting +cargo clippy --all-targets --all-features + +# Run tests +RUST_TEST_THREADS=1 cargo test +``` + +### Testing Requirements + +***All contributions must pass existing tests*** and include new tests when applicable: + +* Write unit tests for new functionality +* Add integration tests for complex features +* Ensure all tests pass before submitting +* Maintain or improve code coverage + +For detailed testing information, see the [Testing Guide](/monitor/1.0.x/testing). + +### Commit Message Format + +***Follow conventional commit format*** with types like: + +* `feat:` - New features +* `fix:` - Bug fixes +* `docs:` - Documentation changes +* `test:` - Test additions or modifications +* `refactor:` - Code refactoring +* `chore:` - Maintenance tasks + +## Issue and Pull Request Labeling + +The project uses a structured labeling system to organize issues and PRs. Key label categories include: + +### Area Labels (`A-`) +* `A-arch` - Architectural concerns +* `A-blocks` - Block processing +* `A-clients` - Blockchain clients +* `A-configs` - Configuration issues +* `A-docs` - Documentation +* `A-tests` - Testing + +### Type Labels (`T-`) +* `T-bug` - Bug reports +* `T-feature` - New features +* `T-task` - General tasks +* `T-documentation` - Documentation updates + +### Priority Labels (`P-`) +* `P-high` - Critical tasks +* `P-medium` - Important tasks +* `P-low` - Low priority + +### Difficulty Labels (`D-`) +* `D-easy` - Beginner-friendly +* `D-medium` - Intermediate +* `D-hard` - Complex issues + + + +For complete labeling guidelines and all available labels, see the [labeling section](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md#issue-and-pull-request-labeling-guidelines) in CONTRIBUTING.md. + + +## Code Review Process + +### Review Requirements + +* All PRs require review and approval +* At least one Reviewer and one Approver must approve +* Address all review comments before merging +* Commits are automatically squashed when merging + +### Review Guidelines + +Reviewers should focus on: + +1. ***Soundness*** - Is the idea behind the contribution sound? +2. ***Architecture*** - Is the contribution architected correctly? +3. ***Polish*** - Is the contribution polished and ready? + +### Getting Reviews + +If your PR isn’t getting attention: + +* Contact the team on [Telegram](https://t.me/openzeppelin_tg/4) +* Ensure your PR has appropriate labels +* Keep PRs focused and reasonably sized + +## Security + +* Follow the [Security Policy](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/SECURITY.md) +* Report security vulnerabilities through the proper channels +* Never commit sensitive information or credentials + +## Community Guidelines + +### Code of Conduct + +Contributors must follow the [Code of Conduct](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CODE_OF_CONDUCT.md), which: + +* Establishes standards for respectful collaboration +* Outlines enforcement procedures +* Promotes an inclusive environment + +## Getting Help + +### Community Support + +* ***GitHub Discussions***: For questions and community interaction +* ***Issues***: For bug reports and feature requests +* ***Telegram***: [Join our community chat](https://t.me/openzeppelin_tg/4) +* ***Good First Issues***: [Find beginner-friendly issues](https://github.com/openzeppelin/openzeppelin-monitor/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) + +### Additional Resources + +* ***Full CONTRIBUTING.md***: [Complete contribution guidelines](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) +* ***User Documentation***: [Monitor documentation](https://docs.openzeppelin.com/monitor) +* ***OpenZeppelin Website***: [Main website](https://openzeppelin.com/) diff --git a/docs/content/monitor/1.0.x/error.mdx b/docs/content/monitor/1.0.x/error.mdx new file mode 100644 index 00000000..3b9c1dab --- /dev/null +++ b/docs/content/monitor/1.0.x/error.mdx @@ -0,0 +1,167 @@ +--- +title: Error Handling +--- + +## Overview + +The OpenZeppelin Monitor uses a structured error handling system that provides rich context and tracing capabilities across service boundaries. Let’s start with a real-world example of how errors flow through our system. + +## Error Flow Example + +Let’s follow how an error propagates through our blockchain monitoring system: + +**Low-level Transport (`endpoint_manager.rs`)** + +```rust +// Creates basic errors with specific context +async fn send_raw_request(...) -> Result + let response = client.post(...) + .await + .map_err(|e| anyhow::anyhow!("Failed to send request: {", e))?; + + if !status.is_success() + return Err(anyhow::anyhow!("HTTP error {: {}", status, error_body)); + } +} +``` + +**Client Layer (`evm/client.rs`)** + +```rust +// Adds business context to low-level errors +async fn get_transaction_receipt(...) -> Result + let response = self.alloy_client + .send_raw_request(...) + .await + .with_context(|| format!("Failed to get transaction receipt: {", tx_hash))?; + + if receipt_data.is_null() + return Err(anyhow::anyhow!("Transaction receipt not found")); + +} +``` + +**Filter Layer (`evm/filter.rs`)** + +```rust +// Converts to domain-specific errors +async fn filter_block(...) -> Result, FilterError> + let receipts = match futures::future::join_all(receipt_futures).await { + Ok(receipts) => receipts, + Err(e) => { + return Err(FilterError::network_error( + format!("Failed to get transaction receipts for block {", block_num), + Some(e.into()), + None, + )); + } + }; +} +``` + +When this error occurs, it produces the following log: + +```text +ERROR filter_block: openzeppelin_monitor::utils::error: Error occurred, + error.message: Failed to get transaction receipts for block 15092829, + error.trace_id: a464d73c-5992-4cb5-a002-c8d705bfef8d, + error.timestamp: 2025-03-14T09:42:03.412341+00:00, + error.chain: Failed to get receipt for transaction 0x7722194b65953085fe1e9ec01003f1d7bdd6258a0ea5c91a59da80419513d95d + Caused by: HTTP error 429 Too Many Requests: "code":-32007,"message":"[Exceeded request limit per second]" + network: ethereum_mainnet +``` + +## Error Structure + +### Error Context +Every error in our system includes detailed context information: + +```rust +pub struct ErrorContext + /// The error message + pub message: String, + /// The source error (if any) + pub source: Option>, + /// Unique trace ID for error tracking + pub trace_id: String, + /// Timestamp when the error occurred + pub timestamp: DateTime, + /// Optional key-value metadata + pub metadata: HashMap, + +``` + +### Domain-Specific Error Types + +| Module | Error Type | Description | +| --- | --- | --- | +| `**Configuration**` | `ConfigError` | * `ValidationError` - Configuration validation failures * `ParseError` - Configuration parsing issues * `FileError` - File system related errors * `Other` - Unclassified errors | +| `**Security**` | `SecurityError` | * `ValidationError` - Security validation failures * `ParseError` - Security data parsing issues * `NetworkError` - Security service connectivity issues * `Other` - Unclassified security-related errors | +| `**Blockchain Service**` | `BlockChainError` | * `ConnectionError` - Network connectivity issues * `RequestError` - Malformed requests or invalid responses * `BlockNotFound` - Requested block not found * `TransactionError` - Transaction processing failures * `InternalError` - Internal client errors * `ClientPoolError` - Client pool related issues * `Other` - Unclassified errors | +| `**Block Watcher Service**` | `BlockWatcherError` | * `SchedulerError` - Block watching scheduling issues * `NetworkError` - Network connectivity problems * `ProcessingError` - Block processing failures * `StorageError` - Storage operation failures * `BlockTrackerError` - Block tracking issues * `Other` - Unclassified errors | +| `**Filter Service**` | `FilterError` | * `BlockTypeMismatch` - Block type validation failures * `NetworkError` - Network connectivity issues * `InternalError` - Internal processing errors * `Other` - Unclassified errors | +| `**Notification Service**` | `NotificationError` | * `NetworkError` - Network connectivity issues * `ConfigError` - Configuration problems * `InternalError` - Internal processing errors * `ExecutionError` - Script execution failures * `Other` - Unclassified errors | +| `**Repository**` | `RepositoryError` | * `ValidationError` - Data validation failures * `LoadError` - Data loading issues * `InternalError` - Internal processing errors * `Other` - Unclassified errors | +| `**Script Utils**` | `ScriptError` | * `NotFound` - Resource not found errors * `ExecutionError` - Script execution failures * `ParseError` - Script parsing issues * `SystemError` - System-level errors * `Other` - Unclassified errors | +| `**Trigger Service**` | `TriggerError` | * `NotFound` - Resource not found errors * `ExecutionError` - Trigger execution failures * `ConfigurationError` - Trigger configuration issues * `Other` - Unclassified errors | +| `**Monitor Executor**` | `MonitorExecutionError` | * `NotFound` - Resource not found errors * `ExecutionError` - Monitor execution failures * `Other` - Unclassified errors | + +## Error Handling Guidelines + +### When to Use Each Pattern + +| Scenario | Approach | +| --- | --- | +| Crossing Domain Boundaries | Convert to domain-specific error type using custom error constructors | +| Within Same Domain | Use `.with_context()` to add information while maintaining error type | +| External API Boundaries | Always convert to your domain’s error type to avoid leaking implementation details | + +### Error Creation Examples + +**Creating a Configuration Error without a source** + +```rust +let error = ConfigError::validation_error( + "Invalid network configuration", + None, + Some(HashMap::from([ + ("network", "ethereum"), + ("field", "rpc_url") + ])) +); +``` + +**Creating a Configuration Error with a source** + +```rust + +let io_error = std::io::Error::new(std::io::ErrorKind::Other, "Failed to read file"); + +let error = ConfigError::validation_error( + "Invalid network configuration", + Some(io_error.into()), + None +); +``` + +### Tracing with #[instrument] + +```rust +#[instrument(skip_all, fields(network = %_network.slug))] +async fn filter_block( + &self, + client: &T, + _network: &Network, + block: &BlockType, + monitors: &[Monitor], +) -> Result, FilterError> + tracing::debug!("Processing block {", block_number); + // ... +} +``` + +Key aspects: + +1. `skip_all` - Skips automatic instrumentation of function parameters for performance +2. `fields(...)` - Adds specific fields we want to track (like network slug) +3. `tracing::debug!` - Adds debug-level spans for important operations diff --git a/docs/content/monitor/1.0.x/index.mdx b/docs/content/monitor/1.0.x/index.mdx new file mode 100644 index 00000000..690766ad --- /dev/null +++ b/docs/content/monitor/1.0.x/index.mdx @@ -0,0 +1,1438 @@ +--- +title: OpenZeppelin Monitor +--- + +## Overview + +In the rapidly evolving world of blockchain technology, effective monitoring is crucial for ensuring security and performance. OpenZeppelin Monitor is a blockchain monitoring service that watches for specific on-chain activities and triggers notifications based on configurable conditions. The service offers multi-chain support with configurable monitoring schedules, flexible trigger conditions, and an extensible architecture for adding new chains. + +### Key Capabilities + +* ***Real-time Monitoring***: Watch blockchain networks in real-time for specific events and transactions +* ***Smart Filtering***: Use flexible expressions to define exactly what you want to monitor +* ***Multi-notification Support***: Send alerts via Slack, Discord, Email, Telegram, Webhooks, or custom scripts +* ***Configurable Scheduling***: Set custom monitoring schedules using cron expressions +* ***Data Persistence***: Store monitoring data and resume from checkpoints +* ***Extensible Architecture***: Easy to add support for new blockchains and notification types + +### Supported Networks + +* ***EVM-Compatible Networks*** +* ***Stellar*** + +### Notification Channels + +* ***Slack*** - Send formatted messages to Slack channels +* ***Discord*** - Post alerts to Discord channels via webhooks +* ***Email*** - Send email notifications with SMTP support +* ***Telegram*** - Send messages to Telegram chats via bot API +* ***Webhooks*** - Send HTTP requests to custom endpoints +* ***Custom Scripts*** - Execute Python, JavaScript, or Bash scripts + + + +To get started immediately, see [Quickstart](/monitor/1.0.x/quickstart). + + +## Installation + +### Prerequisites + +* Use ***Rust 2021 edition***, version `1.86` or later. +* ***Docker*** (optional, for containerized deployment) + +### Local Installation + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Build the application:*** + + ```bash + cargo build --release + ``` +3. ***Move binary to project root:*** + + ```bash + mv ./target/release/openzeppelin-monitor . + ``` +4. ***Verify installation:*** + + ```bash + ./openzeppelin-monitor --help + ``` +5. ***View available options:*** + + ```bash + ./openzeppelin-monitor --help + + # Enable logging to file + ./openzeppelin-monitor --log-file + + # Enable metrics server + ./openzeppelin-monitor --metrics + + # Validate configuration files without starting the service + ./openzeppelin-monitor --check + ``` + +### Docker Installation + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Set up environment:*** + + ```bash + cp .env.example .env + # Edit .env file with your configuration + ``` +3. ***Start with Docker Compose:*** + + ```bash + cargo make docker-compose-up + ``` + +#### Metrics Configuration + +The metrics server, Prometheus, and Grafana can be enabled by setting `METRICS_ENABLED=true` in your `.env` file. + +You can start services directly with Docker Compose: + +```bash +# without metrics profile ( METRICS_ENABLED=false by default ) +docker compose up -d + +# With metrics enabled +docker compose --profile metrics up -d +``` + +To view prometheus metrics in a UI, you can use `http://localhost:9090` on your browser. + +To view grafana dashboard, you can use `http://localhost:3000` on your browser. + +By default, predefined metrics within a dashboard is populated in grafana. + +### Configuration Guidelines + +#### Recommended File Naming Conventions + +* Network configurations: `_.json` + * Example: `ethereum_mainnet.json`, `stellar_testnet.json` + * Should match the `slug` property inside the file +* Monitor configurations: `__monitor.json` + * Example: `usdc_transfer_monitor.json`, `dai_liquidation_monitor.json` + * Referenced by monitors using their `name` property +* Trigger configurations: `_.json` + * Example: `slack_notifications.json`, `email_alerts.json` + * Individual triggers referenced by their configuration key + +#### Configuration References + +* Monitor, network, and trigger names ***must be unique*** across all configurations files +* Monitor’s `networks` array must contain valid network `slug` values from network configuration files +* Monitor’s `triggers` array must contain valid trigger configuration keys +* Example valid references: + + ```json + // networks/ethereum_mainnet.json + { + "slug": "ethereum_mainnet", + ... + } + + // triggers/slack_notifications.json + { + "large_transfer_slack": { + ... + } + } + + // monitors/usdc_transfer_monitor.json + { + "networks": ["ethereum_mainnet"], + "triggers": ["large_transfer_slack"], + ... + } + ``` + + + + +Ensure all referenced slugs and trigger keys exist in their respective configuration files. The monitor will fail to start if it cannot resolve these references. + + + +#### Safe Protocol Guidelines + +The monitor implements protocol security validations across different components and will issue warnings when potentially insecure configurations are detected. While insecure protocols are not blocked, we strongly recommend following these security guidelines: + +##### Network Protocols + +###### RPC URLs +* **HTTPS Recommended**: Using `https://` for RPC endpoints is strongly recommended +* **WSS Recommended**: For WebSocket connections, `wss://` (secure WebSocket) is strongly recommended +* **Warning**: Using `http://` or `ws://` will trigger security warnings as they transmit data unencrypted + +##### Notification Protocols + +###### Webhook Notifications +* **HTTPS Recommended**: URLs should use HTTPS protocol +* **Authentication Recommended**: Including either: + * `X-API-Key` header + * `Authorization` header +* **Optional Secret**: Can include a secret for HMAC authentication + * When a secret is provided, the monitor will: + * Generate a timestamp in milliseconds + * Create an HMAC-SHA256 signature of the payload and timestamp + * Add the signature in the `X-Signature` header + * Add the timestamp in the `X-Timestamp` header + * The signature is computed as: `HMAC-SHA256(secret, payload + timestamp)` +* **Warning**: Non-HTTPS URLs or missing authentication headers will trigger security warnings + +###### Slack Notifications +* **HTTPS Recommended**: Webhook URLs should start with `https://hooks.slack.com/` +* **Warning**: Non-HTTPS URLs will trigger security warnings + +###### Discord Notifications +* **HTTPS Recommended**: Webhook URLs should start with `https://discord.com/api/webhooks/` +* **Warning**: Non-HTTPS URLs will trigger security warnings + +###### Telegram Notifications +* ***Protocol:*** `POST` request with a `application/json` payload to the `sendMessage` method. +* ***Endpoint:*** `https://api.telegram.org/bot/sendMessage` +* ***Security:*** + * ***HTTPS Required:*** The API endpoint uses HTTPS. + * Authentication is handled via the ***Bot Token*** in the URL. Keep this token secure. +* ***Formatting:*** Messages are sent with `parse_mode` set to `MarkdownV2`. Special characters in the message title and body are automatically escaped to prevent formatting errors. + +###### Email Notifications +* **Secure Ports Recommended**: The following ports are considered secure: + * 465: SMTPS (SMTP over SSL) + * 587: SMTP with STARTTLS + * 993: IMAPS (IMAP over SSL) +* **Warning**: Using other ports will trigger security warnings +* **Valid Format**: Email addresses must follow RFC 5322 format + +###### Notifications Retry Policy + +Following notification protocols support retry policies: + +* Slack +* Discord +* Telegram +* Webhook +* Email + +Default retry policy is using exponential backoff with the following parameters: +| | | | +| --- | --- | --- | +| Parameter | Default Value | Description | +| `max_retries` | `3` | Maximum number of retries before giving up | +| `base_for_backoff` | `2` | Base duration for exponential backoff calculations in seconds | +| `initial_backoff` | `250` | Initial backoff duration in milliseconds | +| `max_backoff` | `10` | Maximum backoff duration in seconds | +| `jitter` | `Full` | Jitter strategy to apply to the backoff duration, currently supports `Full` and `None` | + +These parameters can be overridden by providing custom `RetryConfig` struct in `retry_policy` field in trigger configuration. + +##### Script Security + +###### File Permissions (Unix Systems) +* **Restricted Write Access**: Script files should not have overly permissive write permissions +* **Recommended Permissions**: Use `644` (`rw-r--r--`) for script files +* **Warning**: Files with mode `022` or more permissive will trigger security warnings + +**Example Setting Recommended Permissions** + +```bash +chmod 644 ./config/filters/my_script.sh +``` + +#### Secret Management + +The monitor implements a secure secret management system with support for multiple secret sources and automatic memory zeroization. + +##### Secret Sources + +The monitor supports three types of secret sources: + +* **Plain Text**: Direct secret values (wrapped in `SecretString` for secure memory handling) +* **Environment Variables**: Secrets stored in environment variables +* **Hashicorp Cloud Vault**: Secrets stored in Hashicorp Cloud Vault + +##### Security Features + +* **Automatic Zeroization**: Secrets are automatically zeroized from memory when no longer needed +* **Type-Safe Resolution**: Secure handling of secret resolution with proper error handling +* **Configuration Support**: Serde support for configuration files + +##### Configuration + +Secrets can be configured in the JSON files using the following format: + +```json +{ + "type": "Plain", + "value": "my-secret-value" +} +``` + +```json +{ + "type": "Environment", + "value": "MY_SECRET_ENV_VAR" +} +``` + +```json +{ + "type": "HashicorpCloudVault", + "value": "my-secret-name" +} +``` + +##### Hashicorp Cloud Vault Integration + +To use Hashicorp Cloud Vault, configure the following environment variables: + +| Environment Variable | Description | +| --- | --- | +| `HCP_CLIENT_ID` | Hashicorp Cloud Vault client ID | +| `HCP_CLIENT_SECRET` | Hashicorp Cloud Vault client secret | +| `HCP_ORG_ID` | Hashicorp Cloud Vault organization ID | +| `HCP_PROJECT_ID` | Hashicorp Cloud Vault project ID | +| `HCP_APP_NAME` | Hashicorp Cloud Vault application name | + +##### Best Practices + +* Use environment variables or vault for production secrets +* Avoid storing plain text secrets in configuration files +* Use appropriate access controls for vault secrets +* Monitor vault access patterns for suspicious activity + +#### Basic Configuration + +* Set up environment variables: + +Copy the example environment file and update values according to your needs + +```bash +cp .env.example .env +``` + +This table lists the environment variables and their default values. + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `RUST_LOG` | `info` | `info, debug, warn, error, trace` | Log level. | +| `LOG_MODE` | `stdout` | `stdout, file` | Write logs either to console or to file. | +| `LOG_DATA_DIR` | `logs/` | `` | Directory to write log files on host. | +| `MONITOR_DATA_DIR` | `null` | `` | Persist monitor data between container restarts. | +| `LOG_MAX_SIZE` | `1073741824` | `` | Size after which logs needs to be rolled. Accepts both raw bytes (e.g., "1073741824") or human-readable formats (e.g., "1GB", "500MB"). | +| `METRICS_ENABLED` | `false` | `true`, `false` | Enable metrics server for external tools to scrape metrics. | +| `METRICS_PORT` | `8081` | `` | Port to use for metrics server. | +| `HCP_CLIENT_ID` | - | `` | Hashicorp Cloud Vault client ID for secret management. | +| `HCP_CLIENT_SECRET` | - | `` | Hashicorp Cloud Vault client secret for secret management. | +| `HCP_ORG_ID` | - | `` | Hashicorp Cloud Vault organization ID for secret management. | +| `HCP_PROJECT_ID` | - | `` | Hashicorp Cloud Vault project ID for secret management. | +| `HCP_APP_NAME` | - | `` | Hashicorp Cloud Vault application name for secret management. | +* Copy and configure some example files: + +```bash +# EVM Configuration +cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json +cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json + +# Stellar Configuration +cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json +cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json + +# Notification Configuration +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json + +# Filter Configuration +cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh +cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh +``` +### Command Line Options + +The monitor supports several command-line options for configuration and control: + +| **Option** | **Default** | **Description** | +| --- | --- | --- | +| `**--log-file**` | `false` | Write logs to file instead of stdout | +| `**--log-level**` | `info` | Set log level (trace, debug, info, warn, error) | +| `**--log-path**` | `logs/` | Path to store log files | +| `**--log-max-size**` | `1GB` | Maximum log file size before rolling | +| `**--metrics-address**` | `127.0.0.1:8081` | Address to start the metrics server on | +| `**--metrics**` | `false` | Enable metrics server | +| `**--monitor-path**` | - | Path to the monitor to execute (for testing) | +| `**--network**` | - | Network to execute the monitor for (for testing) | +| `**--block**` | - | Block number to execute the monitor for (for testing) | +| `**--check**` | `false` | Validate configuration files without starting the service | + +## Data Storage Configuration + +The monitor uses file-based storage by default. + +### File Storage + +When `store_blocks` is enabled in the network configuration, the monitor stores: + +* Processed blocks: `./data/_blocks_.json` +* Missed blocks: `./data/_missed_blocks.txt` (used to store missed blocks) + +The content of the `missed_blocks.txt` file may help to determine the right `max_past_blocks` value based on the network’s block time and the monitor’s cron schedule. + +Additionally, the monitor will always store: + +* Last processed block: `./data/_last_block.txt` (enables resuming from last checkpoint) + +## Configuration Files + +### Network Configuration + +A Network configuration defines connection details and operational parameters for a specific blockchain network, supporting both EVM and Stellar-based chains. + +**Example Network Configuration** + +```json +{ + "network_type": "Stellar", + "slug": "stellar_mainnet", + "name": "Stellar Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "https://soroban.stellar.org" + }, + "weight": 100 + } + ], + "network_passphrase": "Public Global Stellar Network ; September 2015", + "block_time_ms": 5000, + "confirmation_blocks": 2, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 20, + "store_blocks": true +} +``` + +#### Available Fields + +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**network_type**` | `String` | Type of blockchain (**"EVM"** or **"Stellar"**) | +| `**slug**` | `String` | **Required** - **_Unique_** identifier for the network | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable network name | +| `**rpc_urls**` | `Array[Object]` | List of RPC endpoints with weights for load balancing | +| `**chain_id**` | `Number` | Network chain ID (**EVM only**) | +| `**network_passphrase**` | `String` | Network identifier (**Stellar only**) | +| `**block_time_ms**` | `Number` | Average block time in milliseconds | +| `**confirmation_blocks**` | `Number` | Number of blocks to wait for confirmation | +| `**cron_schedule**` | `String` | Monitor scheduling in cron format | +| `**max_past_blocks**` | `Number` | Maximum number of past blocks to process | +| `**store_blocks**` | `Boolean` | Whether to store processed blocks (defaults output to `./data/` directory) | + +#### Important Considerations + +* We strongly recommend using private RPC providers for improved reliability. + +### Trigger Configuration + +A Trigger defines actions to take when monitored conditions are met. Triggers can send notifications, make HTTP requests, or execute scripts. + +**Example Trigger Configuration** + +```json +{ + "evm_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "https://hooks.slack.com/services/A/B/C" + }, + "message": { + "title": "large_transfer_slack triggered", + "body": "Large transfer of ${events.0.args.value} USDC from $events.0.args.from to $events.0.args.to | https://etherscan.io/tx/$transaction.hash#eventlog" + } + } + }, + "stellar_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "environment", + "value": "SLACK_WEBHOOK_URL" + }, + "message": { + "title": "large_transfer_usdc_slack triggered", + "body": "${monitor.name} triggered because of a large transfer of $functions.0.args.amount USDC to $functions.0.args.to | https://stellar.expert/explorer/testnet/tx/$transaction.hash" + } + } + } +} +``` + +#### Trigger Types + +##### Slack Notifications +```json +{ + "slack_url": { + "type": "HashicorpCloudVault", + "value": "slack-webhook-url" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Slack Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"slack"** for Slack notifications | +| `**config.slack_url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.slack_url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Title that appears in the Slack message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Email Notifications +```json +{ + "host": "smtp.gmail.com", + "port": 465, + "username": { + "type": "plain", + "value": "sender@example.com" + }, + "password": { + "type": "environment", + "value": "SMTP_PASSWORD" + }, + "message": { + "title": "Alert Subject", + "body": "Alert message for ${transaction.hash}" + }, + "sender": "sender@example.com", + "recipients": ["recipient@example.com"] +} +``` + +##### Email Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"email"** for email notifications | +| `**config.host**` | `String` | SMTP server hostname | +| `**config.port**` | `Number` | SMTP port (defaults to **465**) | +| `**config.username.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.username.value**` | `String` | Secret value (username, environment variable name, or vault secret name) | +| `**config.password.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.password.value**` | `String` | Secret value (password, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Email subject line | +| `**config.message.body**` | `String` | Email body template with variable substitution | +| `**config.sender**` | `String` | Sender email address | +| `**config.recipients**` | `Array[String]` | List of recipient email addresses | + +##### Webhook Notifications +```json +{ + "url": { + "type": "HashicorpCloudVault", + "value": "webhook-url" + }, + "method": "POST", + "secret": { + "type": "environment", + "value": "WEBHOOK_SECRET" + }, + "headers": { + "Content-Type": "application/json" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Webhook Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"webhook"** for webhook notifications | +| `**config.url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.method**` | `String` | HTTP method (POST, GET, etc.) defaults to POST | +| `**config.secret.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.secret.value**` | `String` | Secret value (HMAC secret, environment variable name, or vault secret name) | +| `**config.headers**` | `Object` | Headers to include in the webhook request | +| `**config.message.title**` | `String` | Title that appears in the webhook message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Discord Notifications +```json +{ + "discord_url": { + "type": "plain", + "value": "https://discord.com/api/webhooks/123-456-789" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Discord Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"discord"** for Discord notifications | +| `**config.discord_url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.discord_url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Title that appears in the Discord message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Telegram Notifications +```json +{ + "token": { + "type": "HashicorpCloudVault", + "value": "telegram-bot-token" + }, + "chat_id": "9876543210", + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Telegram Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"telegram"** for Telegram notifications | +| `**config.token.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.token.value**` | `String` | Secret value (bot token, environment variable name, or vault secret name) | +| `**config.chat_id**` | `String` | Telegram chat ID | +| `**config.disable_web_preview**` | `Boolean` | Whether to disable web preview in Telegram messages (defaults to false) | +| `**config.message.title**` | `String` | Title that appears in the Telegram message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Custom Script Notifications +```json +{ + "language": "Bash", + "script_path": "./config/triggers/scripts/custom_notification.sh", + "arguments": ["--verbose"], + "timeout_ms": 1000 +} +``` + +##### Script Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"script"** for Custom Script notifications | +| `**language**` | `String` | The language of the script | +| `**script_path**` | `String` | The path to the script | +| `**arguments**` | `Array[String]` | The arguments of the script (optional). | +| `**timeout_ms**` | `Number` | The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed. | + +For more information about custom scripts, see [Custom Scripts Section](/monitor/1.0.x/scripts). + + + +***Security Risk***: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +#### Available Template Variables + +The monitor uses a structured JSON format with nested objects for template variables. The data is flattened into dot notation for template use. + +##### Common Variables +| **Variable** | **Description** | +| --- | --- | +| `**monitor.name**` | Name of the triggered monitor | +| `**transaction.hash**` | Hash of the transaction | +| `**functions.[index].signature**` | Function signature | +| `**events.[index].signature**` | Event signature | + +##### Network-Specific Variables + +###### EVM Variables +| **Variable** | **Description** | +| --- | --- | +| `**transaction.from**` | Sender address | +| `**transaction.to**` | Recipient address | +| `**transaction.value**` | Transaction value | +| `**events.[index].args.[param]**` | Event parameters by name | +| `**functions.[index].args.[param]**` | Function parameters by name | + +###### Stellar Variables +| **Variable** | **Description** | +| --- | --- | +| `**events.[index].args.[position]**` | Event parameters by position | +| `**functions.[index].args.[param]**` | Function parameters by name | + + + +Transaction-related variables (`transaction.from`, `transaction.to`, `transaction.value`) are not available for Stellar networks. + + +#### Message Formatting + +Slack, Discord, Telegram, Email and Webhook support Markdown formatting in their message bodies. You can use Markdown syntax to enhance your notifications. + +##### Example Email Notification with Markdown +```json +{ + "email_notification": { + "name": "Formatted Alert", + "trigger_type": "email", + "config": { + "host": "smtp.example.com", + "port": 465, + "username": {"type": "plain", "value": "alerts@example.com"}, + "password": {"type": "plain", "value": "password"}, + "message": { + "title": "**High Value Transfer Alert**", + "body": "### Transaction Details\n\n* **Amount:** ${events.0.args.value} USDC\n* **From:** `$events.0.args.from`\n* **To:** `$events.0.args.to`\n\n> Transaction Hash: $transaction.hash\n\n[View on Explorer](https://etherscan.io/tx/$transaction.hash)" + }, + "sender": "alerts@example.com", + "recipients": ["recipient@example.com"] + } + } +} +``` + +##### Example Slack Notification with Markdown +```json +{ + "slack_notification": { + "name": "Formatted Alert", + "trigger_type": "slack", + "config": { + "slack_url": {"type": "plain", "value": "https://hooks.slack.com/services/XXX/YYY/ZZZ"}, + "message": { + "title": "*🚨 High Value Transfer Alert*", + "body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `$events.0.args.from`\n• *To:* `$events.0.args.to`\n\n>Transaction Hash: `$transaction.hash`\n\n" + } + } + } +} +``` + +##### Example Discord Notification with Markdown +```json +{ + "discord_notification": { + "name": "Formatted Alert", + "trigger_type": "discord", + "config": { + "discord_url": {"type": "plain", "value": "https://discord.com/api/webhooks/XXX/YYY"}, + "message": { + "title": "**🚨 High Value Transfer Alert**", + "body": "# Transaction Details\n\n* **Amount:** `${events.0.args.value}` USDC\n* **From:** `$events.0.args.from`\n* **To:** `$events.0.args.to`\n\n>>> Transaction Hash: `$transaction.hash`\n\n**[View on Explorer](https://etherscan.io/tx/$transaction.hash)" + } + } + } +} +``` + +##### Example Telegram Notification with Markdown +```json +{ + "telegram_notification": { + "name": "Formatted Alert", + "trigger_type": "telegram", + "config": { + "token": {"type": "plain", "value": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, + "chat_id": "9876543210", + "message": { + "title": "*🚨 High Value Transfer Alert*", + "body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `$events.0.args.from`\n• *To:* `$events.0.args.to`\n\n`Transaction Hash: $transaction.hash`\n\n[View on Explorer](https://etherscan.io/tx/$transaction.hash)" + } + } + } +} +``` + +#### Important Considerations + +* Email notification port defaults to 465 if not specified. +* Template variables are context-dependent: + * Event-triggered notifications only populate event variables. + * Function-triggered notifications only populate function variables. + * Mixing contexts results in empty values. +* Credentials in configuration files should be properly secured. +* Consider using environment variables for sensitive information. + +### Monitor Configuration + +A Monitor defines what blockchain activity to watch and what actions to take when conditions are met. Each monitor combines: + +* Network targets (which chains to monitor) +* Contract addresses to watch +* Conditions to match (functions, events, transactions) +* Trigger conditions (custom scripts that act as filters for each monitor match to determine whether a trigger should be activated). +* Triggers to execute when conditions are met + +**Example Monitor Configuration** + +```json +{ + "name": "Large USDC Transfers", + "networks": ["ethereum_mainnet"], + "paused": false, + "addresses": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "contract_spec": [ ... ] + } + ], + "match_conditions": { + "functions": [ + { + "signature": "transfer(address,uint256)", + "expression": "value > 1000000" + } + ], + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 1000000" + } + ], + "transactions": [ + { + "status": "Success", + "expression": "value > 1500000000000000000" + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"] +} +``` + +#### Available Fields + +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** identifier for this monitor | +| `**networks**` | `Array[String]` | List of network slugs this monitor should watch | +| `**paused**` | `Boolean` | Whether this monitor is currently paused | +| `**addresses**` | `Array[Object]` | Contract addresses to monitor with optional ABIs | +| `**match_conditions**` | `Object` | Collection of conditions that can trigger the monitor | +| `**trigger_conditions**` | `Array[Object]` | Collection of filters to apply to monitor matches before executing triggers | +| `**triggers**` | `Array[String]` | IDs of triggers to execute when conditions match | + +#### Match Conditions + +Monitors support three types of match conditions that can be combined: + +##### Function Conditions +Match specific function calls to monitored contracts: + +```json +{ + "functions": [ + { + "signature": "transfer(address,uint256)", + "expression": "value > 1000" + } + ] +} +``` + +##### Event Conditions +Match events emitted by monitored contracts: + +```json +{ + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 1000000" + } + ] +} +``` + +##### Transaction Conditions +Match transaction properties. The available fields and expression syntax depend on the network type (EVM/Stellar) + +```json +{ + "transactions": [ + { + "status": "Success", // Only match successful transactions + "expression": "value > 1500000000000000000" // Match transactions with value greater than 1.5 ETH + } + ] +} +``` + +#### Available Transaction Fields (EVM) +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**value**` | `uint256` | Transaction value in wei | +| `**from**` | `address` | Sender address (case-insensitive comparison) | +| `**to**` | `address` | Recipient address (case-insensitive comparison) | +| `**hash**` | `string` | Transaction hash | +| `**gas_price**` | `uint256` | Gas price in wei (legacy transactions) | +| `**max_fee_per_gas**` | `uint256` | EIP-1559 maximum fee per gas | +| `**max_priority_fee_per_gas**` | `uint256` | EIP-1559 priority fee | +| `**gas_limit**` | `uint256` | Gas limit for transaction | +| `**nonce**` | `uint256` | Sender nonce | +| `**input**` | `string` | Hex-encoded input data (e.g., **"0xa9059cbb..."**) | +| `**gas_used**` | `uint256` | Actual gas used (from receipt) | +| `**transaction_index**` | `uint64` | Position in block | + +#### Available Transaction Fields (Stellar) +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**hash**` | `string` | Transaction hash | +| `**ledger**` | `i64` | Ledger sequence number where the transaction was included | +| `**value**` | `i64` | Value associated with the **first** relevant operation (e.g., payment amount). Defaults to 0 if no relevant operation or value is found. | +| `**from**` | `address` | Source account address of the **first** relevant operation (e.g., payment sender). Case-insensitive comparison. | +| `**to**` | `address` | Destination account address of the **first** relevant operation (e.g., payment recipient or invoked contract). Case-insensitive comparison. | + +#### Matching Rules + +* If no conditions are specified, all transactions match +* For multiple condition types: + * Transaction conditions are checked first + * Then either function OR event conditions must match + * Both transaction AND (function OR event) must match if both specified + +### Expressions + +Expressions allow for condition checking of function arguments, event parameters, and transaction fields. + +**Supported Parameter/Field Types and Basic Operations:** + +| Type | Description | Example Operators | Notes | +| --- | --- | --- | --- | +| `**Numeric (uint/int variants)**` | Integer values (e.g., `42`, `-100`) or decimal values (e.g., `3.14`, `-0.5`). | `>`, `>=`, `<`, `<=`, `==`, `!=` | Numbers must have digits before and after a decimal point if one is present (e.g., `.5` or `5.` are not valid standalone numbers). | +| `**Address**` | Blockchain addresses. | `==`, `!=` | Comparisons (e.g., `from == '0xABC...'`) are typically case-insensitive regarding the hex characters of the address value itself. | +| `**String**` | Text values. Can be single-quoted (e.g., ’hello'`) or, on the right-hand side of a comparison, unquoted (e.g., `active`). | `==`, `!=`, `starts_with`, `ends_with`, `contains` | Quoted strings support `\'` to escape a single quote and `\\` to escape a backslash. All string comparison operations (e.g., `name == 'Alice'`, `description contains 'error'`) are performed case-insensitively during evaluation. See the dedicated "String Operations" section for more examples and details. | +| `**Boolean**` | True or false values. | `==`, `!=` | Represented as `true` or `false`. These keywords are parsed case-insensitively (e.g., `TRUE`, `False` are also valid in expressions). | +| `**Hex String Literal**` | A string literal starting with `0x` or `0X` followed by hexadecimal characters (0-9, a-f, A-F). | `==`, `!=`, `starts_with`, `ends_with`, `contains` | Treated as a string for comparison purposes (e.g., `input_data starts_with '0xa9059cbb'`). Comparison is case-sensitive for the hex characters after `0x`. | +| `**Array (EVM/Stellar)**` | Ordered list of items. For Stellar, often a JSON string in config (e.g., ’["a", "id":1]'`). For EVM, typically decoded from ABI parameters. | `contains`, `==`, `!=`, `[index]` | Detailed operations, including indexed access and behavior of `contains`, vary by network. See "Operations on Complex Types" below. | +| `**Object/Map (Stellar)**` | Key-value pairs, typically represented as a JSON string in config (e.g., ’"key": "value", "id": 123'`). | `.key_access`, `==`, `!=` | Supports dot notation for field access (e.g., `data.id`). See "Operations on Complex Types" for details. | +| `**Vec (Stellar)**` | Ordered list, where the parameter’s value can be a CSV string (e.g., `"foo,bar"`) or a JSON array string (e.g., ’["foo","bar"]'`). | `contains`, `==`, `!=` | Behavior of `contains` and `==` differs based on whether the value is CSV or a JSON array string. See "Operations on Complex Types" for details. | + +**Logical Operators:** + +* AND - All conditions must be true +* OR - At least one condition must be true +* () - Parentheses for grouping +* AND has higher precedence than OR (i.e., AND operations are evaluated before OR operations if not grouped by parentheses) + +**Variable Naming and Access (Left-hand side of conditions):** + +The left-hand side (LHS) of a condition specifies the data field or parameter whose value you want to evaluate. + +**Base Names:** + +* These are the direct names of parameters or fields, such as `amount`, `from`, `status`, or event parameter indices like `0`, `1` (common in Stellar events). +* Base names can consist of alphanumeric characters (a-z, A-Z, 0-9) and underscores (`_`). +* They can start with a letter, an underscore, or a digit. Starting with a digit is primarily relevant for numerically indexed parameters (e.g., Stellar event parameters). +* **Important:** Variable names are case-sensitive during evaluation. The name used in the expression must exactly match the casing of the field name in the source data (e.g., from an ABI or blockchain data structure). For example, if a field is named `TotalValue` in the data, an expression using `totalvalue` will not find it. +* Variable names cannot be keywords (e.g., `true`, `AND`, `OR`, `contains`). Keywords themselves are parsed case-insensitively. + +**Path Accessors (for complex types):** + +If a base parameter is a complex type like an object, map, or array, you can access its internal data using accessors: + +**Key Access:** Use dot notation (`.`) to access properties of an object or map. + +* Examples: `transaction.value`, `user.name`, `data.0` (if `0` is a valid key name as a string). +* Keys typically consist of alphanumeric characters and underscores. They usually start with a letter or underscore, but purely numeric keys (e.g., `.0`, `.123`) are also supported for map-like structures where keys might be strings representing numbers. +* Keys cannot contain hyphens (`-`). + +**Index Access:** Use bracket notation (`[]`) to access elements of an array by their zero-based integer index. + +* Examples: `my_array[0]`, `log_entries[3]`. +* The index must be a non-negative integer. + +**Combined Access:** You can combine key and index accessors to navigate nested structures. + +* Example: `event.data_array[0].property` (accesses the `property` field of the first object in `data_array`, which is part of `event`). +* Example: `map.numeric_key_as_string_0[1].name` (accesses the `name` property of the second element of an array stored under the key `0` in `map`). + +**String Operations:** + +Several operators are available for matching patterns and comparing string values. These are particularly useful for EVM transaction `input` data, Stellar parameters defined with `kind: "string"`, or any other field that contains text. + +* `string_param starts_with 'prefix'`:: + Checks if the string parameter’s value begins with the specified `prefix`. + Example: `transaction.input starts_with '0xa9059cbb'` (checks for ERC20 transfer function selector). +* `string_param ends_with 'suffix'`:: + Checks if the string parameter’s value ends with the specified `suffix`. + Example: `file_name ends_with '.txt'` +* `string_param contains 'substring'`:: + Checks if the string parameter’s value contains the specified `substring` anywhere within it. + Example: `message contains 'error'` +* `string_param == 'exact_string'`:: + Checks if the string parameter’s value is exactly equal to `exact_string`. +* `string_param != 'different_string'`:: + Checks if the string parameter’s value is not equal to `different_string`. + +**Important Notes on String Operations:** + +* **Operator Keywords:** The operator keywords themselves (`starts_with`, `ends_with`, `contains`, `AND`, `OR`, `true`, `false`, comparison symbols like `==`, `>`) are parsed case-insensitively. For example, `CONTAINS` is treated the same as `contains`, and `TRUE` is the same as `true`. +* **Case-Insensitive Evaluation for String Comparisons:** When comparing string data (e.g., from event parameters, transaction fields, or function arguments) with literal string values in your expression, all standard string operations perform a ***case-insensitive*** comparison during evaluation. + * Equality (`==`) and Inequality (`!=`) + * Pattern matching (`starts_with`, `ends_with`, `contains`) +* **Variable Name Case Sensitivity:** It is important to distinguish this from variable names (the left-hand side of your condition, e.g., `status`). Variable names **are** case-sensitive and must exactly match the field names in your source data (ABI, etc.). + +**Whitespace Handling:** +Flexible whitespace is generally allowed around operators, parentheses, and keywords for readability. However, whitespace within quoted string literals is significant and preserved. + +#### Operations on Complex Types + +Beyond simple primitive types, expressions can also interact with more complex data structures like arrays, objects, and vectors. + +##### EVM Specifics + +**Array Operations (`kind: "array"`)** + +When an EVM parameter is an array (often represented internally or configured with `kind: "array"` and its value being a JSON string representation if manually configured), the following operations are supported: + +* `array_param contains 'value'` checks if the string ’value'` exists within the array. +* `array_param == '["raw_json_array_string"]'` string comparison of the array’s entire JSON string representation against the provided string +* `array_param != '["raw_json_array_string"]'` the negation of the above +* `array_param[0]` indexed access + +##### Stellar Specifics + +**Object (`kind: "object"`) / Map (`kind: "Map"`) Operations** + +* `object_param.key == 'value'` checks if the object or map has a key named `key` with the value ’value'`. +* `object_param.nested_key.another_key > 100` checks if the nested key `another_key` within `nested_key` has a value greater than 100. +* `object_param == '"raw_json_object_string"'` checks if the object or map matches the provided JSON string representation. +* `object_param != '"raw_json_object_string"'` the negation of the above + +**Array (`kind: "array"`) Operations** + +* `array_param[index]` accesses the element at the specified `index` in the array. +* `array_param[0] == 'value'` checks if the first element in the array is equal to ’value'`. +* `array_param[1].property == 'value'` checks if the second element in the array has a property named `property` with the value ’value'`. +* `array_param contains 'value'` checks if the array contains the string ’value'`. +* `array_param == '["raw_json_array_string"]'` checks if the array matches the provided JSON string representation. +* `array_param != '["raw_json_array_string"]'` the negation of the above + +**Vector (`kind: "vec"`) Operations** +When a Stellar parameter has `kind: "vec"`, its value can be either a CSV string or a JSON array string. + +* `vec_param contains 'item'` checks if the vector contains the string ’item'`. This works for both CSV and JSON array strings. +* `vec_param == 'raw_string_value'` checks if the vector matches the provided raw string value. This works for both CSV and JSON array strings. +* `vec_param != 'raw_string_value'` the negation of the above + +**Event Parameter Access (Stellar)** + +Stellar event parameters are typically accessed by their numeric index as the base variable name (e.g., `0`, `1`, `2`). If an indexed event parameter is itself a complex type (like an array or map, represented as a JSON string), you can then apply the respective access methods: + +* If event parameter `0` (kind: "Map") is ’"id": 123, "name": "Test"'`: + * `0.id == 123` + * `0.name contains 'est'` (case-insensitive) +* If event parameter `1` (kind: "array") is ’["alpha", "val": "beta"]'`: + * `1[0] == 'ALPHA'` (case-insensitive) + * `1[1].val == 'Beta'` (case-insensitive) + * `1 contains 'beta'` (case-insensitive deep search) + +##### EVM Examples + +These examples assume common EVM event parameters or transaction fields. + +**Basic Comparisons** + +```json +// Numeric +"transaction.value > 1000000000000000000" // Value greater than 1 ETH +"event.amount <= 500" +"block.number == 12345678" + +// String (case-insensitive evaluation for '==' and 'contains') +"transaction.to == '0xdeadbeef...'" // Address check (address value comparison itself is case-insensitive) +"event.token_name == 'mytoken'" +"transaction.input contains 'a9059cbb'" // Checks for ERC20 transfer selector + +// Boolean +"receipt.status == true" // or simply "receipt.status" if boolean field can be evaluated directly +"event.isFinalized == false" +``` + +**Logical Operators** + +```json +"transaction.value > 1000 AND event.type == 'Deposit'" +"(receipt.status == true OR event.fallback_triggered == true) AND user.is_whitelisted == false" +``` + +**String Operations** + +```json +"transaction.input starts_with '0xa9059cbb'" // Case-insensitive for the operation +"event.message ends_with 'failed'" +"event.details contains 'critical alert'" +``` + +**Array Operations** + +Assume `event.ids` is `[10, 20, 30]` and `event.participants` is `["user": "Alice", "role": "admin", "user": "Bob", "role": "editor"]`. +```json +"event.ids[0] == 10" +"event.ids contains '20'" // Checks for string '20' (case-insensitive) + +"event.participants contains 'Alice'" // True (deep search, case-insensitive) +"event.participants contains 'editor'" // True (deep search, case-insensitive) +"event.participants == '[\"user\": \"Alice\", \"role\": \"admin\", \"user\": \"Bob\", \"role\": \"editor\"]'" // Raw JSON match (case-sensitive for structure and keys) +``` + +##### Stellar Examples + +**Basic Comparisons** + +```json +// Numeric +"event.params.amount > 10000000" // Accessing 'amount' field in an object 'params' +"ledger.sequence >= 123456" + +// String (case-insensitive evaluation for '==' and 'contains') +"event.params.recipient == 'GBD22...'" // Address check +"event.type == 'payment_processed'" + +// Boolean +"transaction.successful == true" +"event.data.is_verified == false" +``` + +**Logical Operators** + +```json +"event.data.value > 500 AND event.source_account == 'GCA7Z...'" +"(event.type == 'TRANSFER' OR event.type == 'PAYMENT') AND event.params.asset_code == 'XLM'" +``` + +**String Operations** + +```json +"event.contract_id starts_with 'CA23...'" +"event.memo ends_with '_TEST'" +"event.params.description contains 'urgent'" +``` + +**Object (`kind: "object"`) / Map (`kind: "Map"`) Operations** + +Assume `event.details` (kind: "Map") is ’"id": 123, "user": "name": "CHarlie", "status": "Active", "tags": ["new"]'`. +```json +"event.details.id == 123" +"event.details.user.name == 'charlie'" // Case-insensitive string comparison +"event.details.user.status contains 'act'" // Case-insensitive contains +"event.details.tags == '[\"new\"]'" // Raw JSON string match for the 'tags' field +``` + +**Array (`kind: "array"`) Operations** + +Assume `event.items` (kind: "array") is ’["sku": "A1", "qty": 10, "sku": "B2", "qty": 5, "notes":"Rush order"]'`. +```json +"event.items[0].sku == 'a1'" +"event.items[1].qty < 10" +"event.items contains 'A1'" // Deep search (case-insensitive) +"event.items contains 'rush order'" // Deep search (case-insensitive) +``` + +**Vector (`kind: "vec"`) Operations** + +Assume `csv_data` (kind: "vec") is `"ALPHA,Bravo,Charlie"` and `json_array_data` (kind: "vec") is ’["Delta", "id": "ECHO", "Foxtrot"]'`. +```json +"csv_data contains 'bravo'" // Case-insensitive CSV element match +"csv_data == 'ALPHA,Bravo,Charlie'" // Raw string match + +"json_array_data contains 'delta'" // Case-insensitive deep search (like array) +"json_array_data contains 'ECHO'" // Case-insensitive deep search (like array) +``` + +**Event Parameter Access (Numerically Indexed)** + +Assume event parameter `0` is `12345` (u64), `1` (kind: "array") is ’["Val1", "VAL2"]'`, and `2` (kind: "Map") is ’"keyA": "dataX", "keyB": 789'`. +```json +"0 > 10000" +"1[0] == 'val1'" +"1 contains 'val2'" +"2.keyA == 'DATAX'" +"2.keyB < 1000" +``` + + + +With SEP-48 support, Stellar functions can now reference parameters by name (e.g., `amount > 1000`) instead of position (e.g., `2 > 1000`). Events still use indexed parameters until SEP-48 support is added for events. + +You can find the contract specification through Stellar contract explorer tool. For example: +[Stellar DEX Contract Interface](https://lab.stellar.org/smart-contracts/contract-explorer?$=network$id=mainnet&label=Mainnet&horizonUrl=https:////horizon.stellar.org&rpcUrl=https:////mainnet.sorobanrpc.com&passphrase=Public%20Global%20Stellar%20Network%20/;%20September%202015;&smartContracts$explorer$contractId=CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE;;) + + +#### Trigger Conditions (Custom filters) + +Custom filters allow you to create sophisticated filtering logic for processing monitor matches. These filters act as additional validation layers that determine whether a match should trigger the execution of a trigger or not. + +For more information about custom scripts, see [Custom Scripts Section](/monitor/1.0.x/scripts). + + + +***Security Risk***: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +**Example Trigger Conditions Configuration** + +```json +{ + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "Bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 +} +``` + +#### Available Fields + +##### Trigger Conditions Fields +| Field | Type | Description | +| --- | --- | --- | +| `**script_path**` | String | The path to the script | +| `**language**` | String | The language of the script | +| `**arguments**` | Array[String] | The arguments of the script (optional). | +| `**timeout_ms**` | Number | The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed and the match will be included by default. | + +#### Important Considerations + +* Network slugs in the monitor must match valid network configurations. +* Trigger IDs must match configured triggers. +* Expression syntax and available variables differ between EVM and Stellar networks. +* ABIs can be provided in two ways: + * For EVM networks: Through the monitor configuration using standard Ethereum ABI format + * For Stellar networks: Through the monitor configuration using SEP-48 format, or automatically fetched from the chain if not provided +* The monitoring frequency is controlled by the network’s `cron_schedule`. +* Each monitor can watch multiple networks and addresses simultaneously. +* Monitors can be paused without removing their configuration. + +## Running the Monitor + +### Local Execution + +1. ***Basic startup:*** + + ```bash + ./openzeppelin-monitor + ``` +2. ***With logging to file:*** + + ```bash + ./openzeppelin-monitor --log-file + ``` +3. ***With metrics enabled:*** + + ```bash + ./openzeppelin-monitor --metrics + ``` +4. ***Validate configuration without starting:*** + + ```bash + ./openzeppelin-monitor --check + ``` + +### Docker Execution + +1. ***Start all services:*** + + ```bash + cargo make docker-compose-up + ``` +2. ***With metrics and monitoring (Prometheus + Grafana):*** + + ```bash + # Set METRICS_ENABLED=true in .env file, then: + docker compose --profile metrics up -d + ``` +3. ***View logs:*** + + ```bash + docker compose logs -f monitor + ``` +4. ***Stop services:*** + + ```bash + cargo make docker-compose-down + ``` + +### Command Line Options + +| | | | +| --- | --- | --- | +| Option | Default | Description | +| `--log-file` | `false` | Write logs to file instead of stdout | +| `--log-level` | `info` | Set log level (trace, debug, info, warn, error) | +| `--metrics` | `false` | Enable metrics server on port 8081 | +| `--check` | `false` | Validate configuration files only | +| `--help` | - | Show all available options | + +### Testing your configuration + +#### Network Configuration +The `validate_network_config.sh` script helps ensure your network configuration is properly set up and operational. The script: + +* Tests the health of all configured RPC endpoints +* Validates connectivity using network-specific methods +* Provides clear visual feedback for each endpoint + +```bash +# Test default networks directory (/config/networks/) +./scripts/validate_network_config.sh + +# Test a specific configuration directory +./scripts/validate_network_config.sh -f /path/to/configs +``` + + +Run this script when setting up new networks, before deploying configuration changes, or when troubleshooting connectivity issues. + + +#### Validating Configuration Files + +Before starting the monitor service, you can validate your configuration files using the `--check` option: + +```bash +./openzeppelin-monitor --check +``` + +This command will: + +* Parse and validate all configuration files +* Check for syntax errors +* Verify references between monitors, networks, and triggers +* Report any issues without starting the service + +It’s recommended to run this check after making changes to any configuration files. + +#### Monitor Configuration +The monitor can be tested in two modes: + +#### 1. Latest Block Mode + +This mode processes the most recent blocks across all configured networks. + +```bash +./openzeppelin-monitor --monitor-path="config/monitors/evm_transfer_usdc.json" +``` + +What this does: + +* Runs the "Large Transfer of USDC Token" monitor +* Targets all networks specified in the configuration +* Processes only the latest block for each network +* Sends a notification to all associated channels for every match that is found + +#### 2. Specific Block Mode + +This mode allows you to analyze a particular block on a specific network, which is useful for debugging specific transactions, verifying monitor behavior on known events, and testing monitor performance on historical data. + +```bash +./openzeppelin-monitor \ + --monitor-path="config/monitors/evm_transfer_usdc.json" \ + --network=ethereum_mainnet \ + --block=12345678 +``` + +What this does: + +* Runs the "Large Transfer of USDC Token" monitor +* Targets only the specified network (`ethereum_mainnet`) +* Processes only the specified block (`12345678`) +* Sends a notification to all associated channels for every match that is found + + + + +Specific Block Mode requires both parameters: + +* `--network`: The network to analyze +* `--block`: The block number to process + + + +#### Data Persistence (Optional) + +* Set `LOG_MODE` as file will persist the log data in `logs/` on host. To change it to a different directory use `LOG_DATA_DIR`. +* Set `MONITOR_DATA_DIR` to specific dir on your host system which will persist data between container restarts. + +## Error Handling + +The monitor implements a comprehensive error handling system with rich context and tracing capabilities. For detailed information about error handling, see [Error Handling Guide](/monitor/1.0.x/error). + +## Important Considerations + +### Performance Considerations + +* Monitor performance depends on network congestion and RPC endpoint reliability. + * View the [list of RPC calls](/monitor/1.0.x/rpc#list-of-rpc-calls) made by the monitor. +* The `max_past_blocks` configuration is critical: + * Calculate as: `(cron_interval_ms/block_time_ms) + confirmation_blocks + 1` (defaults to this calculation if not specified). + * Example for 1-minute Ethereum cron: `(60000/12000) + 12 + 1 = 18 blocks`. + * Too low settings may result in missed blocks. +* Trigger conditions are executed sequentially based on their position in the trigger conditions array. Proper execution also depends on the number of available file descriptors on your system. To ensure optimal performance, it is recommended to increase the limit for open file descriptors to at least 2048 or higher. On Unix-based systems you can check the current limit by running `ulimit -n` and _***temporarily***_ increase it with `ulimit -n 2048`. +* Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect. +* See performance considerations about custom scripts [here](/monitor/1.0.x/scripts#performance-considerations). + +### Notification Considerations + +* Template variables are context-dependent: + * Event-triggered notifications only populate event variables. + * Function-triggered notifications only populate function variables. + * Mixing contexts results in empty values. +* Custom script notifications have additional considerations: + * Scripts receive monitor match data and arguments as JSON input + * Scripts must complete within their configured timeout_ms or they will be terminated + * Script modifications require monitor restart to take effect + * Supported languages are limited to Python, JavaScript, and Bash + +## Support + +For support or inquiries, contact us on [Telegram](https://t.me/openzeppelin_tg/4). + +Have feature requests or want to contribute? Join our community on [GitHub](https://github.com/OpenZeppelin/openzeppelin-monitor/) + +## License +This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details. + +## Security +For security concerns, please refer to our [Security Policy](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/SECURITY.md). diff --git a/docs/content/monitor/1.0.x/project-structure.mdx b/docs/content/monitor/1.0.x/project-structure.mdx new file mode 100644 index 00000000..0f6e170f --- /dev/null +++ b/docs/content/monitor/1.0.x/project-structure.mdx @@ -0,0 +1,206 @@ +--- +title: Project Structure +--- + +This document describes the project structure and organization of the OpenZeppelin Monitor codebase, including the source code layout, configuration files, and development resources. + +## Project Layout + +The project follows a standard Rust project layout with additional directories for configuration, documentation, and operational resources: + +``` +openzeppelin-monitor/ +├── src/ # Source code +│ ├── bootstrap/ # Bootstrap functions for the application +│ ├── models/ # Data structures and types +│ ├── repositories/ # Configuration storage +│ ├── services/ # Core business logic +│ ├── utils/ # Helper functions +│ +├── config/ # Configuration files +├── tests/ # Integration and property-based tests +├── data/ # Runtime data storage +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── cmd/ # Metrics and monitoring +├── examples/ # Example configuration files +└── ... other root files (Cargo.toml, README.md, etc.) +``` + +## Source Code Organization + +### `src/` Directory + +The main source code directory contains the core implementation files organized into several modules: + +#### `bootstrap/` +Application initialization and setup for `main.rs`: + +* Handles service initialization and dependency injection +* Manages the startup sequence and service lifecycle + +#### `models/` +Core data structures and types: + +* `blockchain/`: Platform-specific implementations + * `evm/`: Ethereum Virtual Machine specific types + * `stellar/`: Stellar blockchain specific types +* `config/`: Configuration loading and validation +* `core/`: Core domain models +* `security/`: Security and secret management + +#### `repositories/` +Configuration storage: + +* Handles loading and validating configuration files +* Provides storage interfaces for monitors, networks, and triggers +* Implements validation of configuration references + +#### `services/` +Core business logic: + +* `blockchain/`: Blockchain client interfaces + * `transports/`: Transport clients + * `evm/`: Ethereum Virtual Machine transport client + * `stellar/`: Stellar transport client + * `clients/`: Client implementations + * `evm/`: Ethereum Virtual Machine client + * `stellar/`: Stellar client +* `blockwatcher/`: Block monitoring and processing +* `filter/`: Transaction and event filtering + * `filters/`: Filter implementations + * `evm/`: Ethereum Virtual Machine filter + * `stellar/`: Stellar filter +* `notification/`: Alert handling +* `trigger/`: Trigger evaluation and execution + * `script/`: Script execution utilities + +#### `utils/` +Helper functions: + +* `cron_utils`: Cron schedule utilities +* `expression`: Expression evaluation +* `logging/`: Logging utilities +* `macros/`: Macros for common functionality +* `metrics/`: Metrics utilities +* `monitor/`: Monitor configuration test utilities +* `tests/`: Contains test utilities and helper functions + * `builders/`: Test builder patterns implementing fluent interfaces for creating test fixtures + * `evm/`: Builder implementations specific to Ethereum Virtual Machine (EVM) testing + * `stellar/`: Builder implementations specific to Stellar blockchain testing + +## Configuration and Data + +### `config/` Directory + +Contains JSON configuration files for: + +* ***Network configurations*** (`networks/`) + * Connection details for blockchain networks + * RPC endpoints and network parameters +* ***Monitor configurations*** (`monitors/`) + * Monitoring rules and conditions + * Network and trigger references +* ***Trigger configurations*** (`triggers/`) + * Notification settings + * Script definitions +* ***Filter configurations*** (`filters/`) + * Match filter scripts + + + +The `examples/config/` directory contains example JSON configuration files for each (network, monitor, trigger and filters). + + +### `data/` Directory + +Runtime data storage: + +* Block processing state +* Operational data +* Temporary files + + + +The `data/`, `logs/` and `config/` directories are gitignored except for example files. These directories are mounted to persist the configs and runtime data. + + +## Examples and Resources + +### `examples/` Directory + +Provides practical examples and sample configurations to help users get started: + +* Demonstrates typical service configurations for various networks +* Acts as a quick-start guide for customizing the monitor +* Serves as a reference for best practices in configuration + +## Metrics and Monitoring + +### `cmd/prometheus/` Directory + +Prometheus exporters and monitoring infrastructure: + +* `dashboards/`: Grafana dashboards +* `datasources/`: Prometheus datasources +* `prometheus.yml`: Prometheus configuration +* `grafana.ini`: Grafana configuration + +## Testing and Documentation + +### `tests/` Directory + +Contains comprehensive test suites: + +* Integration tests +* Property-based tests +* Mock implementations +* Test utilities and helpers + +### `docs/` Directory + +Project documentation: + +* User guides +* API documentation +* Configuration examples +* Architecture diagrams + +### `scripts/` Directory + +Utility scripts for: + +* Development workflows +* Documentation generation +* Build processes +* Deployment helpers + +## Development Tools + +### Pre-commit Hooks + +Located in the project root: + +* Code formatting checks +* Linting rules +* Commit message validation + +### Build Configuration + +Core build files: + +* `Cargo.toml`: Project dependencies and metadata +* `rustfmt.toml`: Code formatting rules +* `rust-toolchain.toml`: Rust version and components + +## Docker Support + +The project includes Docker configurations for different environments: + +* `Dockerfile.development`: Development container setup +* `Dockerfile.production`: Production-ready container +* Before running the docker compose set your env variables in `.env` according to your needs + + + +For detailed information about running the monitor in containers, see the Docker deployment [section](/monitor/1.0.x#docker-installation) in the user documentation. diff --git a/docs/content/monitor/1.0.x/quickstart.mdx b/docs/content/monitor/1.0.x/quickstart.mdx new file mode 100644 index 00000000..d6bd9786 --- /dev/null +++ b/docs/content/monitor/1.0.x/quickstart.mdx @@ -0,0 +1,614 @@ +--- +title: Quick Start Guide +--- + +OpenZeppelin Monitor is a powerful tool for monitoring blockchain events and transactions. This guide will help you get up and running quickly with practical examples for both EVM and Stellar networks. + +## What You'll Learn + +* How to set up OpenZeppelin Monitor locally or with Docker +* How to configure monitoring for USDC transfers on Ethereum +* How to monitor DEX swaps on Stellar +* How to set up notifications via Slack and email + +## Prerequisites + +Before you begin, ensure you have the following installed: + +* ***Rust 2021 edition*** - Required for building from source +* ***Docker*** - Optional, for containerized deployment +* ***Git*** - For cloning the repository + + + +If you don’t have Rust installed, visit https://rustup.rs/ to install it. + + +## Quick Setup Options + +We provide two setup paths to get you started: + +### Option 1: Automated Setup (Recommended) + +For the fastest setup experience, use our automated script that handles everything for you. + +#### What the Automated Setup Does + +The `setup_and_run.sh` script provides a complete solution that: + +* ***Builds the monitor application*** from source +* ***Copies example configurations*** from `examples/` to `config/` + * Network configurations for major blockchains + * Pre-configured monitor examples (USDC transfers, Stellar DEX swaps) + * Required filter scripts and basic trigger notifications +* ***Validates all configurations*** to ensure proper setup +* ***Optionally runs the monitor*** to verify everything works + +#### Running the Automated Setup + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Make the script executable:*** + + ```bash + chmod +x setup_and_run.sh + ``` +3. ***Run the automated setup:*** + + ```bash + ./setup_and_run.sh + ``` + +The script provides colored output and clear guidance throughout the process. + +#### After Automated Setup + +Once complete, you’ll have: + +* A fully built OpenZeppelin Monitor +* Example configurations ready for customization +* Clear guidance on next steps + +***Next Steps:*** +. Customize the copied configurations in `config/` directories +. Update RPC URLs and notification credentials +. Run the monitor with `./openzeppelin-monitor` + + + +The setup script creates working configurations with placeholder values. ***Remember to update your files with actual RPC endpoints and notification credentials*** before starting real monitoring. + + +### Option 2: Manual Setup + +For users who prefer more control over the setup process. + +#### Building from Source + +1. ***Clone and build:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + cargo build --release + ``` +2. ***Move the binary to project root:*** + + ```bash + mv ./target/release/openzeppelin-monitor . + ``` + +#### Docker Setup + +For containerized deployment: + +1. ***Start services:*** + + ```bash + docker compose up + ``` + + + +By default, Docker Compose uses `Dockerfile.development`. For production, set: +`DOCKERFILE=Dockerfile.production` before running the command. + + +#### Docker Management Commands + +| Command | Description | +| --- | --- | +| `docker ps -a` | Verify container status | +| `docker compose down` | Stop services (without metrics) | +| `docker compose --profile metrics down` | Stop services (with metrics) | +| `docker compose logs -f` | View logs (follow mode) | + +## Environment Configuration + +### Logging Configuration + +Configure logging verbosity by setting the `RUST_LOG` environment variable: + +| Level | Description | +| --- | --- | +| `error` | Only error messages | +| `warn` | Warnings and errors | +| `info` | General information (recommended) | +| `debug` | Detailed debugging information | +| `trace` | Very detailed trace information | + +```bash +export RUST_LOG=info +``` + +### Local Configuration + +Copy the example environment file and customize it: + +```bash +cp .env.example .env +``` + +For detailed configuration options, see [Basic Configuration](/monitor/1.0.x#basic-configuration). + +## Practical Examples + +Now let’s set up real monitoring scenarios. Choose the example that matches your needs: + +### Example 1: Monitor USDC Transfers (Ethereum) + +This example monitors large USDC transfers on Ethereum mainnet and sends notifications when transfers exceed 10,000 USDC. + +#### Step 1: Network Configuration + +Create the Ethereum mainnet configuration: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json +``` + +***Key Configuration Details:*** + +```json + + "network_type": "EVM", + "slug": "ethereum_mainnet", + "name": "Ethereum Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "YOUR_RPC_URL_HERE" + , + "weight": 100 + } + ], + "chain_id": 1, + "block_time_ms": 12000, + "confirmation_blocks": 12, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 18, + "store_blocks": false +} +``` + + + +***Important:*** Replace `YOUR_RPC_URL_HERE` with your actual Ethereum RPC endpoint. You can use providers like Infura, Alchemy, or run your own node. + + +#### Step 2: Monitor Configuration + +Set up the USDC transfer monitor: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json +cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh +``` + +***Monitor Configuration Overview:*** + +```json + + "name": "Large Transfer of USDC Token", + "paused": false, + "networks": ["ethereum_mainnet"], + "addresses": [ + { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "contract_spec": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + , + + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + , + + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + + ], + "name": "Transfer", + "type": "event" + } + ] + } + ], + "match_conditions": + "functions": [], + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 10000000000" + + ], + "transactions": [ + + "status": "Success", + "expression": null + + ] + }, + "trigger_conditions": [ + + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + + ], + "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"] +} +``` + + + +* The `expression: "value > 10000000000"` monitors transfers over 10,000 USDC (USDC has 6 decimals) +* Remove the `trigger_conditions` array to disable additional filtering +* The USDC contract address `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` is the official USDC contract on Ethereum mainnet + + +#### Step 3: Notification Setup + +##### Slack Notifications + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +``` + +***Slack Configuration:*** + +```json + + "evm_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "SLACK_WEBHOOK_URL" + , + "message": + "title": "large_transfer_slack triggered", + "body": "Large transfer of ${events.0.args.value USDC from $events.0.args.from to $events.0.args.to | https://etherscan.io/tx/$transaction.hash#eventlog" + } + } + } +} +``` + + + +To get a Slack webhook URL: + +1. Go to https://api.slack.com/apps +2. Create a new app or select existing one +3. Enable "Incoming Webhooks" +4. Create a webhook for your channel + + +##### Email Notifications + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json +``` + +***Email Configuration:*** + +```json + + "evm_large_transfer_usdc_email": { + "name": "Large Transfer Email Notification", + "trigger_type": "email", + "config": { + "host": "smtp.gmail.com", + "port": 465, + "username": { + "type": "plain", + "value": "your_email@gmail.com" + , + "password": + "type": "plain", + "value": "SMTP_PASSWORD" + , + "message": + "title": "large_transfer_usdc_email triggered", + "body": "Large transfer of ${events.0.args.value USDC from $events.0.args.from to $events.0.args.to | https://etherscan.io/tx/$transaction.hash#eventlog" + }, + "sender": "your_email@gmail.com", + "recipients": [ + "recipient1@example.com", + "recipient2@example.com" + ] + } + } +} +``` + + + +For Gmail, you’ll need to use an "App Password" instead of your regular password. Enable 2FA and generate an app password in your Google Account settings. + + +#### Step 4: Run the Monitor + +***Local Deployment:*** + +```bash +./openzeppelin-monitor +``` + +***Docker Deployment:*** + +```bash +cargo make docker-compose-up +``` + +#### What Happens Next + +Once running, the monitor will: + +1. Check for new Ethereum blocks every minute +2. Watch for USDC transfers over 10,000 USDC +3. Send notifications via Slack and email when large transfers occur + +#### Customization Options + +* ***Adjust threshold:*** Modify `"value > 10000000000"` to change the minimum transfer amount +* ***Monitor other tokens:*** Create new monitor configurations for different ERC20 tokens +* ***Add more networks:*** Configure additional EVM networks (Polygon, BSC, etc.) + +### Example 2: Monitor DEX Swaps (Stellar) + +This example monitors large DEX swaps on Stellar mainnet. + +#### Step 1: Network Configuration + +Create the Stellar mainnet configuration: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json +``` + +***Key Configuration Details:*** + +```json + + "network_type": "Stellar", + "slug": "stellar_mainnet", + "name": "Stellar Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "YOUR_RPC_URL_HERE" + , + "weight": 100 + } + ], + "network_passphrase": "Public Global Stellar Network ; September 2015", + "block_time_ms": 5000, + "confirmation_blocks": 2, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 20, + "store_blocks": true +} +``` + +#### Step 2: Monitor Configuration + +Set up the DEX swap monitor: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json +cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh +``` + +***Monitor Configuration Overview:*** + +```json + + "name": "Large Swap By Dex", + "paused": false, + "networks": ["stellar_mainnet"], + "addresses": [ + { + "address": "CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE", + "contract_spec": [ + { + "function_v0": { + "doc": "", + "name": "swap", + "inputs": [ + { + "doc": "", + "name": "user", + "type_": "address" + , + + "doc": "", + "name": "in_idx", + "type_": "u32" + , + + "doc": "", + "name": "out_idx", + "type_": "u32" + , + + "doc": "", + "name": "in_amount", + "type_": "u128" + , + + "doc": "", + "name": "out_min", + "type_": "u128" + + ], + "outputs": ["u128"] + } + } + ] + } + ], + "match_conditions": + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + + ], + "events": [], + "transactions": [ + + "status": "Success", + "expression": null + + ] + }, + "trigger_conditions": [ + + "script_path": "./config/filters/stellar_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + + ], + "triggers": ["stellar_large_swap_by_dex_slack"] +} +``` + + + +* The `contract_spec` field is optional for Stellar contracts. If not provided, the monitor automatically fetches the contract’s SEP-48 interface from the chain +* You can explore Stellar contract interfaces using the [Stellar Contract Explorer](https://lab.stellar.org/smart-contracts/contract-explorer) +* The expression `"out_min > 1000000000"` monitors swaps with minimum output over 1 billion tokens + + +#### Step 3: Notification Setup + +Set up Slack notifications for Stellar swaps: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +``` + +***Slack Configuration:*** + +```json + + "stellar_large_swap_by_dex_slack": { + "name": "Large Swap By Dex Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "slack-webhook-url" + , + "message": + "title": "large_swap_by_dex_slack triggered", + "body": "${monitor.name triggered because of a large swap of $functions.0.args.out_min tokens | https://stellar.expert/explorer/public/tx/$transaction.hash" + } + } + } +} +``` + +#### Step 4: Run the Monitor + +***Local Deployment:*** + +```bash +./openzeppelin-monitor +``` + +***Docker Deployment:*** + +```bash +cargo make docker-compose-up +``` + +#### What Happens Next + +Once running, the monitor will: + +1. Check for new Stellar blocks every minute +2. Watch for large DEX swaps +3. Send notifications via Slack when large swaps occur + +## Next Steps + +Now that you have OpenZeppelin Monitor running, here are some suggestions for what to do next: + +### Testing and Validation + +* [Test your configuration](/monitor/1.0.x#testing-your-configuration) against specific block numbers +* Verify your RPC endpoints are working correctly +* Test notification channels with small transactions + +### Security and Best Practices + +* [Configure secure secret management](/monitor/1.0.x#secret-management) for sensitive data +* Use environment variables or Hashicorp Cloud Vault for credentials +* Regularly update your RPC endpoints and monitor configurations + +### Advanced Configuration + +* Explore additional examples in the [`examples/config/monitors` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/monitors) +* Set up monitoring for multiple networks simultaneously +* Configure custom filter scripts for complex conditions + +### Getting Help + +* Check the [GitHub Issues](https://github.com/OpenZeppelin/openzeppelin-monitor/issues) for known problems +* Review the [User Documentation](/monitor/1.0.x) for detailed configuration options +* Join the OpenZeppelin community for support + + + +Start with simple monitoring scenarios and gradually add complexity. This helps you understand how the system works and makes troubleshooting easier. diff --git a/docs/content/monitor/1.0.x/rpc.mdx b/docs/content/monitor/1.0.x/rpc.mdx new file mode 100644 index 00000000..3655be8e --- /dev/null +++ b/docs/content/monitor/1.0.x/rpc.mdx @@ -0,0 +1,303 @@ +--- +title: RPC Client +--- + +## Overview + +The OpenZeppelin Monitor includes a robust RPC client implementation with automatic endpoint rotation and fallback capabilities. This ensures reliable blockchain monitoring even when individual RPC endpoints experience issues. + +* Multiple RPC endpoint support with weighted load balancing +* Automatic fallback on endpoint failures +* Rate limit handling (429 responses) +* Connection health checks +* Thread-safe endpoint rotation + +## Configuration + +### RPC URLs + +RPC endpoints are configured in the network configuration files with weights for load balancing: +```json +{ + "rpc_urls": [ + { + "type_": "rpc", + "url": {"type": "plain", "value": "https://primary-endpoint.example.com"}, + "weight": 100 + }, + { + "type_": "rpc", + "url": {"type": "plain", "value": "https://backup-endpoint.example.com"}, + "weight": 50 + } + ] +} +``` + +For high-availability setups, configure at least 3 (private) RPC endpoints with appropriate weights to ensure continuous operation even if multiple endpoints fail. + + +### Configuration Fields + +| Field | Type | Description | +| --- | --- | --- | +| `type_` | `String` | Type of endpoint (currently only "rpc" is supported) | +| `url.type` | `String` | Secret type ("Plain", "Environment", or "HashicorpCloudVault") | +| `url.value` | `String` | The RPC endpoint URL | +| `weight` | `Number` | Load balancing weight (0-100) | + +## Endpoint Management + +The endpoint manager handles: + +* Initial endpoint selection based on weights +* Automatic rotation on failures +* Connection health checks +* Thread-safe endpoint updates + +Each blockchain network type has its own specialized transport client that wraps the base `HttpTransportClient`. +The transport clients are implemented as: + +1. **Core HTTP Transport**: `HttpTransportClient` provides core HTTP functionality, including the integrated retryable client. +2. **Network-Specific Transports**: + * `EVMTransportClient` for EVM networks + * `StellarTransportClient` for Stellar networks + +### Rotation Strategy + +The RPC client includes an automatic rotation strategy for handling specific types of failures: + +* For 429 (Too Many Requests) responses: + * Immediately rotates to a fallback URL + * Retries the request with the new endpoint + * Continues this process until successful or all endpoints are exhausted + +#### Configuration Options + +The error codes that trigger RPC endpoint rotation can be customized in the `src/services/blockchain/transports/mod.rs` file. + +```rust +pub const ROTATE_ON_ERROR_CODES: [u16; 1] = [429]; +``` + +### Retry Strategy + +The transport layer uses a combination of same-endpoint retries and endpoint rotation to handle transient failures and maintain service availability. + +#### Same-Endpoint Retry (via `reqwest-retry`) + +The `HttpTransportClient` (and by extension, EVM and Stellar clients) utilizes a `reqwest_middleware::ClientWithMiddleware`. This client is configured during initialization using the `utils::http::create_retryable_http_client` utility. This utility layers `reqwest_retry::RetryTransientMiddleware` on top of a shared base `reqwest::Client`. + +This middleware handles: + +* Automatic retries for transient HTTP errors (e.g., 5xx server errors, network timeouts) for requests made to the **currently active RPC URL**. +* An exponential backoff policy between these retry attempts. +* Parameters like the number of retries, backoff durations, and jitter are defined in an `RetryConfig` struct (see [Configuration Options](#configuration-options)). +* This same-endpoint retry mechanism is independent of, and operates before, the endpoint rotation logic. If all same-endpoint retries fail for the current URL, the error is then processed by the `EndpointManager`. + +#### Endpoint Rotation (via `EndpointManager`) + +If all same-endpoint retries fail for the currently active RPC URL, or if certain HTTP status codes (e.g., 429 Too Many Requests, as defined in `ROTATE_ON_ERROR_CODES`) are received, the `EndpointManager` (used by `HttpTransportClient`) will attempt to rotate to a healthy fallback URL. This ensures that if one endpoint becomes persistently unavailable, the system can switch to an alternative. The health check for a fallback URL also benefits from the same-endpoint retry mechanism. + +#### Configuration Options + +The same-endpoint retry behavior is configured via the `RetryConfig` struct, which is used by `create_retryable_http_client` to set up the `ExponentialBackoff` policy for `reqwest-retry`. + +The default settings for `RetryConfig` result in an `ExponentialBackoff` policy approximately equivalent to: +```rust +// This illustrates the default policy created by RetryConfig::default() +// and create_retryable_http_client. +let http_retry_config = RetryConfig::default(); +let retry_policy = ExponentialBackoff::builder() + .base(http_retry_config.base_for_backoff) + .retry_bounds(http_retry_config.initial_backoff, http_retry_config.max_backoff) + .jitter(http_retry_config.jitter) + .build_with_max_retries(http_retry_config.max_retries); +``` + +The configurable options are defined in the `RetryConfig` struct: +```rust +// In utils::http +pub struct RetryConfig { + /// Maximum number of retries for transient errors (after the initial attempt). + pub max_retries: u32, + /// Initial backoff duration before the first retry. + pub initial_backoff: Duration, + /// Maximum backoff duration for retries. + pub max_backoff: Duration, + /// Base for the exponential backoff calculation (e.g., 2). + pub base_for_backoff: u64, + /// Jitter to apply to the backoff duration. + pub jitter: Jitter, +} +``` + +The client architecture ensures efficient resource use and consistent retry behavior: + +1. A single base `reqwest::Client` is created by `HttpTransportClient` with optimized connection pool settings. This base client is shared. +2. The `create_retryable_http_client` utility takes this base client and an `RetryConfig` to produce a `ClientWithMiddleware`. +3. This `ClientWithMiddleware` (the "retryable client") is then used for all HTTP operations within `HttpTransportClient`, including initial health checks, requests sent via `EndpointManager`, and `try_connect` calls during rotation. This ensures all operations benefit from the configured retry policy and the shared connection pool. + +Each transport client may define its own retry policy: + +```rust +// src/services/transports/http.rs +pub struct HttpTransportClient { + pub client: ClientWithMiddleware, + endpoint_manager: EndpointManager, + test_connection_payload: Option, +} + +// Example of client creation with retry mechanism +// Use default retry policy +let http_retry_config = RetryConfig::default(); + +// Create the base HTTP client +let base_http_client = reqwest::ClientBuilder::new() + .pool_idle_timeout(Duration::from_secs(90)) + .pool_max_idle_per_host(32) + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(20)) + .build() + .context("Failed to create base HTTP client")?; + +// Create a retryable HTTP client with the base client and retry policy +let retryable_client = create_retryable_http_client( + &http_retry_config, + base_http_client, + Some(TransientErrorRetryStrategy), // Use custom or default retry strategy +); +``` + +### Implementation Details +The `EndpointManager` uses the retry-enabled `ClientWithMiddleware` provided by `HttpTransportClient` for its attempts on the primary URL. If these attempts (including internal `reqwest-retry` retries) ultimately fail with an error that warrants rotation (e.g., a 429 status code, or persistent network errors), then `EndpointManager` initiates the URL rotation sequence. + +```mermaid +sequenceDiagram + participant User as User/Application + participant HTC as HttpTransportClient + participant EM as EndpointManager + participant RetryClient as ClientWithMiddleware (reqwest-retry) + participant RPC_Primary as Primary RPC + participant RPC_Fallback as Fallback RPC + + User->>HTC: send_raw_request() + HTC->>EM: send_raw_request(self, ...) + EM->>RetryClient: POST to RPC_Primary + Note over RetryClient, RPC_Primary: RetryClient handles same-endpoint retries internally (e.g., for 5xx) + alt Retries on RPC_Primary succeed + RPC_Primary-->>RetryClient: Success + RetryClient-->>EM: Success + EM-->>HTC: Success + HTC-->>User: Response + else All retries on RPC_Primary fail (e.g. network error or 429) + RPC_Primary-->>RetryClient: Final Error (e.g. 429 or network error) + RetryClient-->>EM: Final Error from RPC_Primary + EM->>EM: Decide to Rotate (based on error type) + EM->>HTC: try_connect(Fallback_URL) (HTC uses its RetryClient for this) + HTC->>RetryClient: POST to RPC_Fallback (health check) + alt Fallback health check succeeds + RPC_Fallback-->>RetryClient: Success (health check) + RetryClient-->>HTC: Success (health check) + HTC-->>EM: Success (health check) + EM->>EM: Update active URL to RPC_Fallback + EM->>RetryClient: POST to RPC_Fallback (actual request) + RPC_Fallback-->>RetryClient: Success + RetryClient-->>EM: Success + EM-->>HTC: Success + HTC-->>User: Response + else Fallback health check fails + RPC_Fallback-->>RetryClient: Error (health check) + RetryClient-->>HTC: Error (health check) + HTC-->>EM: Error (health check) + EM-->>HTC: Final Error (all URLs failed) + HTC-->>User: Error Response + end + end +``` + +## List of RPC Calls + +Below is a list of RPC calls made by the monitor for each network type for each iteration of the cron schedule. +As the number of blocks being processed increases, the number of RPC calls grows, potentially leading to rate limiting issues or increased costs if not properly managed. + +```mermaid +graph TD + subgraph Common Operations + A[Main] --> D[Process New Blocks] + end + + subgraph EVM Network Calls + B[Network Init] -->|net_version| D + D -->|eth_blockNumber| E[For every block in range] + E -->|eth_getBlockByNumber| G1[Process Block] + G1 -->|eth_getLogs| H[Get Block Logs] + H -->|Only when needed| J[Get Transaction Receipt] + J -->|eth_getTransactionReceipt| I[Complete] + end + + subgraph Stellar Network Calls + C[Network Init] -->|getNetwork| D + D -->|getLatestLedger| F[In batches of 200 blocks] + F -->|getLedgers| G2[Process Block] + G2 -->|For each monitored contract without ABI| M[Fetch Contract Spec] + M -->|getLedgerEntries| N[Get WASM Hash] + N -->|getLedgerEntries| O[Get WASM Code] + O --> G2 + G2 -->|In batches of 200| P[Fetch Block Data] + P -->|getTransactions| L1[Get Transactions] + P -->|getEvents| L2[Get Events] + L1 --> Q[Complete] + L2 --> Q + end +``` + +**EVM** + +* RPC Client initialization (per active network): `net_version` +* Fetching the latest block number (per cron iteration): `eth_blockNumber` +* Fetching block data (per block): `eth_getBlockByNumber` +* Fetching block logs (per block): `eth_getLogs` +* Fetching transaction receipt (only when needed): + * When monitor condition requires receipt-specific fields (e.g., `gas_used`) + * When monitoring transaction status and no logs are present to validate status + +**Stellar** + +* RPC Client initialization (per active network): `getNetwork` +* Fetching the latest ledger (per cron iteration): `getLatestLedger` +* Fetching ledger data (batched up to 200 in a single request): `getLedgers` +* During block filtering, for each monitored contract without an ABI in config: + * Fetching contract instance data: `getLedgerEntries` + * Fetching contract WASM code: `getLedgerEntries` +* Fetching transactions (batched up to 200 in a single request): `getTransactions` +* Fetching events (batched up to 200 in a single request): `getEvents` + +## Best Practices + +* Configure multiple private endpoints with appropriate weights +* Use geographically distributed endpoints when possible +* Monitor endpoint health and adjust weights as needed +* Set appropriate retry policies based on network characteristics + +## Troubleshooting + +### Common Issues + +* **429 Too Many Requests**: Increase the number of fallback URLs, adjust weights or reduce monitoring frequency +* **Connection Timeouts**: Check endpoint health and network connectivity +* **Invalid Responses**: Verify endpoint compatibility with your network type + +### Logging + +Enable debug logging for detailed transport information: + +```bash +RUST_LOG=debug +``` + +This will show: + +* Endpoint rotations +* Connection attempts +* Request/response details diff --git a/docs/content/monitor/1.0.x/scripts.mdx b/docs/content/monitor/1.0.x/scripts.mdx new file mode 100644 index 00000000..f58eaf03 --- /dev/null +++ b/docs/content/monitor/1.0.x/scripts.mdx @@ -0,0 +1,616 @@ +--- +title: Custom Scripts +--- + +OpenZeppelin Monitor allows you to implement custom scripts for additional filtering of monitor matches and custom notification handling. + + + +***Security Risk:*** Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +## Custom Filter Scripts + +Custom filter scripts allow you to apply additional conditions to matches detected by the monitor. This helps you refine the alerts you receive based on criteria specific to your use case. + +### Implementation Guide + +1. Create a script in one of the supported languages: + * Bash + * Python + * JavaScript +2. Your script will receive a JSON object with the following structure: + * EVM + + ```json + { + "args": ["--verbose"], + "monitor_match": { + "EVM": { + "matched_on": { + "events": [], + "functions": [ + { + "expression": null, + "signature": "transfer(address,uint256)" + } + ], + "transactions": [ + { + "expression": null, + "status": "Success" + } + ] + }, + "matched_on_args": { + "events": null, + "functions": [ + { + "args": [ + { + "indexed": false, + "kind": "address", + "name": "to", + "value": "0x94d953b148d4d7143028f397de3a65a1800f97b3" + }, + { + "indexed": false, + "kind": "uint256", + "name": "value", + "value": "434924400" + } + ], + "hex_signature": "a9059cbb", + "signature": "transfer(address,uint256)" + } + ] + }, + "monitor": { + "addresses": [ + { + "contract_spec": null, + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + } + ], + "match_conditions": { + "events": [ + { + "expression": "value > 10000000000", + "signature": "Transfer(address,address,uint256)" + } + ], + "functions": [ + { + "expression": null, + "signature": "transfer(address,uint256)" + } + ], + "transactions": [ + { + "expression": null, + "status": "Success" + } + ] + }, + "name": "Large Transfer of USDC Token", + "networks": ["ethereum_mainnet"], + "paused": false, + "trigger_conditions": [ + { + "arguments": ["--verbose"], + "language": "Bash", + "script_path": "./config/filters/evm_filter_block_number.sh", + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_script"] + }, + "receipt": { + "blockHash": "0x...", + "blockNumber": "0x...", + "contractAddress": null, + "cumulativeGasUsed": "0x...", + "effectiveGasPrice": "0x...", + "from": "0x...", + "gasUsed": "0xb068", + "status": "0x1", + "to": "0x...", + "transactionHash": "0x...", + "transactionIndex": "0x1fc", + "type": "0x2" + }, + "logs": [ + { + "address": "0xd1f2586790a5bd6da1e443441df53af6ec213d83", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000060af8cf92e5aa9ead4a592d657cd6debecfbc616", + "0x000000000000000000000000d1f2586790a5bd6da1e443441df53af6ec213d83" + ], + "data": "0x00000000000000000000000000000000000000000000106015728793d21f77ac", + "blockNumber": "0x1451aca", + "transactionHash": "0xa39d1b9b3edda74414bd6ffaf6596f8ea12cf0012fd9a930f71ed69df6ff34d0", + "transactionIndex": "0x0", + "blockHash": "0x9432868b7fc57e85f0435ca3047f6a76add86f804b3c1af85647520061e30f80", + "logIndex": "0x2", + "removed": false + } + ], + "transaction": { + "accessList": [], + "blockHash": "0x...", + "blockNumber": "0x1506545", + "chainId": "0x1", + "from": "0x...", + "gas": "0x7a120", + "gasPrice": "0x...", + "hash": "0x...", + "maxFeePerGas": "0x...", + "maxPriorityFeePerGas": "0x...", + "nonce": "0x14779f", + "to": "0x...", + "transactionIndex": "0x...", + "type": "0x2", + "value": "0x0" + } + } + } + } + ``` + * Stellar + + ```json + { + "args": ["--verbose"], + "monitor_match": { + "Stellar": { + "monitor": { + "name": "Large Swap By Dex", + "networks": ["stellar_mainnet"], + "paused": false, + "addresses": [ + { + "address": "GCXYK...", + "contract_spec": null + } + ], + "match_conditions": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [] + }, + "trigger_conditions": [ + { + "arguments": ["--verbose"], + "language": "Bash", + "script_path": "./config/filters/stellar_filter_block_number.sh", + "timeout_ms": 1000 + } + ], + "triggers": ["stellar_large_transfer_usdc_script"] + }, + "transaction": { + "status": "SUCCESS", + "txHash": "2b5a0c...", + "applicationOrder": 3, + "feeBump": false, + "envelopeXdr": "AAAAAA...", + "envelopeJson": { + "type": "ENVELOPE_TYPE_TX", + "tx": {/* transaction details */} + }, + "resultXdr": "AAAAAA...", + "resultJson": /* result details */, + "resultMetaXdr": "AAAAAA...", + "resultMetaJson": /* metadata details */, + "diagnosticEventsXdr": ["AAAAAA..."], + "diagnosticEventsJson": [/* event details */], + "ledger": 123456, + "createdAt": 1679644800, + "decoded": { + "envelope": {/* decoded envelope */}, + "result": /* decoded result */, + "meta": /* decoded metadata */ + } + }, + "ledger": { + "hash": "abc1...", + "sequence": 123456, + "ledgerCloseTime": "2024-03-20T10:00:00Z", + "headerXdr": "AAAAAA...", + "headerJson": {/* header details */}, + "metadataXdr": "AAAAAA...", + "metadataJSON": /* metadata details */ + }, + "matched_on": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [] + }, + "matched_on_args": { + "functions": [], + "events": null + } + } + } + } + ``` + +### Script Output Requirements + +* Your script should print a boolean value indicating whether the match should be filtered. +* Print `true` if the match should be filtered out (not trigger an alert). +* Print `false` if the match should be processed (trigger an alert). +* Only the **last** printed line will be considered for evaluation. + +### Example Filter Script (Bash) + +```bash +#!/bin/bash + +main() { + # Read JSON input from stdin + input_json=$(cat) + + # Parse arguments from the input JSON and initialize verbose flag + verbose=false + args=$(echo "$input_json" | jq -r '.args[]? // empty') + if [ ! -z "$args" ]; then + while IFS= read -r arg; do + if [ "$arg" = "--verbose" ]; then + verbose=true + echo "Verbose mode enabled" + fi + done <<< "$args" + fi + + # Extract the monitor match data from the input + monitor_data=$(echo "$input_json" | jq -r '.monitor_match') + + if [ "$verbose" = true ]; then + echo "Input JSON received:" + fi + + # Extract blockNumber from the EVM receipt or transaction + block_number_hex=$(echo "$monitor_data" | jq -r '.EVM.transaction.blockNumber' || echo "") + + # Validate that block_number_hex is not empty + if [ -z "$block_number_hex" ]; then + echo "Invalid JSON or missing blockNumber" + echo "false" + exit 1 + fi + + # Remove 0x prefix if present and clean the string + block_number_hex=$(echo "$block_number_hex" | tr -d '\n' | tr -d ' ') + block_number_hex=${block_number_hex#0x} + + if [ "$verbose" = true ]; then + echo "Extracted block number (hex): $block_number_hex" + fi + + # Convert hex to decimal with error checking + if ! block_number=$(printf "%d" $((16#$block_number_hex)) 2>/dev/null); then + echo "Failed to convert hex to decimal" + echo "false" + exit 1 + fi + + if [ "$verbose" = true ]; then + echo "Converted block number (decimal): $block_number" + fi + + # Check if even or odd using modulo + is_even=$((block_number % 2)) + + if [ $is_even -eq 0 ]; then + echo "Block number $block_number is even" + echo "Verbose mode: $verbose" + echo "true" + exit 0 + else + echo "Block number $block_number is odd" + echo "Verbose mode: $verbose" + echo "false" + exit 0 + fi +} + +# Call main function +main +``` + +### Example Filter Script (JavaScript) + +```javascript +#!/usr/bin/env node + +try { + let inputData = ''; + // Read from stdin + process.stdin.on('data', chunk => { + inputData += chunk; + }); + + process.stdin.on('end', () => { + const data = JSON.parse(inputData); + const monitorMatch = data.monitor_match; + const args = data.args; + + // Extract block_number + let blockNumber = null; + if (monitorMatch.EVM) { + const hexBlock = monitorMatch.EVM.transaction?.blockNumber; + if (hexBlock) { + // Convert hex string to integer + blockNumber = parseInt(hexBlock, 16); + } + } + + if (blockNumber === null) { + console.log('false'); + return; + } + + const result = blockNumber % 2 === 0; + console.log(`Block number ${blockNumber} is ${result ? 'even' : 'odd'}`); + console.log(result.toString()); + }); +} catch (e) { + console.log(`Error processing input: ${e}`); + console.log('false'); +} + +``` + +### Example Filter Script (Python) + +```python +#!/usr/bin/env python3 + +import sys +import json + +def main(): + try: + # Read input from stdin + input_data = sys.stdin.read() + if not input_data: + print("No input JSON provided", flush=True) + return False + + # Parse input JSON + try: + data = json.loads(input_data) + monitor_match = data['monitor_match'] + args = data['args'] + except json.JSONDecodeError as e: + print(f"Invalid JSON input: {e}", flush=True) + return False + + # Extract block_number + block_number = None + if "EVM" in monitor_match: + hex_block = monitor_match['EVM']['transaction'].get('blockNumber') + if hex_block: + # Convert hex string to integer + block_number = int(hex_block, 16) + + if block_number is None: + print("Block number is None") + return False + + result = block_number % 2 == 0 + print(f"Block number {block_number} is {'even' if result else 'odd'}", flush=True) + return result + + except Exception as e: + print(f"Error processing input: {e}", flush=True) + return False + +if __name__ == "__main__": + result = main() + # Print the final boolean result + print(str(result).lower(), flush=True) + +``` + +This examples script filters EVM transactions based on their block number: + +* Returns `true` (filter out) for transactions in even-numbered blocks +* Returns `false` (allow) for transactions in odd-numbered blocks +* Accepts a `--verbose` flag for detailed logging +* Explore other examples in the [`examples/config/filters` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/filters). + +### Integration + +Integrate your custom filter script with the monitor by following the [configuration guidelines](/monitor/1.0.x#trigger-conditions-custom-filters). + + + +Trigger conditions are executed sequentially based on their position in the trigger conditions array. Every filter must return `false` for the match to be included and are only considered if they were executed successfully. + + +## Custom Notification Scripts + +Custom notification scripts allow you to define how alerts are delivered when specific conditions are met. This can include sending alerts to different channels or formatting notifications in a particular way. + +### Implementation Guide + +1. Create a script in one of the supported languages: + * Bash + * Python + * JavaScript +2. Your script will receive the same JSON input format as [filter scripts](#implementation-guide) + +### Script Output Requirements + +* A non-zero exit code indicates an error occurred +* Error messages should be written to `stderr` +* A zero exit code indicates successful execution + +### Example Notification Script (Bash) + +```bash +#!/bin/bash + +main() { + # Read JSON input from stdin + input_json=$(cat) + + # Parse arguments from the input JSON and initialize verbose flag + verbose=false + args=$(echo "$input_json" | jq -r '.args[]? // empty') + if [ ! -z "$args" ]; then + while IFS= read -r arg; do + if [ "$arg" = "--verbose" ]; then + verbose=true + echo "Verbose mode enabled" + fi + done <<< "$args" + fi + + # Extract the monitor match data from the input + monitor_data=$(echo "$input_json" | jq -r '.monitor_match') + + # Validate input + if [ -z "$input_json" ]; then + echo "No input JSON provided" + exit 1 + fi + + # Validate JSON structure + if ! echo "$input_json" | jq . >/dev/null 2>&1; then + echo "Invalid JSON input" + exit 1 + fi + + if [ "$verbose" = true ]; then + echo "Input JSON received:" + echo "$input_json" | jq '.' + echo "Monitor match data:" + echo "$monitor_data" | jq '.' + fi + + # Process args if they exist + args_data=$(echo "$input_json" | jq -r '.args') + if [ "$args_data" != "null" ]; then + echo "Args: $args_data" + fi + + # If we made it here, everything worked + echo "Verbose mode: $verbose" + # return a non zero exit code and an error message + echo "Error: This is a test error" >&2 + exit 1 +} + +# Call main function +main +``` + +### Example Notification Script (JavaScript) + +```javascript +#!/usr/bin/env node + +try { + let inputData = ''; + // Read from stdin + process.stdin.on('data', chunk => { + inputData += chunk; + }); + + process.stdin.on('end', () => { + // Parse input JSON + const data = JSON.parse(inputData); + const monitorMatch = data.monitor_match; + const args = data.args; + + // Log args if they exist + if (args && args.length > 0) { + console.log(`Args: ${JSON.stringify(args)}`); + } + + // Validate monitor match data + if (!monitorMatch) { + console.log("No monitor match data provided"); + return; + } + }); +} catch (e) { + console.log(`Error processing input: ${e}`); +} + +``` + +### Example Notification Script (Python) + +```python +#!/usr/bin/env python3 + +import sys +import json + +def main(): + try: + # Read input from stdin + input_data = sys.stdin.read() + if not input_data: + print("No input JSON provided", flush=True) + return + + # Parse input JSON + try: + data = json.loads(input_data) + monitor_match = data['monitor_match'] + args = data['args'] + if args: + print(f"Args: {args}") + except json.JSONDecodeError as e: + print(f"Invalid JSON input: {e}", flush=True) + return + + except Exception as e: + print(f"Error processing input: {e}", flush=True) + +if __name__ == "__main__": + main() + +``` + +This examples demonstrates how to: + +* Process the input JSON data +* Handle verbose mode for debugging +* Return error messages via `stderr` +* Set appropriate exit codes +* Explore other examples in the [`examples/config/triggers/scripts` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/triggers/scripts). + +### Integration + +Integrate your custom notification script with the triggers by following the [configuration guidelines](/monitor/1.0.x#custom-script-notifications). + +## Performance Considerations + +* **File descriptor limits**: Each script execution requires file descriptors for `stdin`, `stdout`, and `stderr` + * Ensure your system allows at least 2,048 open file descriptors + * Check your current limit on Unix-based systems with `ulimit -n` + * Temporarily increase the limit with `ulimit -n 2048` + * For permanent changes, modify `/etc/security/limits.conf` or equivalent for your system +* **Script timeout**: Configure appropriate timeout values in your trigger conditions to prevent long-running scripts from blocking the pipeline + * The `timeout_ms` parameter controls how long a script can run before being terminated +* **Resource usage**: Complex scripts may consume significant CPU or memory resources + * Consider optimizing resource-intensive operations in your scripts + * Monitor system performance during high-volume periods +* **Script reloading**: Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect diff --git a/docs/content/monitor/1.0.x/testing.mdx b/docs/content/monitor/1.0.x/testing.mdx new file mode 100644 index 00000000..7f9c5e27 --- /dev/null +++ b/docs/content/monitor/1.0.x/testing.mdx @@ -0,0 +1,125 @@ +--- +title: Testing Guide +--- + +This document provides information about testing OpenZeppelin Monitor, including running tests, generating coverage reports, and understanding the test structure. + +## Test Organization + +The project includes comprehensive test suites organized into different categories: + +### Test Types + +* ***Unit Tests***: Located within `src/` modules alongside the code they test +* ***Integration Tests***: Located in `tests/integration/` directory +* ***Property-based Tests***: Located in `tests/properties/` directory +* ***Mock Implementations***: Located in `tests/integration/mocks/` + +### Test Structure + +``` +tests/ +├── integration/ # Integration tests +│ ├── blockchain/ # Blockchain client tests +│ ├── blockwatcher/ # Block monitoring tests +│ ├── filters/ # Filter logic tests +│ ├── fixtures/ # Test data and configurations +│ ├── mocks/ # Mock implementations +│ └── ... +├── properties/ # Property-based tests +│ ├── filters/ # Filter property tests +│ ├── notifications/ # Notification property tests +│ └── ... +└── integration.rs # Integration test entry point +``` + +## Running Tests + +### All Tests + +Run the complete test suite: + +```bash +RUST_TEST_THREADS=1 cargo test +``` + + + +`RUST_TEST_THREADS=1` is required to prevent test conflicts when accessing shared resources like configuration files or network connections. + + +### Specific Test Categories + +***Property-based Tests:*** +```bash +RUST_TEST_THREADS=1 cargo test properties +``` + +***Integration Tests:*** +```bash +RUST_TEST_THREADS=1 cargo test integration +``` + +***Unit Tests Only:*** +```bash +RUST_TEST_THREADS=1 cargo test --lib +``` + +## Coverage Reports + +### Prerequisites + +Install the coverage tool: +```bash +rustup component add llvm-tools-preview +cargo install cargo-llvm-cov +``` + +### Generating Coverage + +***HTML Coverage Report:*** +```bash +RUST_TEST_THREADS=1 cargo +stable llvm-cov --html --open +``` + +This generates an HTML report in `target/llvm-cov/html/` and opens it in your browser. + +***Terminal Coverage Report:*** +```bash +RUST_TEST_THREADS=1 cargo +stable llvm-cov +``` + +## Troubleshooting + +### Common Issues + +***Tests hanging or timing out:*** +- Ensure `RUST_TEST_THREADS=1` is set +- Verify mock setups are correct + +***Coverage tool not found:*** +- Install with `cargo install cargo-llvm-cov` +- Add component with `rustup component add llvm-tools-preview` + +***Permission errors:*** +- Ensure test directories are writable +- Check file permissions on test fixtures + +### Debug Output + +Enable debug logging for tests: +```bash +RUST_LOG=debug RUST_TEST_THREADS=1 cargo test -- --nocapture +``` + +## Contributing Tests + +When contributing new features: + +1. ***Add comprehensive tests*** for new functionality +2. ***Ensure all tests pass*** locally before submitting +3. ***Include both unit and integration tests*** where appropriate +4. ***Update test documentation*** if adding new test patterns +5. ***Maintain or improve code coverage*** + +For more information about contributing, see the project’s contributing guidelines. diff --git a/docs/content/monitor/1.1.x/architecture.mdx b/docs/content/monitor/1.1.x/architecture.mdx new file mode 100644 index 00000000..0a4dc1f8 --- /dev/null +++ b/docs/content/monitor/1.1.x/architecture.mdx @@ -0,0 +1,386 @@ +--- +title: Architecture Guide +--- + +This document describes the high-level architecture of OpenZeppelin Monitor, including the core components, their interactions, and the overall system design. It provides a technical overview of how the service processes blockchain data and triggers notifications based on configurable conditions. + +## System Overview + +OpenZeppelin Monitor is organized as a data processing pipeline that spans from blockchain data collection to notification delivery. The system follows a modular architecture with distinct components for each step in the monitoring process, designed for scalability and extensibility. + +### High-Level Architecture + +The diagram below shows the core processing pipeline of OpenZeppelin Monitor, from blockchain networks and configuration through to notification channels: + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +flowchart LR + subgraph "Blockchain Networks" + EVMN["EVM Networks"] + STLN["Stellar Networks"] + end + subgraph "Configuration" + NETCFG["Network Configs"] + MONCFG["Monitor Configs"] + TRICFG["Trigger Configs"] + end + subgraph "Core Processing Pipeline" + BWS["BlockWatcherService"] + FS["FilterService"] + TS["TriggerService"] + NS["NotificationService"] + end + subgraph "Notification Channels" + Slack + Email + Discord + Telegram + Webhook + Scripts["Custom Scripts"] + end + + %% Data and config flow + EVMN --> BWS + STLN --> BWS + NETCFG --> BWS + MONCFG --> FS + TRICFG --> TS + BWS --> FS + FS --> TS + TS --> NS + NS --> Slack + NS --> Email + NS --> Discord + NS --> Telegram + NS --> Webhook + NS --> Scripts + + %% Styling for major blocks and notification channels + classDef blockchain fill:#fff3e0,stroke:#ef6c00,stroke-width:2px; + classDef config fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px; + classDef mainBlock fill:#e1f5fe,stroke:#01579b,stroke-width:2px; + classDef notification fill:#fce4ec,stroke:#c2185b,stroke-width:2px; + class EVMN,STLN blockchain; + class NETCFG,MONCFG,TRICFG config; + class BWS,FS,TS,NS mainBlock; + class Slack,Email,Discord,Telegram,Webhook,Scripts notification; +``` + +## Component Architecture + +The system consists of several core services that are initialized at startup and work together to process blockchain data and trigger notifications. The service initialization and dependencies are managed through the bootstrap module. + +### Service Initialization Flow + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +graph TD + subgraph Entry Point + MAIN[main.rs] + end + + subgraph Bootstrap + BOOTSTRAP[Bootstrap::initialize_service] + end + + subgraph Block Processing + BT[BlockTracker] + BS[BlockStorage] + BWS[BlockWatcherService] + BH[create_block_handler] + end + + subgraph Core Services + MS[MonitorService] + NS[NetworkService] + TS[TriggerService] + FS[FilterService] + TES[TriggerExecutionService] + NOTS[NotificationService] + end + + subgraph Client Layer + CP[ClientPool] + EVMC[EVMClient] + SC[StellarClient] + end + + %% Initialization Flow + MAIN --> BOOTSTRAP + BOOTSTRAP --> CP + BOOTSTRAP --> NS + BOOTSTRAP --> MS + BOOTSTRAP --> TS + BOOTSTRAP --> FS + BOOTSTRAP --> TES + BOOTSTRAP --> NOTS + + %% Block Processing Setup + BOOTSTRAP --> BT + BOOTSTRAP --> BS + BOOTSTRAP --> BWS + BOOTSTRAP --> BH + + %% Client Dependencies + CP --> EVMC + CP --> SC + BWS --> CP + + %% Service Dependencies + BWS --> BS + BWS --> BT + MS --> NS + MS --> TS + FS --> TES + TES --> NOTS + + %% Block Handler Connection + BH --> FS + BWS --> BH + + style MAIN fill:#e1f5fe,stroke:#01579b + style BOOTSTRAP fill:#fff3e0,stroke:#ef6c00 + classDef blockProcessing fill:#e8f5e9,stroke:#2e7d32 + classDef coreServices fill:#f3e5f5,stroke:#7b1fa2 + classDef clients fill:#fce4ec,stroke:#c2185b + + class BT,BS,BWS,BH blockProcessing + class MS,NS,TS,FS,TES,NOTS coreServices + class CP,EVMC,SC clients +``` + +### Core Components + +#### Block Processing Components + +* ***BlockWatcherService***: Orchestrates the block monitoring process by polling blockchain networks for new blocks and coordinating the processing pipeline. +* ***BlockTracker***: Tracks processed block numbers to prevent duplicate processing and ensure data consistency across service restarts. +* ***BlockStorage***: Persists block processing state for recovery and maintains the last processed block number for each network. + +#### Client Layer Components + +* ***ClientPool***: Manages blockchain client instances and provides network connectivity with connection pooling and failover capabilities. +* ***EVMClient***: Handles communication with Ethereum Virtual Machine compatible networks (Ethereum, Polygon, BSC, etc.). +* ***StellarClient***: Manages connections to Stellar blockchain networks with protocol-specific optimizations. + +#### Processing Pipeline Components + +* ***FilterService***: Applies monitor filters to blockchain data, evaluating conditions and match expressions to identify relevant transactions and events. +* ***TriggerExecutionService***: Executes triggers based on matched monitor conditions, evaluating trigger logic and preparing notification payloads. +* ***NotificationService***: Delivers notifications through configured channels (Slack, Email, Discord, Telegram, Webhooks, Scripts). + +#### Configuration Management Components + +* ***MonitorService***: Manages monitor configurations and provides access to active monitors with validation and lifecycle management. +* ***NetworkService***: Manages network configurations and provides network details for client connections and monitoring operations. +* ***TriggerService***: Manages trigger configurations and provides trigger details for notification execution. + +### Service Responsibilities + +The following table describes the key responsibilities of each service in the OpenZeppelin Monitor architecture: + +| Service | Responsibility | +| --- | --- | +| **MonitorService** | Manages monitor configurations and provides access to active monitors | +| **NetworkService** | Manages network configurations and provides network details | +| **TriggerService** | Manages trigger configurations and provides trigger details | +| **FilterService** | Filters blockchain data based on monitor conditions and match expressions | +| **TriggerExecutionService** | Executes triggers based on matched monitor conditions | +| **NotificationService** | Delivers notifications through configured channels | +| **BlockWatcherService** | Polls blockchain networks for new blocks and coordinates processing | +| **BlockTracker** | Tracks processed block numbers to prevent duplicate processing | +| **BlockStorage** | Persists block processing state for recovery | +| **ClientPool** | Manages blockchain client instances and provides network connectivity | + +### Block Processing Workflow + +The following _runtime flow_ illustrates how data moves through the system, from blockchain networks to notification channels. This sequence represents the core monitoring loop that executes for each configured network. + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +sequenceDiagram + participant BWS as BlockWatcherService + participant BS as BlockStorage + participant BC as BlockchainClient + participant FS as FilterService + participant TES as TriggerExecutionService + participant NS as NotificationService + + rect rgb(232, 245, 233) + Note over BWS: Orchestrates block processing + BWS->>BS: Get last processed block + end + + rect rgb(225, 245, 254) + Note over BC: Blockchain interface + BWS->>BC: Get latest block number + BWS->>BC: Get blocks (last+1 to latest) + end + + loop For each block + rect rgb(243, 229, 245) + Note over FS: Applies monitor filters + BWS->>FS: filter_block(block) + FS->>FS: Apply monitor filters + FS-->>BWS: Monitor matches + end + + rect rgb(255, 248, 225) + Note over TES: Evaluates trigger conditions + BWS->>TES: Process monitor matches + TES->>TES: Run trigger conditions + end + + rect rgb(252, 228, 236) + Note over NS: Delivers notifications + TES->>NS: Execute notifications + end + end + + rect rgb(255, 243, 224) + Note over BS: Persists processing state + BWS->>BS: Store latest processed block + end +``` + +### Data Flow Architecture + +#### 1. Block Discovery Phase +The `BlockWatcherService` initiates the monitoring cycle by: + +* Retrieving the last processed block number from `BlockStorage` +* Querying the blockchain for the latest block number +* Calculating the range of new blocks to process + +#### 2. Data Retrieval Phase +The `BlockchainClient` fetches block data: + +* Connects to the appropriate blockchain network via RPC +* Retrieves full block data including transactions and events +* Handles network-specific data formats and protocols + +#### 3. Filtering Phase +The `FilterService` processes each block: + +* Applies monitor-specific filters to transactions and events +* Evaluates match expressions and conditions +* Identifies relevant data that matches monitoring criteria + +#### 4. Trigger Evaluation Phase +The `TriggerExecutionService` processes matches: + +* Evaluates trigger conditions for matched data +* Prepares notification payloads with relevant context +* Determines which notification channels to activate + +#### 5. Notification Delivery Phase +The `NotificationService` delivers alerts: + +* Formats messages for each notification channel +* Handles channel-specific delivery protocols +* Manages delivery retries and error handling + +#### 6. State Persistence Phase +The `BlockStorage` updates processing state: + +* Records the latest processed block number +* Ensures data consistency for recovery scenarios +* Maintains processing history for debugging + +### Error Handling and Resilience + +The architecture includes several resilience mechanisms: + +* ***Connection Pooling***: The `ClientPool` manages multiple connections to prevent single points of failure +* ***State Recovery***: `BlockStorage` enables the service to resume from the last known good state after restarts +* ***Retry Logic***: Notification delivery includes configurable retry mechanisms for transient failures +* ***Graceful Degradation***: Individual component failures don’t cascade to the entire system + +For detailed information about RPC logic and network communication, see the [RPC section](/monitor/1.1.x/rpc). + +## Configuration Architecture + +The system uses a JSON-based configuration system organized into distinct categories: + +### Configuration Categories + +* ***Network Configurations***: Define blockchain network connections, RPC endpoints, and network parameters +* ***Monitor Configurations***: Specify monitoring rules, conditions, and network/trigger references +* ***Trigger Configurations***: Define notification settings and script definitions +* ***Filter Configurations***: Contain match filter scripts for data filtering + +### Configuration Validation + +The system implements comprehensive validation: + +* Cross-reference validation between monitors, networks, and triggers +* Schema validation for all configuration files +* Runtime validation of configuration references during service startup + + + +For configuration examples and best practices, see the [Configuration Guidelines](/monitor/1.1.x#configuration_guidelines) section in the user documentation. + + +## Extensibility Points + +The architecture is designed for extensibility in several key areas: + +### Blockchain Support +* ***Client Layer***: New blockchain protocols can be added by implementing the `BlockchainClient` trait +* ***Transport Layer***: Protocol-specific transport clients handle network communication details +* ***Filter Layer***: Chain-specific filters process protocol-dependent data formats + +### Notification Channels +* ***Channel Plugins***: New notification channels can be added by implementing the notification interface +* ***Script Support***: Custom notification logic can be implemented using Python, JavaScript, or Bash scripts + +### Monitoring Logic +* ***Expression Engine***: Flexible expression evaluation for complex monitoring conditions +* ***Script Triggers***: Custom trigger logic can be implemented using supported scripting languages + +## Performance Considerations + +The architecture is optimized for: + +* ***Concurrent Processing***: Multiple networks can be monitored simultaneously +* ***Efficient Block Processing***: Batch processing of blocks to minimize RPC calls +* ***Memory Management***: Streaming processing of large blocks to prevent memory issues +* ***Connection Reuse***: Client pooling reduces connection overhead + +## Security Architecture + +The system implements several security measures: + +* ***Secure Protocols***: Support for HTTPS/WSS +* ***Secret Management***: Secure handling of API keys and sensitive configuration data +* ***Input Validation***: Comprehensive validation of all external inputs and configurations + +## Related Documentation + +For detailed information about the project structure, source code organization, and development resources, see the [Project Structure](/monitor/1.1.x/project-structure) guide. + +For information about RPC logic and network communication, see the [RPC section](/monitor/1.1.x/rpc). + +For configuration examples and best practices, see the [Configuration Guidelines](/monitor/1.1.x#configuration_guidelines) section in the user documentation. diff --git a/docs/content/monitor/1.1.x/changelog.mdx b/docs/content/monitor/1.1.x/changelog.mdx new file mode 100644 index 00000000..276fa8ad --- /dev/null +++ b/docs/content/monitor/1.1.x/changelog.mdx @@ -0,0 +1,300 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v1.1.0) - 2025-10-22 + +## [1.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v1.0.0...v1.1.0) (2025-10-22) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Add events and functions summary in notifications ([#339](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/339)) ([000ae24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/000ae24e896cd0867c6252111a71151942d820bc)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Enhance filter expression parsing and evaluation ([#222](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/222)) ([3cb0849](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3cb084919b3d477f329a85fbafce1ce6d696b16d)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Handle Stellar JSON-RPC outside of retention window error for `getTransactions` and `getEvents` ([#270](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/270)) ([ae116ff](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ae116ff10f393a04c19d3b845df656027c6be4b9)) +* Implement client pooling for Webhook-based notifiers ([#281](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/281)) ([4f480c6](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4f480c6a05aeb949cfd8e227c5c08f19a5e60180)) +* Introduce `TransportError` ([#259](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/259)) ([0e04cfb](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0e04cfb57109251095ef8ee526fb5e05f5792792)) +* Introduce centralized retryable HTTP client creation ([#273](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/273)) ([5f6edaf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5f6edaf5deb77a5d9dfead52a162e923aad6a2ab)) +* Introduce retry mechanism for Email notifier ([#282](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/282)) ([b6301aa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b6301aaac963ae904d93e07674d9d01543ecfcd0)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) +* Update payload builder to support formatted titles ([#336](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/336)) ([12213b3](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/12213b32d609bf6a1ba69ce548f70809971f9fb3)) +* Upgrade stellar crates and read events from specs ([#371](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/371)) ([7273a3f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7273a3f8d9249692db6b6ca53f4d8b28b21670f4)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* Bump MSRV ([#291](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/291)) ([f2d7953](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f2d795310cd1417ad2fac854ea5f80cf6296b761)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* correct env var value in semgrep.yml ([#317](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/317)) ([7a8253f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7a8253fd23ae27c73b3971e2a688c39051c08a84)) +* Deprecate Stellar `paging_token` in `GetEvents` response ([#344](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/344)) ([68d20f9](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/68d20f91b643ef3a7c85ee897308d4f92d43698b)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Duplicate name in triggers config ([#274](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/274)) ([00f58f4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/00f58f4be3f9452792f9fdcf5dd8696947a274cb)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Generate SBOM step in release pipeline ([#294](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/294)) ([327269d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/327269d1ce2a16e9c8419e872ca02503c318c480)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* Multi arch. docker images and binary mismatch ([#382](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/382)) ([a61701e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a61701e11a13af03cdf86689b58e670b7d984a38)) +* Pagination logic in stellar getEvents relies only on cursor data ([#265](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/265)) ([fca4057](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fca4057ff5847e04981e5903eebe6ccf3931726c)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* Release binaries and enable nightly workflows to create binary artifacts and images ([#313](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/313)) ([43a0091](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/43a0091ed7b57a4ca33ca25a73423a73929802f7)) +* Remove deprecated reviewers field from dependabot.yml ([#316](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/316)) ([152843d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/152843df396b089e1c6054221206097339502f1b)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* Replace automatic minor version bumps ([#285](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/285)) ([0c9e14a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0c9e14a542cae2d2c7ff580ff7de28b0d9aab22a)) +* Risk of key collision for monitor custom scripts ([#258](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/258)) ([2aa4cd7](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2aa4cd730dbcbbd1cf0892394cedc4ea06332375)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) +* Semgrep CI integration ([#315](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/315)) ([a2bc23b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a2bc23baa27630ba914fca12ac40b191cbbad525)) +* Stellar ledgers are deterministic ([#257](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/257)) ([56a9f9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/56a9f9e10e533ea96c01cb1f0f67024600ad89df)) +* syntax error in codeql.yml ([#322](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/322)) ([7068e9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7068e9ee3845a007ed9d6c80157cbe86555ad14e)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Update the Semgrep config ([#306](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/306)) ([d4ed740](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d4ed7405e790098a0b1a0df3701feccb1908c56c)) +* Use unicode character for emoji ([#295](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/295)) ([bdccda5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bdccda5f2ca72612a4455a293c30647618476f95)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v1.0.0) - 2025-06-30 + +## [1.0.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.2.0...v1.0.0) (2025-06-30) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Enhance filter expression parsing and evaluation ([#222](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/222)) ([3cb0849](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3cb084919b3d477f329a85fbafce1ce6d696b16d)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Handle Stellar JSON-RPC outside of retention window error for `getTransactions` and `getEvents` ([#270](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/270)) ([ae116ff](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ae116ff10f393a04c19d3b845df656027c6be4b9)) +* Implement client pooling for Webhook-based notifiers ([#281](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/281)) ([4f480c6](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4f480c6a05aeb949cfd8e227c5c08f19a5e60180)) +* Introduce `TransportError` ([#259](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/259)) ([0e04cfb](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0e04cfb57109251095ef8ee526fb5e05f5792792)) +* Introduce centralized retryable HTTP client creation ([#273](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/273)) ([5f6edaf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5f6edaf5deb77a5d9dfead52a162e923aad6a2ab)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Duplicate name in triggers config ([#274](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/274)) ([00f58f4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/00f58f4be3f9452792f9fdcf5dd8696947a274cb)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* Pagination logic in stellar getEvents relies only on cursor data ([#265](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/265)) ([fca4057](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fca4057ff5847e04981e5903eebe6ccf3931726c)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* Replace automatic minor version bumps ([#285](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/285)) ([0c9e14a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0c9e14a542cae2d2c7ff580ff7de28b0d9aab22a)) +* Risk of key collision for monitor custom scripts ([#258](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/258)) ([2aa4cd7](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2aa4cd730dbcbbd1cf0892394cedc4ea06332375)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) +* Stellar ledgers are deterministic ([#257](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/257)) ([56a9f9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/56a9f9e10e533ea96c01cb1f0f67024600ad89df)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + +[Changes][v1.0.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v0.2.0) - 2025-05-14 + +## [0.2.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.1.0...v0.2.0) (2025-05-14) + + +## ⚠️ ⚠️ Breaking Changes in v0.2.0 + +* Renamed abi to contract_spec in monitor configurations. +* Stellar function expressions now use named parameters instead of positional indexes, for example; + + ``` + (Transfer(address,address,amount)): + 2 > 1000 → amount > 1000 + ``` +* Template variables now follow dot notation rather than underscores, for example: + * monitor_name → monitor.name + * transaction_hash → transaction.hash + * function_0_amount → functions.0.args.amount + * event_0_signature → events.0.signature +* Sensitive configuration values (e.g., URLs, usernames, passwords, tokens) must now be defined using the SecretValue object structure, for example: + + * RPC URLs: + + ``` + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "https://eth.drpc.org" + }, + "weight": 100 + } + ] + ``` + + * Webhook URLs: + + ``` + "discord_url": { + "type": "plain", + "value": "https://discord.com/api/webhooks/123-456-789" + } + ``` + + +### 🚀 Features + +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) + + +### 🐛 Bug Fixes + +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) + +[Changes][v0.2.0] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v0.1.0) - 2025-04-07 + +## 0.1.0 (2025-04-07) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Netlify integration & Release workflow doc ([#162](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/162)) ([3b77025](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3b7702569e7c5828ca55fb67f7eec2672bf768b2)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + + +### 📚 Documentation + +* Add Antora documentation ([#48](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/48)) ([2f737c4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2f737c4c040090bd3acd0af90d3f24045b8ff173)) +* add link to contributing in README ([#33](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/33)) ([5abb548](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5abb548c199f3a033860b027461e5fb3cd60e565)) +* Add list of RPC calls ([#67](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/67)) ([aae9577](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/aae9577f4e011eaca12adb7997bf5fd28a558f83)) +* Add quickstart guide ([#56](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/56)) ([e422353](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e422353873335540afce5a9a5702c786c71eea75)) +* add readme documentation ([#8](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/8)) ([357006d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/357006d98f6cc8d160920e702dc78662008d39a3)) +* add rust documentation ([#5](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/5)) ([3832570](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3832570adf4854279fcda215fbbba5eb0d5396a1)) +* Adding node to docker images - custom scripts ([#76](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/76)) ([da6516c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/da6516c6f3afccb297cb1c1251f673e02ceaeaa5)) +* Custom scripts documentation to antora and readme ([#91](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/91)) ([2b81058](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2b81058f810e6b4d18a2c79e96002fb77890e9e0)) +* Fix quickstart closing tag ([#118](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/118)) ([d360379](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d3603796f39c15ed5247efab90ab95c5537c76d2)) +* Fix telegram channel ([9899259](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/98992599ab8998113b6202781787a48ce0aab3db)) +* Implement README feedback ([#50](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/50)) ([5b6ba64](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5b6ba6419a06b9abd60412fa02b09da2a416e38c)) +* Improve docs ([#100](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/100)) ([9586a25](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/9586a253f2a76993bbf82d4834b37863edabab60)) +* improve readme section and examples ([#9](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/9)) ([009db37](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/009db3719e1be03120733755ade3c1c45e13f8a5)) +* Improvements to custom scripts ([#98](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/98)) ([69047d9](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/69047d90a2fe057446f7c1b3f3526ab31bc6afcb)) +* Re-order example and fix test flag ([#52](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/52)) ([f90b6df](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f90b6df73ef7a6040eab59d71402b34877c88fc5)) +* Readability improvements ([#109](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/109)) ([8e23389](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/8e23389ea0dcb3b221227a6cddd17de39603acbb)) +* Update project structure ([#101](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/101)) ([207edd2](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/207edd28f3fb0a805d40d6ba9109abe9e6553d23)) +* Update README and antora docs ([#57](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/57)) ([6a2299e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/6a2299e0c41052ef9523aec1aa6f5852990e9179)) +* Update RPC documentation after client pool feature ([#96](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/96)) ([ade2811](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ade2811431c07c6b46730cbce5e357934df14cd5)) +* Update telegram channel in docs ([#99](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/99)) ([9899259](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/98992599ab8998113b6202781787a48ce0aab3db)) +* Updated Quickstart guide ([#108](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/108)) ([b81c7cd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b81c7cd22143a7d2854ef496ab59e114d70c360f)) + +[Changes][v0.1.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.2.0...v1.0.0 +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.1.0...v0.2.0 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/tree/v0.1.0 diff --git a/docs/content/monitor/1.1.x/contribution.mdx b/docs/content/monitor/1.1.x/contribution.mdx new file mode 100644 index 00000000..429f5a9d --- /dev/null +++ b/docs/content/monitor/1.1.x/contribution.mdx @@ -0,0 +1,335 @@ +--- +title: Contribution Guidelines +--- + +Welcome to the OpenZeppelin Monitor project! We appreciate your interest in contributing. This guide outlines the requirements and processes for contributing to the project. + +## Getting Started + +The OpenZeppelin Monitor project has comprehensive contribution guidelines documented in the [`CONTRIBUTING.md`](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) file. This documentation provides a summary of key requirements, but for complete details including GitHub workflow, labeling guidelines, and advanced topics, please refer to the full CONTRIBUTING.md file. + + + +For the most up-to-date and comprehensive contribution guidelines, always refer to the [CONTRIBUTING.md](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) file in the repository. + + +## Key Requirements + +### Contributor License Agreement (CLA) + +***You must sign the CLA before contributing.*** The CLA process is automated through GitHub workflows that check and label PRs accordingly. + +* All contributors must complete the CLA process +* The CLA bot will automatically check your PR status +* PRs cannot be merged without a signed CLA + +### Signed Commits + +***All commits must be GPG-signed*** as a security requirement. + +* Configure GPG signing for your commits +* Unsigned commits will not be accepted +* This helps ensure code integrity and authenticity + +## Development Environment Setup + +### Prerequisites + +Before contributing, ensure you have: + +* ***Rust 2021 edition*** - Required for development +* ***Git*** - For version control +* ***Python/pip*** - For pre-commit hooks + +### System Dependencies (Linux) + +For Ubuntu 22.04+ or Debian-based systems (both x86 and ARM64 architectures), install required packages: + +***Note:*** Python 3.9+ is required for pre-commit hooks compatibility. + +```bash +# Install required packages directly +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + git \ + pkg-config \ + libssl-dev \ + libffi-dev \ + libyaml-dev \ + python3 \ + python3-venv \ + python3-pip +``` + +Or use the provided system package script (automatically ensures Python 3.9+ compatibility): + +```bash +chmod +x ./scripts/linux/sys_pkgs_core.sh +chmod +x ./scripts/linux/sys_pkgs_dev.sh +# Installs required packages and ensures compatible Python version +./scripts/linux/sys_pkgs_core.sh // For runtime dependencies only +./scripts/linux/sys_pkgs_dev.sh // For Python/dev dependencies (calls core script) +``` + +### Initial Setup + +```bash +# Clone and set up the repository +git clone https://github.com/openzeppelin/openzeppelin-monitor +cd openzeppelin-monitor + +# Build the project +cargo build + +# Set up environment variables +cp .env.example .env +``` + +### Running Tests + +```bash +# All tests +RUST_TEST_THREADS=1 cargo test + +# Integration tests +RUST_TEST_THREADS=1 cargo test integration + +# Property-based tests +RUST_TEST_THREADS=1 cargo test properties +``` + +## Development Workflow + +### 1. Pre-commit Hooks + +***Required for code quality checks*** including `rustfmt`, `clippy`, and commit message validation. + +* Install and configure pre-commit hooks +* Automatic formatting and linting checks +* Commit message format validation + +#### Installing Pre-commit Hooks + +Install and configure pre-commit hooks to ensure code quality: + +```bash +# Install pre-commit (use pipx for global installation if preferred) +pip install pre-commit + +# Install and configure hooks for commit-msg, pre-commit, and pre-push +pre-commit install --install-hooks -t commit-msg -t pre-commit -t pre-push +``` + + + +If you encounter issues with pip install, you may need to install [pipx](https://github.com/pypa/pipx) for global installation. Use `pipx install pre-commit` instead. + + +The pre-commit hooks will automatically run on every commit and push, checking for: +* Code formatting with `rustfmt` +* Linting with `clippy` +* Commit message format validation +* Other code quality checks + +### 2. GitHub Workflow + +#### Fork and Clone + +1. ***Fork the repository*** on GitHub +2. ***Clone your fork*** locally: + +```bash +# Set up your working directory +export working_dir="${HOME}/repos" +export user= + +# Clone your fork +mkdir -p $working_dir +cd $working_dir +git clone https://github.com/$user/openzeppelin-monitor.git + +# Add upstream remote +cd openzeppelin-monitor +git remote add upstream https://github.com/openzeppelin/openzeppelin-monitor.git +git remote set-url --push upstream no_push +``` + +#### Branch Management + +* Create feature branches from an up-to-date main branch +* Regularly sync with upstream +* Use descriptive branch names + +```bash +# Keep main updated +git fetch upstream +git checkout main +git rebase upstream/main + +# Create feature branch +git checkout -b feature/your-feature-name + +# Keep branch updated +git fetch upstream +git rebase upstream/main +``` + + + +Use `git rebase` instead of `git pull` to avoid merge commits and maintain a clean history. + + +### 3. Pull Request Process + +#### Creating a Pull Request + +1. ***Push your changes*** to your fork: + + ```bash + git push -f origin feature/your-feature-name + ``` +2. ***Create a Pull Request*** on GitHub +3. ***Add appropriate labels*** (see Labeling Guidelines below) +4. ***Include a clear description*** of your changes + +#### Best Practices for PRs + +* Write clear and meaningful commit messages +* Include `fixes #123` in PR body (not commit messages) to auto-close issues +* Break large changes into smaller, logical commits +* Ensure all tests pass +* Include sufficient information for reviewers + +## Code Standards + +### Rust Standards + +Rust API Guidelines: + +* Format code with `rustfmt` +* Pass all `clippy` linting checks +* Follow Rust naming conventions + +```bash +# Format code +cargo fmt + +# Check linting +cargo clippy --all-targets --all-features + +# Run tests +RUST_TEST_THREADS=1 cargo test +``` + +### Testing Requirements + +***All contributions must pass existing tests*** and include new tests when applicable: + +* Write unit tests for new functionality +* Add integration tests for complex features +* Ensure all tests pass before submitting +* Maintain or improve code coverage + +For detailed testing information, see the [Testing Guide](/monitor/1.1.x/testing). + +### Commit Message Format + +***Follow conventional commit format*** with types like: + +* `feat:` - New features +* `fix:` - Bug fixes +* `docs:` - Documentation changes +* `test:` - Test additions or modifications +* `refactor:` - Code refactoring +* `chore:` - Maintenance tasks + +## Issue and Pull Request Labeling + +The project uses a structured labeling system to organize issues and PRs. Key label categories include: + +### Area Labels (`A-`) +* `A-arch` - Architectural concerns +* `A-blocks` - Block processing +* `A-clients` - Blockchain clients +* `A-configs` - Configuration issues +* `A-docs` - Documentation +* `A-tests` - Testing + +### Type Labels (`T-`) +* `T-bug` - Bug reports +* `T-feature` - New features +* `T-task` - General tasks +* `T-documentation` - Documentation updates + +### Priority Labels (`P-`) +* `P-high` - Critical tasks +* `P-medium` - Important tasks +* `P-low` - Low priority + +### Difficulty Labels (`D-`) +* `D-easy` - Beginner-friendly +* `D-medium` - Intermediate +* `D-hard` - Complex issues + + + +For complete labeling guidelines and all available labels, see the [labeling section](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md#issue-and-pull-request-labeling-guidelines) in CONTRIBUTING.md. + + +## Code Review Process + +### Review Requirements + +* All PRs require review and approval +* At least one Reviewer and one Approver must approve +* Address all review comments before merging +* Commits are automatically squashed when merging + +### Review Guidelines + +Reviewers should focus on: + +1. ***Soundness*** - Is the idea behind the contribution sound? +2. ***Architecture*** - Is the contribution architected correctly? +3. ***Polish*** - Is the contribution polished and ready? + +### Getting Reviews + +If your PR isn’t getting attention: + +* Contact the team on [Telegram](https://t.me/openzeppelin_tg/4) +* Ensure your PR has appropriate labels +* Keep PRs focused and reasonably sized + +## Security + +* Follow the [Security Policy](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/SECURITY.md) +* Report security vulnerabilities through the proper channels +* Never commit sensitive information or credentials + +## Community Guidelines + +### Code of Conduct + +Contributors must follow the [Code of Conduct](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CODE_OF_CONDUCT.md), which: + +* Establishes standards for respectful collaboration +* Outlines enforcement procedures +* Promotes an inclusive environment + +## Getting Help + +### Community Support + +* ***GitHub Discussions***: For questions and community interaction +* ***Issues***: For bug reports and feature requests +* ***Telegram***: [Join our community chat](https://t.me/openzeppelin_tg/4) +* ***Good First Issues***: [Find beginner-friendly issues](https://github.com/openzeppelin/openzeppelin-monitor/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) + +### Additional Resources + +* ***Full CONTRIBUTING.md***: [Complete contribution guidelines](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) +* ***User Documentation***: [Monitor documentation](https://docs.openzeppelin.com/monitor) +* ***OpenZeppelin Website***: [Main website](https://openzeppelin.com/) diff --git a/docs/content/monitor/1.1.x/error.mdx b/docs/content/monitor/1.1.x/error.mdx new file mode 100644 index 00000000..0bc23a81 --- /dev/null +++ b/docs/content/monitor/1.1.x/error.mdx @@ -0,0 +1,167 @@ +--- +title: Error Handling +--- + +## Overview + +The OpenZeppelin Monitor uses a structured error handling system that provides rich context and tracing capabilities across service boundaries. Let’s start with a real-world example of how errors flow through our system. + +## Error Flow Example + +Let’s follow how an error propagates through our blockchain monitoring system: + +**Low-level Transport (`endpoint_manager.rs`)** + +```rust +// Creates basic errors with specific context +async fn send_raw_request(...) -> Result { + let response = client.post(...) + .await + .map_err(|e| anyhow::anyhow!("Failed to send request: {}", e))?; + + if !status.is_success() { + return Err(anyhow::anyhow!("HTTP error {}: {}", status, error_body)); + } +} +``` + +**Client Layer (`evm/client.rs`)** + +```rust +// Adds business context to low-level errors +async fn get_transaction_receipt(...) -> Result { + let response = self.alloy_client + .send_raw_request(...) + .await + .with_context(|| format!("Failed to get transaction receipt: {}", tx_hash))?; + + if receipt_data.is_null() { + return Err(anyhow::anyhow!("Transaction receipt not found")); + } +} +``` + +**Filter Layer (`evm/filter.rs`)** + +```rust +// Converts to domain-specific errors +async fn filter_block(...) -> Result, FilterError> { + let receipts = match futures::future::join_all(receipt_futures).await { + Ok(receipts) => receipts, + Err(e) => { + return Err(FilterError::network_error( + format!("Failed to get transaction receipts for block {}", block_num), + Some(e.into()), + None, + )); + } + }; +} +``` + +When this error occurs, it produces the following log: + +```text +ERROR filter_block: openzeppelin_monitor::utils::error: Error occurred, + error.message: Failed to get transaction receipts for block 15092829, + error.trace_id: a464d73c-5992-4cb5-a002-c8d705bfef8d, + error.timestamp: 2025-03-14T09:42:03.412341+00:00, + error.chain: Failed to get receipt for transaction 0x7722194b65953085fe1e9ec01003f1d7bdd6258a0ea5c91a59da80419513d95d + Caused by: HTTP error 429 Too Many Requests: {"code":-32007,"message":"[Exceeded request limit per second]"} + network: ethereum_mainnet +``` + +## Error Structure + +### Error Context +Every error in our system includes detailed context information: + +```rust +pub struct ErrorContext { + /// The error message + pub message: String, + /// The source error (if any) + pub source: Option>, + /// Unique trace ID for error tracking + pub trace_id: String, + /// Timestamp when the error occurred + pub timestamp: DateTime, + /// Optional key-value metadata + pub metadata: HashMap, +} +``` + +### Domain-Specific Error Types + +| Module | Error Type | Description | +| --- | --- | --- | +| `**Configuration**` | `ConfigError` | * `ValidationError` - Configuration validation failures * `ParseError` - Configuration parsing issues * `FileError` - File system related errors * `Other` - Unclassified errors | +| `**Security**` | `SecurityError` | * `ValidationError` - Security validation failures * `ParseError` - Security data parsing issues * `NetworkError` - Security service connectivity issues * `Other` - Unclassified security-related errors | +| `**Blockchain Service**` | `BlockChainError` | * `ConnectionError` - Network connectivity issues * `RequestError` - Malformed requests or invalid responses * `BlockNotFound` - Requested block not found * `TransactionError` - Transaction processing failures * `InternalError` - Internal client errors * `ClientPoolError` - Client pool related issues * `Other` - Unclassified errors | +| `**Block Watcher Service**` | `BlockWatcherError` | * `SchedulerError` - Block watching scheduling issues * `NetworkError` - Network connectivity problems * `ProcessingError` - Block processing failures * `StorageError` - Storage operation failures * `BlockTrackerError` - Block tracking issues * `Other` - Unclassified errors | +| `**Filter Service**` | `FilterError` | * `BlockTypeMismatch` - Block type validation failures * `NetworkError` - Network connectivity issues * `InternalError` - Internal processing errors * `Other` - Unclassified errors | +| `**Notification Service**` | `NotificationError` | * `NetworkError` - Network connectivity issues * `ConfigError` - Configuration problems * `InternalError` - Internal processing errors * `ExecutionError` - Script execution failures * `Other` - Unclassified errors | +| `**Repository**` | `RepositoryError` | * `ValidationError` - Data validation failures * `LoadError` - Data loading issues * `InternalError` - Internal processing errors * `Other` - Unclassified errors | +| `**Script Utils**` | `ScriptError` | * `NotFound` - Resource not found errors * `ExecutionError` - Script execution failures * `ParseError` - Script parsing issues * `SystemError` - System-level errors * `Other` - Unclassified errors | +| `**Trigger Service**` | `TriggerError` | * `NotFound` - Resource not found errors * `ExecutionError` - Trigger execution failures * `ConfigurationError` - Trigger configuration issues * `Other` - Unclassified errors | +| `**Monitor Executor**` | `MonitorExecutionError` | * `NotFound` - Resource not found errors * `ExecutionError` - Monitor execution failures * `Other` - Unclassified errors | + +## Error Handling Guidelines + +### When to Use Each Pattern + +| Scenario | Approach | +| --- | --- | +| Crossing Domain Boundaries | Convert to domain-specific error type using custom error constructors | +| Within Same Domain | Use `.with_context()` to add information while maintaining error type | +| External API Boundaries | Always convert to your domain’s error type to avoid leaking implementation details | + +### Error Creation Examples + +**Creating a Configuration Error without a source** + +```rust +let error = ConfigError::validation_error( + "Invalid network configuration", + None, + Some(HashMap::from([ + ("network", "ethereum"), + ("field", "rpc_url") + ])) +); +``` + +**Creating a Configuration Error with a source** + +```rust + +let io_error = std::io::Error::new(std::io::ErrorKind::Other, "Failed to read file"); + +let error = ConfigError::validation_error( + "Invalid network configuration", + Some(io_error.into()), + None +); +``` + +### Tracing with #[instrument] + +```rust +#[instrument(skip_all, fields(network = %_network.slug))] +async fn filter_block( + &self, + client: &T, + _network: &Network, + block: &BlockType, + monitors: &[Monitor], +) -> Result, FilterError> { + tracing::debug!("Processing block {}", block_number); + // ... +} +``` + +Key aspects: + +1. `skip_all` - Skips automatic instrumentation of function parameters for performance +2. `fields(...)` - Adds specific fields we want to track (like network slug) +3. `tracing::debug!` - Adds debug-level spans for important operations diff --git a/docs/content/monitor/1.1.x/index.mdx b/docs/content/monitor/1.1.x/index.mdx new file mode 100644 index 00000000..3709460d --- /dev/null +++ b/docs/content/monitor/1.1.x/index.mdx @@ -0,0 +1,1533 @@ +--- +title: OpenZeppelin Monitor +--- + +## Overview + +In the rapidly evolving world of blockchain technology, effective monitoring is crucial for ensuring security and performance. OpenZeppelin Monitor is a blockchain monitoring service that watches for specific on-chain activities and triggers notifications based on configurable conditions. The service offers multi-chain support with configurable monitoring schedules, flexible trigger conditions, and an extensible architecture for adding new chains. + +### Key Capabilities + +* ***Real-time Monitoring***: Watch blockchain networks in real-time for specific events and transactions +* ***Smart Filtering***: Use flexible expressions to define exactly what you want to monitor +* ***Multi-notification Support***: Send alerts via Slack, Discord, Email, Telegram, Webhooks, or custom scripts +* ***Configurable Scheduling***: Set custom monitoring schedules using cron expressions +* ***Data Persistence***: Store monitoring data and resume from checkpoints +* ***Extensible Architecture***: Easy to add support for new blockchains and notification types + +### Supported Networks + +* ***EVM-Compatible Networks*** +* ***Stellar*** + +### Notification Channels + +* ***Slack*** - Send formatted messages to Slack channels +* ***Discord*** - Post alerts to Discord channels via webhooks +* ***Email*** - Send email notifications with SMTP support +* ***Telegram*** - Send messages to Telegram chats via bot API +* ***Webhooks*** - Send HTTP requests to custom endpoints +* ***Custom Scripts*** - Execute Python, JavaScript, or Bash scripts + + + + +To get started immediately, see [Quickstart](/monitor/1.1.x/quickstart). + + + +## Installation + +### Prerequisites + +* Use ***Rust 2021 edition***, version `1.86` or later. +* ***Docker*** (optional, for containerized deployment) + +#### System Dependencies (Linux) + +For Ubuntu 22.04+ or Debian-based systems (both x86 and ARM64 architectures), install required packages: + +***Note:*** Python 3.9+ is required for pre-commit hooks compatibility. + +```bash +# Install required packages directly +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + git \ + pkg-config \ + libssl-dev \ + libffi-dev \ + libyaml-dev \ + python3 \ + python3-venv \ + python3-pip +``` + +Or use the provided system package script (automatically ensures Python 3.9+ compatibility): + +```bash +chmod +x ./scripts/linux/sys_pkgs_core.sh +chmod +x ./scripts/linux/sys_pkgs_dev.sh +# Installs required packages and ensures compatible Python version +./scripts/linux/sys_pkgs_core.sh // For runtime dependencies only +./scripts/linux/sys_pkgs_dev.sh // For Python/dev dependencies (calls core script) +``` + +### Local Installation + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Build the application:*** + + ```bash + cargo build --release + ``` +3. ***Move binary to project root:*** + + ```bash + mv ./target/release/openzeppelin-monitor . + ``` +4. ***Verify installation:*** + + ```bash + ./openzeppelin-monitor --help + ``` +5. ***View available options:*** + + ```bash + ./openzeppelin-monitor --help + + # Enable logging to file + ./openzeppelin-monitor --log-file + + # Enable metrics server + ./openzeppelin-monitor --metrics + + # Validate configuration files without starting the service + ./openzeppelin-monitor --check + ``` + +### Docker Installation + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Set up environment:*** + + ```bash + cp .env.example .env + # Edit .env file with your configuration + ``` +3. ***Start with Docker Compose:*** + + ```bash + cargo make docker-compose-up + ``` + +#### Metrics Configuration + +The metrics server, Prometheus, and Grafana can be enabled by setting `METRICS_ENABLED=true` in your `.env` file. + +You can start services directly with Docker Compose: + +```bash +# without metrics profile ( METRICS_ENABLED=false by default ) +docker compose up -d + +# With metrics enabled +docker compose --profile metrics up -d +``` + +To view prometheus metrics in a UI, you can use `http://localhost:9090` on your browser. + +To view grafana dashboard, you can use `http://localhost:3000` on your browser. + +By default, predefined metrics within a dashboard is populated in grafana. + +### Configuration Guidelines + +#### Recommended File Naming Conventions + +* Network configurations: `_.json` + * Example: `ethereum_mainnet.json`, `stellar_testnet.json` + * Should match the `slug` property inside the file +* Monitor configurations: `__monitor.json` + * Example: `usdc_transfer_monitor.json`, `dai_liquidation_monitor.json` + * Referenced by monitors using their `name` property +* Trigger configurations: `_.json` + * Example: `slack_notifications.json`, `email_alerts.json` + * Individual triggers referenced by their configuration key + +#### Configuration References + +* Monitor, network, and trigger names ***must be unique*** across all configurations files +* Monitor’s `networks` array must contain valid network `slug` values from network configuration files +* Monitor’s `triggers` array must contain valid trigger configuration keys +* Example valid references: + + ```json + // networks/ethereum_mainnet.json + { + "slug": "ethereum_mainnet", + ... + } + + // triggers/slack_notifications.json + { + "large_transfer_slack": { + ... + } + } + + // monitors/usdc_transfer_monitor.json + { + "networks": ["ethereum_mainnet"], + "triggers": ["large_transfer_slack"], + ... + } + + ``` + + + + +Ensure all referenced slugs and trigger keys exist in their respective configuration files. The monitor will fail to start if it cannot resolve these references. + + + +#### Safe Protocol Guidelines + +The monitor implements protocol security validations across different components and will issue warnings when potentially insecure configurations are detected. While insecure protocols are not blocked, we strongly recommend following these security guidelines: + +##### Network Protocols + +###### RPC URLs +* **HTTPS Recommended**: Using `https://` for RPC endpoints is strongly recommended +* **WSS Recommended**: For WebSocket connections, `wss://` (secure WebSocket) is strongly recommended +* **Warning**: Using `http://` or `ws://` will trigger security warnings as they transmit data unencrypted + +##### Notification Protocols + +###### Webhook Notifications +* **HTTPS Recommended**: URLs should use HTTPS protocol +* **Authentication Recommended**: Including either: + * `X-API-Key` header + * `Authorization` header +* **Optional Secret**: Can include a secret for HMAC authentication + * When a secret is provided, the monitor will: + * Generate a timestamp in milliseconds + * Create an HMAC-SHA256 signature of the payload and timestamp + * Add the signature in the `X-Signature` header + * Add the timestamp in the `X-Timestamp` header + * The signature is computed as: `HMAC-SHA256(secret, payload + timestamp)` +* **Warning**: Non-HTTPS URLs or missing authentication headers will trigger security warnings + +###### Slack Notifications +* **HTTPS Recommended**: Webhook URLs should start with `https://hooks.slack.com/` +* **Warning**: Non-HTTPS URLs will trigger security warnings + +###### Discord Notifications +* **HTTPS Recommended**: Webhook URLs should start with `https://discord.com/api/webhooks/` +* **Warning**: Non-HTTPS URLs will trigger security warnings + +###### Telegram Notifications +* ***Protocol:*** `POST` request with a `application/json` payload to the `sendMessage` method. +* ***Endpoint:*** `https://api.telegram.org/bot/sendMessage` +* ***Security:*** + * ***HTTPS Required:*** The API endpoint uses HTTPS. + * Authentication is handled via the ***Bot Token*** in the URL. Keep this token secure. +* ***Formatting:*** Messages are sent with `parse_mode` set to `MarkdownV2`. Special characters in the message title and body are automatically escaped to prevent formatting errors. + +###### Email Notifications +* **Secure Ports Recommended**: The following ports are considered secure: + * 465: SMTPS (SMTP over SSL) + * 587: SMTP with STARTTLS + * 993: IMAPS (IMAP over SSL) +* **Warning**: Using other ports will trigger security warnings +* **Valid Format**: Email addresses must follow RFC 5322 format + +###### Notifications Retry Policy + +Following notification protocols support retry policies: + +* Slack +* Discord +* Telegram +* Webhook +* Email + +Default retry policy is using exponential backoff with the following parameters: +| | | | +| --- | --- | --- | +| Parameter | Default Value | Description | +| `max_retries` | `3` | Maximum number of retries before giving up | +| `base_for_backoff` | `2` | Base duration for exponential backoff calculations in seconds | +| `initial_backoff` | `250` | Initial backoff duration in milliseconds | +| `max_backoff` | `10` | Maximum backoff duration in seconds | +| `jitter` | `Full` | Jitter strategy to apply to the backoff duration, currently supports `Full` and `None` | + +These parameters can be overridden by providing custom `RetryConfig` struct in `retry_policy` field in trigger configuration. + +##### Script Security + +###### File Permissions (Unix Systems) +* **Restricted Write Access**: Script files should not have overly permissive write permissions +* **Recommended Permissions**: Use `644` (`rw-r--r--`) for script files +* **Warning**: Files with mode `022` or more permissive will trigger security warnings + +**Example Setting Recommended Permissions** + +```bash +chmod 644 ./config/filters/my_script.sh +``` + +#### Secret Management + +The monitor implements a secure secret management system with support for multiple secret sources and automatic memory zeroization. + +##### Secret Sources + +The monitor supports three types of secret sources: + +* **Plain Text**: Direct secret values (wrapped in `SecretString` for secure memory handling) +* **Environment Variables**: Secrets stored in environment variables +* **Hashicorp Cloud Vault**: Secrets stored in Hashicorp Cloud Vault + +##### Security Features + +* **Automatic Zeroization**: Secrets are automatically zeroized from memory when no longer needed +* **Type-Safe Resolution**: Secure handling of secret resolution with proper error handling +* **Configuration Support**: Serde support for configuration files + +##### Configuration + +Secrets can be configured in the JSON files using the following format: + +```json +{ + "type": "Plain", + "value": "my-secret-value" +} +``` + +```json +{ + "type": "Environment", + "value": "MY_SECRET_ENV_VAR" +} +``` + +```json +{ + "type": "HashicorpCloudVault", + "value": "my-secret-name" +} +``` + +##### Hashicorp Cloud Vault Integration + +To use Hashicorp Cloud Vault, configure the following environment variables: + +| Environment Variable | Description | +| --- | --- | +| `HCP_CLIENT_ID` | Hashicorp Cloud Vault client ID | +| `HCP_CLIENT_SECRET` | Hashicorp Cloud Vault client secret | +| `HCP_ORG_ID` | Hashicorp Cloud Vault organization ID | +| `HCP_PROJECT_ID` | Hashicorp Cloud Vault project ID | +| `HCP_APP_NAME` | Hashicorp Cloud Vault application name | + +##### Best Practices + +* Use environment variables or vault for production secrets +* Avoid storing plain text secrets in configuration files +* Use appropriate access controls for vault secrets +* Monitor vault access patterns for suspicious activity + +#### Basic Configuration + +* Set up environment variables: + +Copy the example environment file and update values according to your needs + +```bash +cp .env.example .env +``` + +This table lists the environment variables and their default values. + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `RUST_LOG` | `info` | `info, debug, warn, error, trace` | Log level. | +| `LOG_MODE` | `stdout` | `stdout, file` | Write logs either to console or to file. | +| `LOG_DATA_DIR` | `logs/` | `` | Directory to write log files on host. | +| `MONITOR_DATA_DIR` | `null` | `` | Persist monitor data between container restarts. | +| `LOG_MAX_SIZE` | `1073741824` | `` | Size after which logs needs to be rolled. Accepts both raw bytes (e.g., "1073741824") or human-readable formats (e.g., "1GB", "500MB"). | +| `METRICS_ENABLED` | `false` | `true`, `false` | Enable metrics server for external tools to scrape metrics. | +| `METRICS_PORT` | `8081` | `` | Port to use for metrics server. | +| `HCP_CLIENT_ID` | - | `` | Hashicorp Cloud Vault client ID for secret management. | +| `HCP_CLIENT_SECRET` | - | `` | Hashicorp Cloud Vault client secret for secret management. | +| `HCP_ORG_ID` | - | `` | Hashicorp Cloud Vault organization ID for secret management. | +| `HCP_PROJECT_ID` | - | `` | Hashicorp Cloud Vault project ID for secret management. | +| `HCP_APP_NAME` | - | `` | Hashicorp Cloud Vault application name for secret management. | +* Copy and configure some example files: + +```bash +# EVM Configuration +cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json +cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json + +# Stellar Configuration +cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json +cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json + +# Notification Configuration +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json + +# Filter Configuration +cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh +cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh +``` +### Command Line Options + +The monitor supports several command-line options for configuration and control: + +| **Option** | **Default** | **Description** | +| --- | --- | --- | +| `**--log-file**` | `false` | Write logs to file instead of stdout | +| `**--log-level**` | `info` | Set log level (trace, debug, info, warn, error) | +| `**--log-path**` | `logs/` | Path to store log files | +| `**--log-max-size**` | `1GB` | Maximum log file size before rolling | +| `**--metrics-address**` | `127.0.0.1:8081` | Address to start the metrics server on | +| `**--metrics**` | `false` | Enable metrics server | +| `**--monitor-path**` | - | Path to the monitor to execute (for testing) | +| `**--network**` | - | Network to execute the monitor for (for testing) | +| `**--block**` | - | Block number to execute the monitor for (for testing) | +| `**--check**` | `false` | Validate configuration files without starting the service | + +## Data Storage Configuration + +The monitor uses file-based storage by default. + +### File Storage + +When `store_blocks` is enabled in the network configuration, the monitor stores: + +* Processed blocks: `./data/_blocks_.json` +* Missed blocks: `./data/_missed_blocks.txt` (used to store missed blocks) + +The content of the `missed_blocks.txt` file may help to determine the right `max_past_blocks` value based on the network’s block time and the monitor’s cron schedule. + +Additionally, the monitor will always store: + +* Last processed block: `./data/_last_block.txt` (enables resuming from last checkpoint) + +## Configuration Files + +### Network Configuration + +A Network configuration defines connection details and operational parameters for a specific blockchain network, supporting both EVM and Stellar-based chains. + +**Example Network Configuration** + +```json +{ + "network_type": "Stellar", + "slug": "stellar_mainnet", + "name": "Stellar Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "https://soroban.stellar.org" + }, + "weight": 100 + } + ], + "network_passphrase": "Public Global Stellar Network ; September 2015", + "block_time_ms": 5000, + "confirmation_blocks": 2, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 20, + "store_blocks": true +} +``` + +#### Available Fields + +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**network_type**` | `String` | Type of blockchain (**"EVM"** or **"Stellar"**) | +| `**slug**` | `String` | **Required** - **_Unique_** identifier for the network | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable network name | +| `**rpc_urls**` | `Array[Object]` | List of RPC endpoints with weights for load balancing | +| `**chain_id**` | `Number` | Network chain ID (**EVM only**) | +| `**network_passphrase**` | `String` | Network identifier (**Stellar only**) | +| `**block_time_ms**` | `Number` | Average block time in milliseconds | +| `**confirmation_blocks**` | `Number` | Number of blocks to wait for confirmation | +| `**cron_schedule**` | `String` | Monitor scheduling in cron format | +| `**max_past_blocks**` | `Number` | Maximum number of past blocks to process | +| `**store_blocks**` | `Boolean` | Whether to store processed blocks (defaults output to `./data/` directory) | + +#### Important Considerations + +* We strongly recommend using private RPC providers for improved reliability. + +### Trigger Configuration + +A Trigger defines actions to take when monitored conditions are met. Triggers can send notifications, make HTTP requests, or execute scripts. + +**Example Trigger Configuration** + +```json +{ + "evm_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "https://hooks.slack.com/services/A/B/C" + }, + "message": { + "title": "${monitor.name} triggered", + "body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog" + } + } + }, + "stellar_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "environment", + "value": "SLACK_WEBHOOK_URL" + }, + "message": { + "title": "large_transfer_usdc_slack triggered", + "body": "${monitor.name} triggered because of a large transfer of ${functions.0.args.amount} USDC to ${functions.0.args.to} | https://stellar.expert/explorer/testnet/tx/${transaction.hash}" + } + } + } +} +``` + +#### Trigger Types + +##### Slack Notifications +```json +{ + "slack_url": { + "type": "HashicorpCloudVault", + "value": "slack-webhook-url" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Slack Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"slack"** for Slack notifications | +| `**config.slack_url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.slack_url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Title that appears in the Slack message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Email Notifications +```json +{ + "host": "smtp.gmail.com", + "port": 465, + "username": { + "type": "plain", + "value": "sender@example.com" + }, + "password": { + "type": "environment", + "value": "SMTP_PASSWORD" + }, + "message": { + "title": "Alert Subject", + "body": "Alert message for ${transaction.hash}", + }, + "sender": "sender@example.com", + "recipients": ["recipient@example.com"] +} +``` + +##### Email Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"email"** for email notifications | +| `**config.host**` | `String` | SMTP server hostname | +| `**config.port**` | `Number` | SMTP port (defaults to **465**) | +| `**config.username.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.username.value**` | `String` | Secret value (username, environment variable name, or vault secret name) | +| `**config.password.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.password.value**` | `String` | Secret value (password, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Email subject line | +| `**config.message.body**` | `String` | Email body template with variable substitution | +| `**config.sender**` | `String` | Sender email address | +| `**config.recipients**` | `Array[String]` | List of recipient email addresses | + +##### Webhook Notifications +```json +{ + "url": { + "type": "HashicorpCloudVault", + "value": "webhook-url" + }, + "method": "POST", + "secret": { + "type": "environment", + "value": "WEBHOOK_SECRET" + }, + "headers": { + "Content-Type": "application/json" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Webhook Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"webhook"** for webhook notifications | +| `**config.url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.method**` | `String` | HTTP method (POST, GET, etc.) defaults to POST | +| `**config.secret.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.secret.value**` | `String` | Secret value (HMAC secret, environment variable name, or vault secret name) | +| `**config.headers**` | `Object` | Headers to include in the webhook request | +| `**config.message.title**` | `String` | Title that appears in the webhook message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Discord Notifications +```json +{ + "discord_url": { + "type": "plain", + "value": "https://discord.com/api/webhooks/123-456-789" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Discord Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"discord"** for Discord notifications | +| `**config.discord_url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.discord_url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Title that appears in the Discord message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Telegram Notifications +```json +{ + "token": { + "type": "HashicorpCloudVault", + "value": "telegram-bot-token" + }, + "chat_id": "9876543210", + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Telegram Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"telegram"** for Telegram notifications | +| `**config.token.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.token.value**` | `String` | Secret value (bot token, environment variable name, or vault secret name) | +| `**config.chat_id**` | `String` | Telegram chat ID | +| `**config.disable_web_preview**` | `Boolean` | Whether to disable web preview in Telegram messages (defaults to false) | +| `**config.message.title**` | `String` | Title that appears in the Telegram message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Custom Script Notifications +```json +{ + "language": "Bash", + "script_path": "./config/triggers/scripts/custom_notification.sh", + "arguments": ["--verbose"], + "timeout_ms": 1000 +} +``` + +##### Script Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"script"** for Custom Script notifications | +| `**language**` | `String` | The language of the script | +| `**script_path**` | `String` | The path to the script | +| `**arguments**` | `Array[String]` | The arguments of the script (optional). | +| `**timeout_ms**` | `Number` | The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed. | + +For more information about custom scripts, see [Custom Scripts Section](/monitor/1.1.x/scripts). + + + +***Security Risk***: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +#### Available Template Variables + +The monitor uses a structured JSON format with nested objects for template variables. The data is flattened into dot notation for template use. + +##### Common Variables +| **Variable** | **Description** | +| --- | --- | +| `**monitor.name**` | Name of the triggered monitor | +| `**transaction.hash**` | Hash of the transaction | +| `**functions**` | All functions matched and their parameters | +| `**events**` | All events matched and their parameters | + +##### Network-Specific Variables + +###### EVM Variables +| **Variable** | **Description** | +| --- | --- | +| `**transaction.from**` | Sender address | +| `**transaction.to**` | Recipient address | +| `**transaction.value**` | Transaction value | +| `**events.[index].signature**` | Event signature | +| `**events.[index].args.[param]**` | Event parameters by name | +| `**functions.[index].signature**` | Function signature | +| `**functions.[index].args.[param]**` | Function parameters by name | + +###### Stellar Variables +| **Variable** | **Description** | +| --- | --- | +| `**events.[index].args.[position]**` | Event parameters by position | +| `**events.[index].args.[param]**` | Event parameters by name (only in case the contract supports event parameters name) | +| `**functions.[index].args.[param]**` | Function parameters by name | + + + + +Transaction-related variables (`transaction.from`, `transaction.to`, `transaction.value`) are not available for Stellar networks. + + + +#### Message Formatting + +Slack, Discord, Telegram, Email and Webhook support Markdown formatting in their message bodies. You can use Markdown syntax to enhance your notifications. + +##### Example Email Notification with Markdown +```json +{ + "email_notification": { + "name": "Formatted Alert", + "trigger_type": "email", + "config": { + "host": "smtp.example.com", + "port": 465, + "username": {"type": "plain", "value": "alerts@example.com"}, + "password": {"type": "plain", "value": "password"}, + "message": { + "title": "**High Value Transfer Alert**", + "body": "### Transaction Details\n\n* **Amount:** ${events.0.args.value} USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n> Transaction Hash: ${transaction.hash}\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})" + }, + "sender": "alerts@example.com", + "recipients": ["recipient@example.com"] + } + } +} +``` + +##### Example Slack Notification with Markdown +```json +{ + "slack_notification": { + "name": "Formatted Alert", + "trigger_type": "slack", + "config": { + "slack_url": {"type": "plain", "value": "https://hooks.slack.com/services/XXX/YYY/ZZZ"}, + "message": { + "title": "*🚨 High Value Transfer Alert*", + "body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n>Transaction Hash: `${transaction.hash}`\n\n" + } + } + } +} +``` + +##### Example Discord Notification with Markdown +```json +{ + "discord_notification": { + "name": "Formatted Alert", + "trigger_type": "discord", + "config": { + "discord_url": {"type": "plain", "value": "https://discord.com/api/webhooks/XXX/YYY"}, + "message": { + "title": "**🚨 High Value Transfer Alert**", + "body": "# Transaction Details\n\n* **Amount:** `${events.0.args.value}` USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n>>> Transaction Hash: `${transaction.hash}`\n\n**[View on Explorer](https://etherscan.io/tx/${transaction.hash})" + } + } + } +} +``` + +##### Example Telegram Notification with Markdown +```json +{ + "telegram_notification": { + "name": "Formatted Alert", + "trigger_type": "telegram", + "config": { + "token": {"type": "plain", "value": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, + "chat_id": "9876543210", + "message": { + "title": "*🚨 High Value Transfer Alert*", + "body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n`Transaction Hash: ${transaction.hash}`\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})" + } + } + } +} +``` + +#### Important Considerations + +* Email notification port defaults to 465 if not specified. +* Template variables are context-dependent: + * Event-triggered notifications only populate event variables. + * Function-triggered notifications only populate function variables. + * Mixing contexts results in empty values. +* Credentials in configuration files should be properly secured. +* Consider using environment variables for sensitive information. + +### Monitor Configuration + +A Monitor defines what blockchain activity to watch and what actions to take when conditions are met. Each monitor combines: + +* Network targets (which chains to monitor) +* Contract addresses to watch +* Conditions to match (functions, events, transactions) +* Trigger conditions (custom scripts that act as filters for each monitor match to determine whether a trigger should be activated). +* Triggers to execute when conditions are met + +**Example Monitor Configuration** + +```json +{ + "name": "Large USDC Transfers", + "networks": ["ethereum_mainnet"], + "paused": false, + "addresses": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "contract_spec": [ ... ] + } + ], + "match_conditions": { + "functions": [ + { + "signature": "transfer(address,uint256)", + "expression": "value > 1000000" + } + ], + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 1000000" + } + ], + "transactions": [ + { + "status": "Success", + "expression": "value > 1500000000000000000" + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "bash", + "arguments": "--verbose", + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"] +} +``` + +#### Available Fields + +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** identifier for this monitor | +| `**networks**` | `Array[String]` | List of network slugs this monitor should watch | +| `**paused**` | `Boolean` | Whether this monitor is currently paused | +| `**addresses**` | `Array[Object]` | Contract addresses to monitor with optional ABIs | +| `**match_conditions**` | `Object` | Collection of conditions that can trigger the monitor | +| `**trigger_conditions**` | `Array[Object]` | Collection of filters to apply to monitor matches before executing triggers | +| `**triggers**` | `Array[String]` | IDs of triggers to execute when conditions match | + +#### Match Conditions + +Monitors support three types of match conditions that can be combined: + +##### Function Conditions +Match specific function calls to monitored contracts: + +```json +{ + "functions": [ + { + "signature": "transfer(address,uint256)", + "expression": "value > 1000" + } + ] +} +``` + +##### Event Conditions +Match events emitted by monitored contracts: + +```json +{ + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 1000000" + } + ] +} +``` + +##### Transaction Conditions +Match transaction properties. The available fields and expression syntax depend on the network type (EVM/Stellar) + +```json +{ + "transactions": [ + { + "status": "Success", // Only match successful transactions + "expression": "value > 1500000000000000000" // Match transactions with value greater than 1.5 ETH + } + ] +} +``` + +#### Available Transaction Fields (EVM) +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**value**` | `uint256` | Transaction value in wei | +| `**from**` | `address` | Sender address (case-insensitive comparison) | +| `**to**` | `address` | Recipient address (case-insensitive comparison) | +| `**hash**` | `string` | Transaction hash | +| `**gas_price**` | `uint256` | Gas price in wei (legacy transactions) | +| `**max_fee_per_gas**` | `uint256` | EIP-1559 maximum fee per gas | +| `**max_priority_fee_per_gas**` | `uint256` | EIP-1559 priority fee | +| `**gas_limit**` | `uint256` | Gas limit for transaction | +| `**nonce**` | `uint256` | Sender nonce | +| `**input**` | `string` | Hex-encoded input data (e.g., **"0xa9059cbb..."**) | +| `**gas_used**` | `uint256` | Actual gas used (from receipt) | +| `**transaction_index**` | `uint64` | Position in block | + +#### Available Transaction Fields (Stellar) +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**hash**` | `string` | Transaction hash | +| `**ledger**` | `i64` | Ledger sequence number where the transaction was included | +| `**value**` | `i64` | Value associated with the **first** relevant operation (e.g., payment amount). Defaults to 0 if no relevant operation or value is found. | +| `**from**` | `address` | Source account address of the **first** relevant operation (e.g., payment sender). Case-insensitive comparison. | +| `**to**` | `address` | Destination account address of the **first** relevant operation (e.g., payment recipient or invoked contract). Case-insensitive comparison. | + +#### Matching Rules + +* If no conditions are specified, all transactions match +* For multiple condition types: + * Transaction conditions are checked first + * Then either function OR event conditions must match + * Both transaction AND (function OR event) must match if both specified + +### Expressions + +Expressions allow for condition checking of function arguments, event parameters, and transaction fields. + +**Supported Parameter/Field Types and Basic Operations:** + +| Type | Description | Example Operators | Notes | +| --- | --- | --- | --- | +| `**Numeric (uint/int variants)**` | Integer values (e.g., `42`, `-100`) or decimal values (e.g., `3.14`, `-0.5`). | `>`, `>=`, `<`, `<=`, `==`, `!=` | Numbers must have digits before and after a decimal point if one is present (e.g., `.5` or `5.` are not valid standalone numbers). | +| `**Address**` | Blockchain addresses. | `==`, `!=` | Comparisons (e.g., `from == '0xABC...'`) are typically case-insensitive regarding the hex characters of the address value itself. | +| `**String**` | Text values. Can be single-quoted (e.g., ’hello'`) or, on the right-hand side of a comparison, unquoted (e.g., `active`). | `==`, `!=`, `starts_with`, `ends_with`, `contains` | Quoted strings support `\'` to escape a single quote and `\\` to escape a backslash. All string comparison operations (e.g., `name == 'Alice'`, `description contains 'error'`) are performed case-insensitively during evaluation. See the dedicated "String Operations" section for more examples and details. | +| `**Boolean**` | True or false values. | `==`, `!=` | Represented as `true` or `false`. These keywords are parsed case-insensitively (e.g., `TRUE`, `False` are also valid in expressions). | +| `**Hex String Literal**` | A string literal starting with `0x` or `0X` followed by hexadecimal characters (0-9, a-f, A-F). | `==`, `!=`, `starts_with`, `ends_with`, `contains` | Treated as a string for comparison purposes (e.g., `input_data starts_with '0xa9059cbb'`). Comparison is case-sensitive for the hex characters after `0x`. | +| `**Array (EVM/Stellar)**` | Ordered list of items. For Stellar, often a JSON string in config (e.g., ’["a", "id":1]'`). For EVM, typically decoded from ABI parameters. | `contains`, `==`, `!=`, `[index]` | Detailed operations, including indexed access and behavior of `contains`, vary by network. See "Operations on Complex Types" below. | +| `**Object/Map (Stellar)**` | Key-value pairs, typically represented as a JSON string in config (e.g., ’"key": "value", "id": 123'`). | `.key_access`, `==`, `!=` | Supports dot notation for field access (e.g., `data.id`). See "Operations on Complex Types" for details. | +| `**Vec (Stellar)**` | Ordered list, where the parameter’s value can be a CSV string (e.g., `"foo,bar"`) or a JSON array string (e.g., ’["foo","bar"]'`). | `contains`, `==`, `!=` | Behavior of `contains` and `==` differs based on whether the value is CSV or a JSON array string. See "Operations on Complex Types" for details. | +| `**Tuple (EVM)**` | Ordered list represented as a JSON array string (e.g., ’["Alice", 0x1234..., 25, true, [12,34]]'`). | `contains`, `==`, `!=` | The `contains` operation performs a case-insensitive deep search through all tuple elements. `==` and `!=` perform comparisons against the entire tuple values. See "Operations on Complex Types" for details. | + +**Logical Operators:** + +* AND - All conditions must be true +* OR - At least one condition must be true +* () - Parentheses for grouping +* AND has higher precedence than OR (i.e., AND operations are evaluated before OR operations if not grouped by parentheses) + +**Variable Naming and Access (Left-hand side of conditions):** + +The left-hand side (LHS) of a condition specifies the data field or parameter whose value you want to evaluate. + +**Base Names:** + +* These are the direct names of parameters or fields, such as `amount`, `from`, `status`, or event parameter indices like `0`, `1` (common in Stellar events). +* Base names can consist of alphanumeric characters (a-z, A-Z, 0-9) and underscores (`_`). +* They can start with a letter, an underscore, or a digit. Starting with a digit is primarily relevant for numerically indexed parameters (e.g., Stellar event parameters). +* **Important:** Variable names are case-sensitive during evaluation. The name used in the expression must exactly match the casing of the field name in the source data (e.g., from an ABI or blockchain data structure). For example, if a field is named `TotalValue` in the data, an expression using `totalvalue` will not find it. +* Variable names cannot be keywords (e.g., `true`, `AND`, `OR`, `contains`). Keywords themselves are parsed case-insensitively. + +**Path Accessors (for complex types):** + +If a base parameter is a complex type like an object, map, or array, you can access its internal data using accessors: + +**Key Access:** Use dot notation (`.`) to access properties of an object or map. + +* Examples: `transaction.value`, `user.name`, `data.0` (if `0` is a valid key name as a string). +* Keys typically consist of alphanumeric characters and underscores. They usually start with a letter or underscore, but purely numeric keys (e.g., `.0`, `.123`) are also supported for map-like structures where keys might be strings representing numbers. +* Keys cannot contain hyphens (`-`). + +**Index Access:** Use bracket notation (`[]`) to access elements of an array by their zero-based integer index. + +* Examples: `my_array[0]`, `log_entries[3]`. +* The index must be a non-negative integer. + +**Combined Access:** You can combine key and index accessors to navigate nested structures. + +* Example: `event.data_array[0].property` (accesses the `property` field of the first object in `data_array`, which is part of `event`). +* Example: `map.numeric_key_as_string_0[1].name` (accesses the `name` property of the second element of an array stored under the key `0` in `map`). + +**String Operations:** + +Several operators are available for matching patterns and comparing string values. These are particularly useful for EVM transaction `input` data, Stellar parameters defined with `kind: "string"`, or any other field that contains text. + +* `string_param starts_with 'prefix'`:: + Checks if the string parameter’s value begins with the specified `prefix`. + Example: `transaction.input starts_with '0xa9059cbb'` (checks for ERC20 transfer function selector). +* `string_param ends_with 'suffix'`:: + Checks if the string parameter’s value ends with the specified `suffix`. + Example: `file_name ends_with '.txt'` +* `string_param contains 'substring'`:: + Checks if the string parameter’s value contains the specified `substring` anywhere within it. + Example: `message contains 'error'` +* `string_param == 'exact_string'`:: + Checks if the string parameter’s value is exactly equal to `exact_string`. +* `string_param != 'different_string'`:: + Checks if the string parameter’s value is not equal to `different_string`. + +**Important Notes on String Operations:** + +* **Operator Keywords:** The operator keywords themselves (`starts_with`, `ends_with`, `contains`, `AND`, `OR`, `true`, `false`, comparison symbols like `==`, `>`) are parsed case-insensitively. For example, `CONTAINS` is treated the same as `contains`, and `TRUE` is the same as `true`. +* **Case-Insensitive Evaluation for String Comparisons:** When comparing string data (e.g., from event parameters, transaction fields, or function arguments) with literal string values in your expression, all standard string operations perform a ***case-insensitive*** comparison during evaluation. + * Equality (`==`) and Inequality (`!=`) + * Pattern matching (`starts_with`, `ends_with`, `contains`) +* **Variable Name Case Sensitivity:** It is important to distinguish this from variable names (the left-hand side of your condition, e.g., `status`). Variable names **are** case-sensitive and must exactly match the field names in your source data (ABI, etc.). + +**Whitespace Handling:** +Flexible whitespace is generally allowed around operators, parentheses, and keywords for readability. However, whitespace within quoted string literals is significant and preserved. + +#### Operations on Complex Types + +Beyond simple primitive types, expressions can also interact with more complex data structures like arrays, objects, and vectors. + +##### EVM Specifics + +**Array Operations (`kind: "array"`)** + +When an EVM parameter is an array (often represented internally or configured with `kind: "array"` and its value being a JSON string representation if manually configured), the following operations are supported: + +* `array_param contains 'value'` checks if the string ’value'` exists within the array. +* `array_param == '["raw_json_array_string"]'` string comparison of the array’s entire JSON string representation against the provided string +* `array_param != '["raw_json_array_string"]'` the negation of the above +* `array_param[0]` indexed access + +**Tuple Operations (`kind: "tuple"`)** + +* `tuple_param contains 'value'` checks if the string ’value'` exists within the tuple. +* `tuple_param == (12, "hello", "testing", 34)` checks if the tuple is equal. +* `tuple_param != (12, "hello", "testing", 34)` checks if the tuple is not equal. + +Where `tuple_param` is the name of tuple param (we should have only one param for tuples). + +**Note on Solidity Structs:** When working with Solidity smart contracts, struct types are automatically converted to tuples during ABI encoding/decoding. For example, a Solidity struct like: + +```solidity +struct User { + uint256 id; + string name; + string email; + uint256 age; +} +``` + +Will be represented as a tuple `**(12, "user_name", "user_email", 34)**` where the values correspond to the struct fields in their declaration order. This conversion is handled transparently by the Solidity compiler and Web3 libraries, allowing you to use the tuple operations above to work with struct data returned from smart contract calls. + +##### Stellar Specifics + +**Object (`kind: "object"`) / Map (`kind: "Map"`) Operations** + +* `object_param.key == 'value'` checks if the object or map has a key named `key` with the value ’value'`. +* `object_param.nested_key.another_key > 100` checks if the nested key `another_key` within `nested_key` has a value greater than 100. +* `object_param == '"raw_json_object_string"'` checks if the object or map matches the provided JSON string representation. +* `object_param != '"raw_json_object_string"'` the negation of the above + +**Array (`kind: "array"`) Operations** + +* `array_param[index]` accesses the element at the specified `index` in the array. +* `array_param[0] == 'value'` checks if the first element in the array is equal to ’value'`. +* `array_param[1].property == 'value'` checks if the second element in the array has a property named `property` with the value ’value'`. +* `array_param contains 'value'` checks if the array contains the string ’value'`. +* `array_param == '["raw_json_array_string"]'` checks if the array matches the provided JSON string representation. +* `array_param != '["raw_json_array_string"]'` the negation of the above + +**Vector (`kind: "vec"`) Operations** +When a Stellar parameter has `kind: "vec"`, its value can be either a CSV string or a JSON array string. + +* `vec_param contains 'item'` checks if the vector contains the string ’item'`. This works for both CSV and JSON array strings. +* `vec_param == 'raw_string_value'` checks if the vector matches the provided raw string value. This works for both CSV and JSON array strings. +* `vec_param != 'raw_string_value'` the negation of the above + +**Event Parameter Access (Stellar)** + +Starting with Stellar Protocol 23, Soroban contracts can include event definitions in their contract specifications. This enables two ways to access event parameters: + +**Access by Name (Protocol 23+):** +When a contract specification includes event definitions, you can access event parameters by their defined names: + +* If an event has a parameter named `recipient` (kind: "Address"): + * `recipient == 'GBBD...ABCD'` +* If an event has a parameter named `amount` (kind: "U128"): + * `amount > 1000000` +* If an event has a parameter named `metadata` (kind: "Map") containing ’"id": 123, "name": "Test"'`: + * `metadata.id == 123` + * `metadata.name contains 'test'` (case-insensitive) +* If an event has a parameter named `items` (kind: "array") containing ’["alpha", "beta"]'`: + * `items contains 'beta'` (case-insensitive deep search) + +**Access by Index (Legacy):** +For contracts without event definitions in their specification, or when working with older contracts, event parameters can be accessed by their numeric index (e.g., `0`, `1`, `2`): + +* If event parameter `0` (kind: "Map") is ’"id": 123, "name": "Test"'`: + * `0.id == 123` + * `0.name contains 'est'` (case-insensitive) +* If event parameter `1` (kind: "array") is ’["alpha", "val": "beta"]'`: + * `1[0] == 'ALPHA'` (case-insensitive) + * `1[1].val == 'Beta'` (case-insensitive) + * `1 contains 'beta'` (case-insensitive deep search) + + + +Named access is recommended when available as it makes expressions more readable and maintainable. The monitor will automatically use named parameters if they’re available in the contract specification, otherwise it falls back to indexed access. + + +##### EVM Examples + +These examples assume common EVM event parameters or transaction fields. + +**Basic Comparisons** + +```json +// Numeric +"transaction.value > 1000000000000000000" // Value greater than 1 ETH +"event.amount <= 500" +"block.number == 12345678" + +// String (case-insensitive evaluation for '==' and 'contains') +"transaction.to == '0xdeadbeef...'" // Address check (address value comparison itself is case-insensitive) +"event.token_name == 'mytoken'" +"transaction.input contains 'a9059cbb'" // Checks for ERC20 transfer selector + +// Boolean +"receipt.status == true" // or simply "receipt.status" if boolean field can be evaluated directly +"event.isFinalized == false" +``` + +**Logical Operators** + +```json +"transaction.value > 1000 AND event.type == 'Deposit'" +"(receipt.status == true OR event.fallback_triggered == true) AND user.is_whitelisted == false" +``` + +**String Operations** + +```json +"transaction.input starts_with '0xa9059cbb'" // Case-insensitive for the operation +"event.message ends_with 'failed'" +"event.details contains 'critical alert'" +``` + +**Array Operations** + +Assume `event.ids` is `[10, 20, 30]` and `event.participants` is `["user": "Alice", "role": "admin", "user": "Bob", "role": "editor"]`. +```json +"event.ids[0] == 10" +"event.ids contains '20'" // Checks for string '20' (case-insensitive) + +"event.participants contains 'Alice'" // True (deep search, case-insensitive) +"event.participants contains 'editor'" // True (deep search, case-insensitive) +"event.participants == '[{\"user\": \"Alice\", \"role\": \"admin\"}, {\"user\": \"Bob\", \"role\": \"editor\"}]'" // Raw JSON match (case-sensitive for structure and keys) +``` + +##### Stellar Examples + +**Basic Comparisons** + +```json +// Numeric +"event.params.amount > 10000000" // Accessing 'amount' field in an object 'params' +"ledger.sequence >= 123456" + +// String (case-insensitive evaluation for '==' and 'contains') +"event.params.recipient == 'GBD22...'" // Address check +"event.type == 'payment_processed'" + +// Boolean +"transaction.successful == true" +"event.data.is_verified == false" +``` + +**Logical Operators** + +```json +"event.data.value > 500 AND event.source_account == 'GCA7Z...'" +"(event.type == 'TRANSFER' OR event.type == 'PAYMENT') AND event.params.asset_code == 'XLM'" +``` + +**String Operations** + +```json +"event.contract_id starts_with 'CA23...'" +"event.memo ends_with '_TEST'" +"event.params.description contains 'urgent'" +``` + +**Object (`kind: "object"`) / Map (`kind: "Map"`) Operations** + +Assume `event.details` (kind: "Map") is ’"id": 123, "user": "name": "CHarlie", "status": "Active", "tags": ["new"]'`. +```json +"event.details.id == 123" +"event.details.user.name == 'charlie'" // Case-insensitive string comparison +"event.details.user.status contains 'act'" // Case-insensitive contains +"event.details.tags == '[\"new\"]'" // Raw JSON string match for the 'tags' field +``` + +**Array (`kind: "array"`) Operations** + +Assume `event.items` (kind: "array") is ’["sku": "A1", "qty": 10, "sku": "B2", "qty": 5, "notes":"Rush order"]'`. +```json +"event.items[0].sku == 'a1'" +"event.items[1].qty < 10" +"event.items contains 'A1'" // Deep search (case-insensitive) +"event.items contains 'rush order'" // Deep search (case-insensitive) +``` + +**Vector (`kind: "vec"`) Operations** + +Assume `csv_data` (kind: "vec") is `"ALPHA,Bravo,Charlie"` and `json_array_data` (kind: "vec") is ’["Delta", "id": "ECHO", "Foxtrot"]'`. +```json +"csv_data contains 'bravo'" // Case-insensitive CSV element match +"csv_data == 'ALPHA,Bravo,Charlie'" // Raw string match + +"json_array_data contains 'delta'" // Case-insensitive deep search (like array) +"json_array_data contains 'ECHO'" // Case-insensitive deep search (like array) +``` + +**Event Parameter Access (Numerically Indexed)** + +Assume event parameter `0` is `12345` (u64), `1` (kind: "array") is ’["Val1", "VAL2"]'`, and `2` (kind: "Map") is ’"keyA": "dataX", "keyB": 789'`. +```json +"0 > 10000" +"1[0] == 'val1'" +"1 contains 'val2'" +"2.keyA == 'DATAX'" +"2.keyB < 1000" +``` + +Now, after Stellar Protocol 23 we can access to Event parameters by name (first, make sure the contract supports the feature) + +``` +"0 > 10000" +"param_name[0] == 'val1'" +"param_name contains 'val2'" +"param_name.keyA == 'DATAX'" +"param_name.keyB < 1000" +``` + + + + +With SEP-48 support, Stellar functions can now reference parameters by name (e.g., `amount > 1000`) instead of position (e.g., `2 > 1000`). Events still use indexed parameters until SEP-48 support is added for events. + +You can find the contract specification through Stellar contract explorer tool. For example: +[Stellar DEX Contract Interface](https://lab.stellar.org/smart-contracts/contract-explorer?$=network$id=mainnet&label=Mainnet&horizonUrl=https:////horizon.stellar.org&rpcUrl=https:////mainnet.sorobanrpc.com&passphrase=Public%20Global%20Stellar%20Network%20/;%20September%202015;&smartContracts$explorer$contractId=CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE;;) + + + +#### Trigger Conditions (Custom filters) + +Custom filters allow you to create sophisticated filtering logic for processing monitor matches. These filters act as additional validation layers that determine whether a match should trigger the execution of a trigger or not. + +For more information about custom scripts, see [Custom Scripts Section](/monitor/1.1.x/scripts). + + + +***Security Risk***: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +**Example Trigger Conditions Configuration** + +```json +{ + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "Bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 +} +``` + +#### Available Fields + +##### Trigger Conditions Fields +| Field | Type | Description | +| --- | --- | --- | +| `**script_path**` | String | The path to the script | +| `**language**` | String | The language of the script | +| `**arguments**` | Array[String] | The arguments of the script (optional). | +| `**timeout_ms**` | Number | The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed and the match will be included by default. | + +#### Important Considerations + +* Network slugs in the monitor must match valid network configurations. +* Trigger IDs must match configured triggers. +* Expression syntax and available variables differ between EVM and Stellar networks. +* ABIs can be provided in two ways: + * For EVM networks: Through the monitor configuration using standard Ethereum ABI format + * For Stellar networks: Through the monitor configuration using SEP-48 format, or automatically fetched from the chain if not provided +* The monitoring frequency is controlled by the network’s `cron_schedule`. +* Each monitor can watch multiple networks and addresses simultaneously. +* Monitors can be paused without removing their configuration. + +## Running the Monitor + +### Local Execution + +1. ***Basic startup:*** + + ```bash + ./openzeppelin-monitor + ``` +2. ***With logging to file:*** + + ```bash + ./openzeppelin-monitor --log-file + ``` +3. ***With metrics enabled:*** + + ```bash + ./openzeppelin-monitor --metrics + ``` +4. ***Validate configuration without starting:*** + + ```bash + ./openzeppelin-monitor --check + ``` + +### Docker Execution + +1. ***Start all services:*** + + ```bash + cargo make docker-compose-up + ``` +2. ***With metrics and monitoring (Prometheus + Grafana):*** + + ```bash + # Set METRICS_ENABLED=true in .env file, then: + docker compose --profile metrics up -d + ``` +3. ***View logs:*** + + ```bash + docker compose logs -f monitor + ``` +4. ***Stop services:*** + + ```bash + cargo make docker-compose-down + ``` + +### Command Line Options + +| | | | +| --- | --- | --- | +| Option | Default | Description | +| `--log-file` | `false` | Write logs to file instead of stdout | +| `--log-level` | `info` | Set log level (trace, debug, info, warn, error) | +| `--metrics` | `false` | Enable metrics server on port 8081 | +| `--check` | `false` | Validate configuration files only | +| `--help` | - | Show all available options | + +### Testing your configuration + +#### Network Configuration +The `validate_network_config.sh` script helps ensure your network configuration is properly set up and operational. The script: + +* Tests the health of all configured RPC endpoints +* Validates connectivity using network-specific methods +* Provides clear visual feedback for each endpoint + +```bash +# Test default networks directory (/config/networks/) +./scripts/validate_network_config.sh + +# Test a specific configuration directory +./scripts/validate_network_config.sh -f /path/to/configs +``` + + +Run this script when setting up new networks, before deploying configuration changes, or when troubleshooting connectivity issues. + + +#### Validating Configuration Files + +Before starting the monitor service, you can validate your configuration files using the `--check` option: + +```bash +./openzeppelin-monitor --check +``` + +This command will: + +* Parse and validate all configuration files +* Check for syntax errors +* Verify references between monitors, networks, and triggers +* Report any issues without starting the service + +It’s recommended to run this check after making changes to any configuration files. + +#### Monitor Configuration +The monitor can be tested in two modes: + +#### 1. Latest Block Mode + +This mode processes the most recent blocks across all configured networks. + +```bash +./openzeppelin-monitor --monitor-path="config/monitors/evm_transfer_usdc.json" +``` + +What this does: + +* Runs the "Large Transfer of USDC Token" monitor +* Targets all networks specified in the configuration +* Processes only the latest block for each network +* Sends a notification to all associated channels for every match that is found + +#### 2. Specific Block Mode + +This mode allows you to analyze a particular block on a specific network, which is useful for debugging specific transactions, verifying monitor behavior on known events, and testing monitor performance on historical data. + +```bash +./openzeppelin-monitor \ + --monitor-path="config/monitors/evm_transfer_usdc.json" \ + --network=ethereum_mainnet \ + --block=12345678 +``` + +What this does: + +* Runs the "Large Transfer of USDC Token" monitor +* Targets only the specified network (`ethereum_mainnet`) +* Processes only the specified block (`12345678`) +* Sends a notification to all associated channels for every match that is found + + + + +Specific Block Mode requires both parameters: + +* `--network`: The network to analyze +* `--block`: The block number to process + + + +#### Data Persistence (Optional) + +* Set `LOG_MODE` as file will persist the log data in `logs/` on host. To change it to a different directory use `LOG_DATA_DIR`. +* Set `MONITOR_DATA_DIR` to specific dir on your host system which will persist data between container restarts. + +## Error Handling + +The monitor implements a comprehensive error handling system with rich context and tracing capabilities. For detailed information about error handling, see [Error Handling Guide](/monitor/1.1.x/error). + +## Important Considerations + +### Performance Considerations + +* Monitor performance depends on network congestion and RPC endpoint reliability. + * View the [list of RPC calls](/monitor/1.1.x/rpc#list_of_rpc_calls) made by the monitor. +* The `max_past_blocks` configuration is critical: + * Calculate as: `(cron_interval_ms/block_time_ms) + confirmation_blocks + 1` (defaults to this calculation if not specified). + * Example for 1-minute Ethereum cron: `(60000/12000) + 12 + 1 = 18 blocks`. + * Too low settings may result in missed blocks. +* Trigger conditions are executed sequentially based on their position in the trigger conditions array. Proper execution also depends on the number of available file descriptors on your system. To ensure optimal performance, it is recommended to increase the limit for open file descriptors to at least 2048 or higher. On Unix-based systems you can check the current limit by running `ulimit -n` and _***temporarily***_ increase it with `ulimit -n 2048`. +* Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect. +* See performance considerations about custom scripts [here](/monitor/1.1.x/scripts#performance_considerations). + +### Notification Considerations + +* Template variables are context-dependent: + * Event-triggered notifications only populate event variables. + * Function-triggered notifications only populate function variables. + * Mixing contexts results in empty values. +* Custom script notifications have additional considerations: + * Scripts receive monitor match data and arguments as JSON input + * Scripts must complete within their configured timeout_ms or they will be terminated + * Script modifications require monitor restart to take effect + * Supported languages are limited to Python, JavaScript, and Bash + +## Support + +For support or inquiries, contact us on [Telegram](https://t.me/openzeppelin_tg/4). + +Have feature requests or want to contribute? Join our community on [GitHub](https://github.com/OpenZeppelin/openzeppelin-monitor/) + +## License +This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details. + +## Security +For security concerns, please refer to our [Security Policy](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/SECURITY.md). diff --git a/docs/content/monitor/1.1.x/project-structure.mdx b/docs/content/monitor/1.1.x/project-structure.mdx new file mode 100644 index 00000000..6e4238f3 --- /dev/null +++ b/docs/content/monitor/1.1.x/project-structure.mdx @@ -0,0 +1,208 @@ +--- +title: Project Structure +--- + +This document describes the project structure and organization of the OpenZeppelin Monitor codebase, including the source code layout, configuration files, and development resources. + +## Project Layout + +The project follows a standard Rust project layout with additional directories for configuration, documentation, and operational resources: + +``` +openzeppelin-monitor/ +├── src/ # Source code +│ ├── bootstrap/ # Bootstrap functions for the application +│ ├── models/ # Data structures and types +│ ├── repositories/ # Configuration storage +│ ├── services/ # Core business logic +│ ├── utils/ # Helper functions +│ +├── config/ # Configuration files +├── tests/ # Integration and property-based tests +├── data/ # Runtime data storage +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── cmd/ # Metrics and monitoring +├── examples/ # Example configuration files +└── ... other root files (Cargo.toml, README.md, etc.) +``` + +## Source Code Organization + +### `src/` Directory + +The main source code directory contains the core implementation files organized into several modules: + +#### `bootstrap/` +Application initialization and setup for `main.rs`: + +* Handles service initialization and dependency injection +* Manages the startup sequence and service lifecycle + +#### `models/` +Core data structures and types: + +* `blockchain/`: Platform-specific implementations + * `evm/`: Ethereum Virtual Machine specific types + * `stellar/`: Stellar blockchain specific types +* `config/`: Configuration loading and validation +* `core/`: Core domain models +* `security/`: Security and secret management + +#### `repositories/` +Configuration storage: + +* Handles loading and validating configuration files +* Provides storage interfaces for monitors, networks, and triggers +* Implements validation of configuration references + +#### `services/` +Core business logic: + +* `blockchain/`: Blockchain client interfaces + * `transports/`: Transport clients + * `evm/`: Ethereum Virtual Machine transport client + * `stellar/`: Stellar transport client + * `clients/`: Client implementations + * `evm/`: Ethereum Virtual Machine client + * `stellar/`: Stellar client +* `blockwatcher/`: Block monitoring and processing +* `filter/`: Transaction and event filtering + * `filters/`: Filter implementations + * `evm/`: Ethereum Virtual Machine filter + * `stellar/`: Stellar filter +* `notification/`: Alert handling +* `trigger/`: Trigger evaluation and execution + * `script/`: Script execution utilities + +#### `utils/` +Helper functions: + +* `cron_utils`: Cron schedule utilities +* `expression`: Expression evaluation +* `logging/`: Logging utilities +* `macros/`: Macros for common functionality +* `metrics/`: Metrics utilities +* `monitor/`: Monitor configuration test utilities +* `tests/`: Contains test utilities and helper functions + * `builders/`: Test builder patterns implementing fluent interfaces for creating test fixtures + * `evm/`: Builder implementations specific to Ethereum Virtual Machine (EVM) testing + * `stellar/`: Builder implementations specific to Stellar blockchain testing + +## Configuration and Data + +### `config/` Directory + +Contains JSON configuration files for: + +* ***Network configurations*** (`networks/`) + * Connection details for blockchain networks + * RPC endpoints and network parameters +* ***Monitor configurations*** (`monitors/`) + * Monitoring rules and conditions + * Network and trigger references +* ***Trigger configurations*** (`triggers/`) + * Notification settings + * Script definitions +* ***Filter configurations*** (`filters/`) + * Match filter scripts + + + +The `examples/config/` directory contains example JSON configuration files for each (network, monitor, trigger and filters). + + +### `data/` Directory + +Runtime data storage: + +* Block processing state +* Operational data +* Temporary files + + + + +The `data/`, `logs/` and `config/` directories are gitignored except for example files. These directories are mounted to persist the configs and runtime data. + + + +## Examples and Resources + +### `examples/` Directory + +Provides practical examples and sample configurations to help users get started: + +* Demonstrates typical service configurations for various networks +* Acts as a quick-start guide for customizing the monitor +* Serves as a reference for best practices in configuration + +## Metrics and Monitoring + +### `cmd/prometheus/` Directory + +Prometheus exporters and monitoring infrastructure: + +* `dashboards/`: Grafana dashboards +* `datasources/`: Prometheus datasources +* `prometheus.yml`: Prometheus configuration +* `grafana.ini`: Grafana configuration + +## Testing and Documentation + +### `tests/` Directory + +Contains comprehensive test suites: + +* Integration tests +* Property-based tests +* Mock implementations +* Test utilities and helpers + +### `docs/` Directory + +Project documentation: + +* User guides +* API documentation +* Configuration examples +* Architecture diagrams + +### `scripts/` Directory + +Utility scripts for: + +* Development workflows +* Documentation generation +* Build processes +* Deployment helpers + +## Development Tools + +### Pre-commit Hooks + +Located in the project root: + +* Code formatting checks +* Linting rules +* Commit message validation + +### Build Configuration + +Core build files: + +* `Cargo.toml`: Project dependencies and metadata +* `rustfmt.toml`: Code formatting rules +* `rust-toolchain.toml`: Rust version and components + +## Docker Support + +The project includes Docker configurations for different environments: + +* `Dockerfile.development`: Development container setup +* `Dockerfile.production`: Production-ready container +* Before running the docker compose set your env variables in `.env` according to your needs + + + +For detailed information about running the monitor in containers, see the Docker deployment [section](/monitor/1.1.x#docker_installation) in the user documentation. diff --git a/docs/content/monitor/1.1.x/quickstart.mdx b/docs/content/monitor/1.1.x/quickstart.mdx new file mode 100644 index 00000000..9bf7b5b9 --- /dev/null +++ b/docs/content/monitor/1.1.x/quickstart.mdx @@ -0,0 +1,654 @@ +--- +title: Quick Start Guide +--- + +OpenZeppelin Monitor is a powerful tool for monitoring blockchain events and transactions. This guide will help you get up and running quickly with practical examples for both EVM and Stellar networks. + +## What You'll Learn + +* How to set up OpenZeppelin Monitor locally or with Docker +* How to configure monitoring for USDC transfers on Ethereum +* How to monitor DEX swaps on Stellar +* How to set up notifications via Slack and email + +## Prerequisites + +Before you begin, ensure you have the following installed: + +* ***Rust 2021 edition*** - Required for building from source +* ***Docker*** - Optional, for containerized deployment +* ***Git*** - For cloning the repository + + + +If you don’t have Rust installed, visit https://rustup.rs/ to install it. + + +### System Dependencies (Linux) + +For Ubuntu 22.04+ or Debian-based systems (both x86 and ARM64 architectures), install required packages: + +***Note:*** Python 3.9+ is required for pre-commit hooks compatibility. + +```bash +# Install required packages directly +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + git \ + pkg-config \ + libssl-dev \ + libffi-dev \ + libyaml-dev \ +``` + +Or use the provided system packages script: + +```bash +chmod +x ./scripts/linux/sys_pkgs_core.sh +./scripts/linux/sys_pkgs_core.sh // For runtime dependencies only +``` + +## Quick Setup Options + +We provide two setup paths to get you started: + +### Option 1: Automated Setup (Recommended) + +For the fastest setup experience, use our automated script that handles everything for you. + +#### What the Automated Setup Does + +The `setup_and_run.sh` script provides a complete solution that: + +* ***Builds the monitor application*** from source +* ***Copies example configurations*** from `examples/` to `config/` + * Network configurations for major blockchains + * Pre-configured monitor examples (USDC transfers, Stellar DEX swaps) + * Required filter scripts and basic trigger notifications +* ***Validates all configurations*** to ensure proper setup +* ***Optionally runs the monitor*** to verify everything works + +#### Running the Automated Setup + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Make the script executable:*** + + ```bash + chmod +x setup_and_run.sh + ``` +3. ***Run the automated setup:*** + + ```bash + ./setup_and_run.sh + ``` + +The script provides colored output and clear guidance throughout the process. + +#### After Automated Setup + +Once complete, you’ll have: + +* A fully built OpenZeppelin Monitor +* Example configurations ready for customization +* Clear guidance on next steps + +***Next Steps:*** +. Customize the copied configurations in `config/` directories +. Update RPC URLs and notification credentials +. Run the monitor with `./openzeppelin-monitor` + + + +The setup script creates working configurations with placeholder values. ***Remember to update your files with actual RPC endpoints and notification credentials*** before starting real monitoring. + + +### Option 2: Manual Setup + +For users who prefer more control over the setup process. + +#### Building from Source + +1. ***Clone and build:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + cargo build --release + ``` +2. ***Move the binary to project root:*** + + ```bash + mv ./target/release/openzeppelin-monitor . + ``` + +#### Docker Setup + +For containerized deployment: + +1. ***Start services:*** + + ```bash + docker compose up + ``` + + + + +By default, Docker Compose uses `Dockerfile.development`. For production, set: +`DOCKERFILE=Dockerfile.production` before running the command. + + + +#### Docker Management Commands + +| Command | Description | +| --- | --- | +| `docker ps -a` | Verify container status | +| `docker compose down` | Stop services (without metrics) | +| `docker compose --profile metrics down` | Stop services (with metrics) | +| `docker compose logs -f` | View logs (follow mode) | + +## Environment Configuration + +### Logging Configuration + +Configure logging verbosity by setting the `RUST_LOG` environment variable: + +| Level | Description | +| --- | --- | +| `error` | Only error messages | +| `warn` | Warnings and errors | +| `info` | General information (recommended) | +| `debug` | Detailed debugging information | +| `trace` | Very detailed trace information | + +```bash +export RUST_LOG=info +``` + +### Local Configuration + +Copy the example environment file and customize it: + +```bash +cp .env.example .env +``` + +For detailed configuration options, see [Basic Configuration](/monitor/1.1.x#basic_configuration). + +## Practical Examples + +Now let’s set up real monitoring scenarios. Choose the example that matches your needs: + +### Example 1: Monitor USDC Transfers (Ethereum) + +This example monitors large USDC transfers on Ethereum mainnet and sends notifications when transfers exceed 10,000 USDC. + +#### Step 1: Network Configuration + +Create the Ethereum mainnet configuration: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json +``` + +***Key Configuration Details:*** + +```json +{ + "network_type": "EVM", + "slug": "ethereum_mainnet", + "name": "Ethereum Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "YOUR_RPC_URL_HERE" + }, + "weight": 100 + } + ], + "chain_id": 1, + "block_time_ms": 12000, + "confirmation_blocks": 12, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 18, + "store_blocks": false +} +``` + + + + +***Important:*** Replace `YOUR_RPC_URL_HERE` with your actual Ethereum RPC endpoint. You can use providers like Infura, Alchemy, or run your own node. + + + +#### Step 2: Monitor Configuration + +Set up the USDC transfer monitor: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json +cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh +``` + +***Monitor Configuration Overview:*** + +```json +{ + "name": "Large Transfer of USDC Token", + "paused": false, + "networks": ["ethereum_mainnet"], + "addresses": [ + { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "contract_spec": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } + ] + } + ], + "match_conditions": { + "functions": [], + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 10000000000" + } + ], + "transactions": [ + { + "status": "Success", + "expression": null + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"] +} +``` + + + + +* The `expression: "value > 10000000000"` monitors transfers over 10,000 USDC (USDC has 6 decimals) +* Remove the `trigger_conditions` array to disable additional filtering +* The USDC contract address `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` is the official USDC contract on Ethereum mainnet + + + +#### Step 3: Notification Setup + +##### Slack Notifications + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +``` + +***Slack Configuration:*** + +```json +{ + "evm_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "SLACK_WEBHOOK_URL" + }, + "message": { + "title": "large_transfer_slack triggered", + "body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog" + } + } + } +} +``` + + + +To get a Slack webhook URL: + +1. Go to https://api.slack.com/apps +2. Create a new app or select existing one +3. Enable "Incoming Webhooks" +4. Create a webhook for your channel + + +##### Email Notifications + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json +``` + +***Email Configuration:*** + +```json +{ + "evm_large_transfer_usdc_email": { + "name": "Large Transfer Email Notification", + "trigger_type": "email", + "config": { + "host": "smtp.gmail.com", + "port": 465, + "username": { + "type": "plain", + "value": "your_email@gmail.com" + }, + "password": { + "type": "plain", + "value": "SMTP_PASSWORD" + }, + "message": { + "title": "large_transfer_usdc_email triggered", + "body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog" + }, + "sender": "your_email@gmail.com", + "recipients": [ + "recipient1@example.com", + "recipient2@example.com" + ] + } + } +} +``` + + + +For Gmail, you’ll need to use an "App Password" instead of your regular password. Enable 2FA and generate an app password in your Google Account settings. + + +#### Step 4: Run the Monitor + +***Local Deployment:*** + +```bash +./openzeppelin-monitor +``` + +***Docker Deployment:*** + +```bash +cargo make docker-compose-up +``` + +#### What Happens Next + +Once running, the monitor will: + +1. Check for new Ethereum blocks every minute +2. Watch for USDC transfers over 10,000 USDC +3. Send notifications via Slack and email when large transfers occur + +#### Customization Options + +* ***Adjust threshold:*** Modify `"value > 10000000000"` to change the minimum transfer amount +* ***Monitor other tokens:*** Create new monitor configurations for different ERC20 tokens +* ***Add more networks:*** Configure additional EVM networks (Polygon, BSC, etc.) + +### Example 2: Monitor DEX Swaps (Stellar) + +This example monitors large DEX swaps on Stellar mainnet. + +#### Step 1: Network Configuration + +Create the Stellar mainnet configuration: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json +``` + +***Key Configuration Details:*** + +```json +{ + "network_type": "Stellar", + "slug": "stellar_mainnet", + "name": "Stellar Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "YOUR_RPC_URL_HERE" + }, + "weight": 100 + } + ], + "network_passphrase": "Public Global Stellar Network ; September 2015", + "block_time_ms": 5000, + "confirmation_blocks": 2, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 20, + "store_blocks": true +} +``` + +#### Step 2: Monitor Configuration + +Set up the DEX swap monitor: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json +cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh +``` + +***Monitor Configuration Overview:*** + +```json +{ + "name": "Large Swap By Dex", + "paused": false, + "networks": ["stellar_mainnet"], + "addresses": [ + { + "address": "CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE", + "contract_spec": [ + { + "function_v0": { + "doc": "", + "name": "swap", + "inputs": [ + { + "doc": "", + "name": "user", + "type_": "address" + }, + { + "doc": "", + "name": "in_idx", + "type_": "u32" + }, + { + "doc": "", + "name": "out_idx", + "type_": "u32" + }, + { + "doc": "", + "name": "in_amount", + "type_": "u128" + }, + { + "doc": "", + "name": "out_min", + "type_": "u128" + } + ], + "outputs": ["u128"] + } + } + ] + } + ], + "match_conditions": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [ + { + "status": "Success", + "expression": null + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/stellar_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + } + ], + "triggers": ["stellar_large_swap_by_dex_slack"] +} +``` + + + + +* The `contract_spec` field is optional for Stellar contracts. If not provided, the monitor automatically fetches the contract’s SEP-48 interface from the chain +* You can explore Stellar contract interfaces using the [Stellar Contract Explorer](https://lab.stellar.org/smart-contracts/contract-explorer) +* The expression `"out_min > 1000000000"` monitors swaps with minimum output over 1 billion tokens +* Now, you can also filter by parameters event’s name (Stellar Protocol 23 has introduced this new feature). See this [section](/monitor/1.1.x#stellar_specifics) for more details + + + +#### Step 3: Notification Setup + +Set up Slack notifications for Stellar swaps: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +``` + +***Slack Configuration:*** + +```json +{ + "stellar_large_swap_by_dex_slack": { + "name": "Large Swap By Dex Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "slack-webhook-url" + }, + "message": { + "title": "large_swap_by_dex_slack triggered", + "body": "${monitor.name} triggered because of a large swap of ${functions.0.args.out_min} tokens | https://stellar.expert/explorer/public/tx/${transaction.hash}" + } + } + } +} +``` + +* If you want to include event details in the notification message body, you can also access event parameters by name. Here’s an example: +```bash +"body": "${monitor.name} triggered because of a large swap from ${events.0.args.from} tokens | https://stellar.expert/explorer/public/tx/${transaction.hash}" +``` + +#### Step 4: Run the Monitor + +***Local Deployment:*** + +```bash +./openzeppelin-monitor +``` + +***Docker Deployment:*** + +```bash +cargo make docker-compose-up +``` + +#### What Happens Next + +Once running, the monitor will: + +1. Check for new Stellar blocks every minute +2. Watch for large DEX swaps +3. Send notifications via Slack when large swaps occur + +## Next Steps + +Now that you have OpenZeppelin Monitor running, here are some suggestions for what to do next: + +### Testing and Validation + +* [Test your configuration](/monitor/1.1.x#testing_your_configuration) against specific block numbers +* Verify your RPC endpoints are working correctly +* Test notification channels with small transactions + +### Security and Best Practices + +* [Configure secure secret management](/monitor/1.1.x#secret_management) for sensitive data +* Use environment variables or Hashicorp Cloud Vault for credentials +* Regularly update your RPC endpoints and monitor configurations + +### Advanced Configuration + +* Explore additional examples in the [`examples/config/monitors` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/monitors) +* Set up monitoring for multiple networks simultaneously +* Configure custom filter scripts for complex conditions + +### Getting Help + +* Check the [GitHub Issues](https://github.com/OpenZeppelin/openzeppelin-monitor/issues) for known problems +* Review the [User Documentation](/monitor/1.1.x) for detailed configuration options +* Join the OpenZeppelin community for support + + + +Start with simple monitoring scenarios and gradually add complexity. This helps you understand how the system works and makes troubleshooting easier. diff --git a/docs/content/monitor/1.1.x/rpc.mdx b/docs/content/monitor/1.1.x/rpc.mdx new file mode 100644 index 00000000..9b1cdf4f --- /dev/null +++ b/docs/content/monitor/1.1.x/rpc.mdx @@ -0,0 +1,307 @@ +--- +title: RPC Client +--- + +## Overview + +The OpenZeppelin Monitor includes a robust RPC client implementation with automatic endpoint rotation and fallback capabilities. This ensures reliable blockchain monitoring even when individual RPC endpoints experience issues. + +* Multiple RPC endpoint support with weighted load balancing +* Automatic fallback on endpoint failures +* Rate limit handling (429 responses) +* Connection health checks +* Thread-safe endpoint rotation + +## Configuration + +### RPC URLs + +RPC endpoints are configured in the network configuration files with weights for load balancing: + +```json +{ + "rpc_urls": [ + { + "type_": "rpc", + "url": {"type": "plain", "value": "https://primary-endpoint.example.com"}, + "weight": 100 + }, + { + "type_": "rpc", + "url": {"type": "plain", "value": "https://backup-endpoint.example.com"}, + "weight": 50 + } + ] +} +``` + + + +For high-availability setups, configure at least 3 (private) RPC endpoints with appropriate weights to ensure continuous operation even if multiple endpoints fail. + + +### Configuration Fields + +| Field | Type | Description | +| --- | --- | --- | +| `**type_**` | `String` | Type of endpoint (currently only "rpc" is supported) | +| `**url.type**` | `String` | Secret type ("Plain", "Environment", or "HashicorpCloudVault") | +| `**url.value**` | `String` | The RPC endpoint URL | +| `**weight**` | `Number` | Load balancing weight (0-100) | + +## Endpoint Management + +The endpoint manager handles + +* Initial endpoint selection based on weights +* Automatic rotation on failures +* Connection health checks +* Thread-safe endpoint updates + +Each blockchain network type has its own specialized transport client that wraps the base `HttpTransportClient`. +The transport clients are implemented as: + +1. **Core HTTP Transport**: `HttpTransportClient` provides core HTTP functionality, including the integrated retryable client. +2. **Network-Specific Transports**: + * `EVMTransportClient` for EVM networks + * `StellarTransportClient` for Stellar networks + +### Rotation Strategy + +The RPC client includes an automatic rotation strategy for handling specific types of failures: + +* For 429 (Too Many Requests) responses: + * Immediately rotates to a fallback URL + * Retries the request with the new endpoint + * Continues this process until successful or all endpoints are exhausted + +#### Configuration Options + +The error codes that trigger RPC endpoint rotation can be customized in the `src/services/blockchain/transports/mod.rs` file. + +```rust +pub const ROTATE_ON_ERROR_CODES: [u16; 1] = [429]; +``` + +### Retry Strategy + +The transport layer uses a combination of same-endpoint retries and endpoint rotation to handle transient failures and maintain service availability. + +#### Same-Endpoint Retry (via `reqwest-retry`) + +The `HttpTransportClient` (and by extension, EVM and Stellar clients) utilizes a `reqwest_middleware::ClientWithMiddleware`. This client is configured during initialization using the `utils::http::create_retryable_http_client` utility. This utility layers `reqwest_retry::RetryTransientMiddleware` on top of a shared base `reqwest::Client`. + +This middleware handles: + +* Automatic retries for transient HTTP errors (e.g., 5xx server errors, network timeouts) for requests made to the **currently active RPC URL**. +* An exponential backoff policy between these retry attempts. +* Parameters like the number of retries, backoff durations, and jitter are defined in an `RetryConfig` struct (see [Configuration Options](#configuration-options)). +* This same-endpoint retry mechanism is independent of, and operates before, the endpoint rotation logic. If all same-endpoint retries fail for the current URL, the error is then processed by the `EndpointManager`. + +#### Endpoint Rotation (via `EndpointManager`) + +If all same-endpoint retries fail for the currently active RPC URL, or if certain HTTP status codes (e.g., 429 Too Many Requests, as defined in `ROTATE_ON_ERROR_CODES`) are received, the `EndpointManager` (used by `HttpTransportClient`) will attempt to rotate to a healthy fallback URL. This ensures that if one endpoint becomes persistently unavailable, the system can switch to an alternative. The health check for a fallback URL also benefits from the same-endpoint retry mechanism. + +#### Configuration Options + +The same-endpoint retry behavior is configured via the `RetryConfig` struct, which is used by `create_retryable_http_client` to set up the `ExponentialBackoff` policy for `reqwest-retry`. + +The default settings for `RetryConfig` result in an `ExponentialBackoff` policy approximately equivalent to: +```rust +// This illustrates the default policy created by RetryConfig::default() +// and create_retryable_http_client. +let http_retry_config = RetryConfig::default(); +let retry_policy = ExponentialBackoff::builder() + .base(http_retry_config.base_for_backoff) + .retry_bounds(http_retry_config.initial_backoff, http_retry_config.max_backoff) + .jitter(http_retry_config.jitter) + .build_with_max_retries(http_retry_config.max_retries); +``` + +The configurable options are defined in the `RetryConfig` struct: +```rust +// In utils::http +pub struct RetryConfig { + /// Maximum number of retries for transient errors (after the initial attempt). + pub max_retries: u32, + /// Initial backoff duration before the first retry. + pub initial_backoff: Duration, + /// Maximum backoff duration for retries. + pub max_backoff: Duration, + /// Base for the exponential backoff calculation (e.g., 2). + pub base_for_backoff: u64, + /// Jitter to apply to the backoff duration. + pub jitter: Jitter, +} +``` + +The client architecture ensures efficient resource use and consistent retry behavior: + +1. A single base `reqwest::Client` is created by `HttpTransportClient` with optimized connection pool settings. This base client is shared. +2. The `create_retryable_http_client` utility takes this base client and an `RetryConfig` to produce a `ClientWithMiddleware`. +3. This `ClientWithMiddleware` (the "retryable client") is then used for all HTTP operations within `HttpTransportClient`, including initial health checks, requests sent via `EndpointManager`, and `try_connect` calls during rotation. This ensures all operations benefit from the configured retry policy and the shared connection pool. + +Each transport client may define its own retry policy: + +```rust +// src/services/transports/http.rs +pub struct HttpTransportClient { + pub client: ClientWithMiddleware, + endpoint_manager: EndpointManager, + test_connection_payload: Option, +} + +// Example of client creation with retry mechanism +// Use default retry policy +let http_retry_config = RetryConfig::default(); + +// Create the base HTTP client +let base_http_client = reqwest::ClientBuilder::new() + .pool_idle_timeout(Duration::from_secs(90)) + .pool_max_idle_per_host(32) + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(20)) + .build() + .context("Failed to create base HTTP client")?; + +// Create a retryable HTTP client with the base client and retry policy +let retryable_client = create_retryable_http_client( + &http_retry_config, + base_http_client, + Some(TransientErrorRetryStrategy), // Use custom or default retry strategy +); + +``` + +### Implementation Details +The `EndpointManager` uses the retry-enabled `ClientWithMiddleware` provided by `HttpTransportClient` for its attempts on the primary URL. If these attempts (including internal `reqwest-retry` retries) ultimately fail with an error that warrants rotation (e.g., a 429 status code, or persistent network errors), then `EndpointManager` initiates the URL rotation sequence. + +```mermaid +sequenceDiagram + participant User as User/Application + participant HTC as HttpTransportClient + participant EM as EndpointManager + participant RetryClient as ClientWithMiddleware (reqwest-retry) + participant RPC_Primary as Primary RPC + participant RPC_Fallback as Fallback RPC + + User->>HTC: send_raw_request() + HTC->>EM: send_raw_request(self, ...) + EM->>RetryClient: POST to RPC_Primary + Note over RetryClient, RPC_Primary: RetryClient handles same-endpoint retries internally (e.g., for 5xx) + alt Retries on RPC_Primary succeed + RPC_Primary-->>RetryClient: Success + RetryClient-->>EM: Success + EM-->>HTC: Success + HTC-->>User: Response + else All retries on RPC_Primary fail (e.g. network error or 429) + RPC_Primary-->>RetryClient: Final Error (e.g. 429 or network error) + RetryClient-->>EM: Final Error from RPC_Primary + EM->>EM: Decide to Rotate (based on error type) + EM->>HTC: try_connect(Fallback_URL) (HTC uses its RetryClient for this) + HTC->>RetryClient: POST to RPC_Fallback (health check) + alt Fallback health check succeeds + RPC_Fallback-->>RetryClient: Success (health check) + RetryClient-->>HTC: Success (health check) + HTC-->>EM: Success (health check) + EM->>EM: Update active URL to RPC_Fallback + EM->>RetryClient: POST to RPC_Fallback (actual request) + RPC_Fallback-->>RetryClient: Success + RetryClient-->>EM: Success + EM-->>HTC: Success + HTC-->>User: Response + else Fallback health check fails + RPC_Fallback-->>RetryClient: Error (health check) + RetryClient-->>HTC: Error (health check) + HTC-->>EM: Error (health check) + EM-->>HTC: Final Error (all URLs failed) + HTC-->>User: Error Response + end + end +``` + +## List of RPC Calls + +Below is a list of RPC calls made by the monitor for each network type for each iteration of the cron schedule. +As the number of blocks being processed increases, the number of RPC calls grows, potentially leading to rate limiting issues or increased costs if not properly managed. + +```mermaid +graph TD + subgraph Common Operations + A[Main] --> D[Process New Blocks] + end + + subgraph EVM Network Calls + B[Network Init] -->|net_version| D + D -->|eth_blockNumber| E[For every block in range] + E -->|eth_getBlockByNumber| G1[Process Block] + G1 -->|eth_getLogs| H[Get Block Logs] + H -->|Only when needed| J[Get Transaction Receipt] + J -->|eth_getTransactionReceipt| I[Complete] + end + + subgraph Stellar Network Calls + C[Network Init] -->|getNetwork| D + D -->|getLatestLedger| F[In batches of 200 blocks] + F -->|getLedgers| G2[Process Block] + G2 -->|For each monitored contract without ABI| M[Fetch Contract Spec] + M -->|getLedgerEntries| N[Get WASM Hash] + N -->|getLedgerEntries| O[Get WASM Code] + O --> G2 + G2 -->|In batches of 200| P[Fetch Block Data] + P -->|getTransactions| L1[Get Transactions] + P -->|getEvents| L2[Get Events] + L1 --> Q[Complete] + L2 --> Q + end +``` + +**EVM** + +* RPC Client initialization (per active network): `net_version` +* Fetching the latest block number (per cron iteration): `eth_blockNumber` +* Fetching block data (per block): `eth_getBlockByNumber` +* Fetching block logs (per block): `eth_getLogs` +* Fetching transaction receipt (only when needed): + * When monitor condition requires receipt-specific fields (e.g., `gas_used`) + * When monitoring transaction status and no logs are present to validate status + +**Stellar** + +* RPC Client initialization (per active network): `getNetwork` +* Fetching the latest ledger (per cron iteration): `getLatestLedger` +* Fetching ledger data (batched up to 200 in a single request): `getLedgers` +* During block filtering, for each monitored contract without an ABI in config: + * Fetching contract instance data: `getLedgerEntries` + * Fetching contract WASM code: `getLedgerEntries` +* Fetching transactions (batched up to 200 in a single request): `getTransactions` +* Fetching events (batched up to 200 in a single request): `getEvents` + +## Best Practices + +* Configure multiple private endpoints with appropriate weights +* Use geographically distributed endpoints when possible +* Monitor endpoint health and adjust weights as needed +* Set appropriate retry policies based on network characteristics + +## Troubleshooting + +### Common Issues + +* **429 Too Many Requests**: Increase the number of fallback URLs, adjust weights or reduce monitoring frequency +* **Connection Timeouts**: Check endpoint health and network connectivity +* **Invalid Responses**: Verify endpoint compatibility with your network type + +### Logging + +Enable debug logging for detailed transport information: + +```bash +RUST_LOG=debug +``` + +This will show: + +* Endpoint rotations +* Connection attempts +* Request/response details diff --git a/docs/content/monitor/1.1.x/scripts.mdx b/docs/content/monitor/1.1.x/scripts.mdx new file mode 100644 index 00000000..53b14e8e --- /dev/null +++ b/docs/content/monitor/1.1.x/scripts.mdx @@ -0,0 +1,617 @@ +--- +title: Custom Scripts +--- + +OpenZeppelin Monitor allows you to implement custom scripts for additional filtering of monitor matches and custom notification handling. + + + +***Security Risk:*** Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +## Custom Filter Scripts + +Custom filter scripts allow you to apply additional conditions to matches detected by the monitor. This helps you refine the alerts you receive based on criteria specific to your use case. + +### Implementation Guide + +1. Create a script in one of the supported languages: + * Bash + * Python + * JavaScript +2. Your script will receive a JSON object with the following structure: + * EVM + + ```json + { + "args": ["--verbose"], + "monitor_match": { + "EVM": { + "matched_on": { + "events": [], + "functions": [ + { + "expression": null, + "signature": "transfer(address,uint256)" + } + ], + "transactions": [ + { + "expression": null, + "status": "Success" + } + ] + }, + "matched_on_args": { + "events": null, + "functions": [ + { + "args": [ + { + "indexed": false, + "kind": "address", + "name": "to", + "value": "0x94d953b148d4d7143028f397de3a65a1800f97b3" + }, + { + "indexed": false, + "kind": "uint256", + "name": "value", + "value": "434924400" + } + ], + "hex_signature": "a9059cbb", + "signature": "transfer(address,uint256)" + } + ] + }, + "monitor": { + "addresses": [ + { + "contract_spec": null, + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + } + ], + "match_conditions": { + "events": [ + { + "expression": "value > 10000000000", + "signature": "Transfer(address,address,uint256)" + } + ], + "functions": [ + { + "expression": null, + "signature": "transfer(address,uint256)" + } + ], + "transactions": [ + { + "expression": null, + "status": "Success" + } + ] + }, + "name": "Large Transfer of USDC Token", + "networks": ["ethereum_mainnet"], + "paused": false, + "trigger_conditions": [ + { + "arguments": ["--verbose"], + "language": "Bash", + "script_path": "./config/filters/evm_filter_block_number.sh", + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_script"] + }, + "receipt": { + "blockHash": "0x...", + "blockNumber": "0x...", + "contractAddress": null, + "cumulativeGasUsed": "0x...", + "effectiveGasPrice": "0x...", + "from": "0x...", + "gasUsed": "0xb068", + "status": "0x1", + "to": "0x...", + "transactionHash": "0x...", + "transactionIndex": "0x1fc", + "type": "0x2" + }, + "logs": [ + { + "address": "0xd1f2586790a5bd6da1e443441df53af6ec213d83", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000060af8cf92e5aa9ead4a592d657cd6debecfbc616", + "0x000000000000000000000000d1f2586790a5bd6da1e443441df53af6ec213d83" + ], + "data": "0x00000000000000000000000000000000000000000000106015728793d21f77ac", + "blockNumber": "0x1451aca", + "transactionHash": "0xa39d1b9b3edda74414bd6ffaf6596f8ea12cf0012fd9a930f71ed69df6ff34d0", + "transactionIndex": "0x0", + "blockHash": "0x9432868b7fc57e85f0435ca3047f6a76add86f804b3c1af85647520061e30f80", + "logIndex": "0x2", + "removed": false + }, + ], + "transaction": { + "accessList": [], + "blockHash": "0x...", + "blockNumber": "0x1506545", + "chainId": "0x1", + "from": "0x...", + "gas": "0x7a120", + "gasPrice": "0x...", + "hash": "0x...", + "maxFeePerGas": "0x...", + "maxPriorityFeePerGas": "0x...", + "nonce": "0x14779f", + "to": "0x...", + "transactionIndex": "0x...", + "type": "0x2", + "value": "0x0" + } + } + } + } + ``` + * Stellar + + ```json + { + "args": ["--verbose"], + "monitor_match": { + "Stellar": { + "monitor": { + "name": "Large Swap By Dex", + "networks": ["stellar_mainnet"], + "paused": false, + "addresses": [ + { + "address": "GCXYK...", + "contract_spec": null + } + ], + "match_conditions": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [] + }, + "trigger_conditions": [ + { + "arguments": ["--verbose"], + "language": "Bash", + "script_path": "./config/filters/stellar_filter_block_number.sh", + "timeout_ms": 1000 + } + ], + "triggers": ["stellar_large_transfer_usdc_script"] + }, + "transaction": { + "status": "SUCCESS", + "txHash": "2b5a0c...", + "applicationOrder": 3, + "feeBump": false, + "envelopeXdr": "AAAAAA...", + "envelopeJson": { + "type": "ENVELOPE_TYPE_TX", + "tx": {/* transaction details */} + }, + "resultXdr": "AAAAAA...", + "resultJson": {/* result details */}, + "resultMetaXdr": "AAAAAA...", + "resultMetaJson": {/* metadata details */}, + "diagnosticEventsXdr": ["AAAAAA..."], + "diagnosticEventsJson": [{/* event details */}], + "ledger": 123456, + "createdAt": 1679644800, + "decoded": { + "envelope": {/* decoded envelope */}, + "result": {/* decoded result */}, + "meta": {/* decoded metadata */} + } + }, + "ledger": { + "hash": "abc1...", + "sequence": 123456, + "ledgerCloseTime": "2024-03-20T10:00:00Z", + "headerXdr": "AAAAAA...", + "headerJson": {/* header details */}, + "metadataXdr": "AAAAAA...", + "metadataJSON": {/* metadata details */} + }, + "matched_on": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [] + }, + "matched_on_args": { + "functions": [], + "events": null + } + } + } + } + ``` + +### Script Output Requirements + +* Your script should print a boolean value indicating whether the match should be filtered. +* Print `true` if the match should be filtered out (not trigger an alert). +* Print `false` if the match should be processed (trigger an alert). +* Only the **last** printed line will be considered for evaluation. + +### Example Filter Script (Bash) + +```bash +#!/bin/bash + +main() { + # Read JSON input from stdin + input_json=$(cat) + + # Parse arguments from the input JSON and initialize verbose flag + verbose=false + args=$(echo "$input_json" | jq -r '.args[]? // empty') + if [ ! -z "$args" ]; then + while IFS= read -r arg; do + if [ "$arg" = "--verbose" ]; then + verbose=true + echo "Verbose mode enabled" + fi + done <<< "$args" + fi + + # Extract the monitor match data from the input + monitor_data=$(echo "$input_json" | jq -r '.monitor_match') + + if [ "$verbose" = true ]; then + echo "Input JSON received:" + fi + + # Extract blockNumber from the EVM receipt or transaction + block_number_hex=$(echo "$monitor_data" | jq -r '.EVM.transaction.blockNumber' || echo "") + + # Validate that block_number_hex is not empty + if [ -z "$block_number_hex" ]; then + echo "Invalid JSON or missing blockNumber" + echo "false" + exit 1 + fi + + # Remove 0x prefix if present and clean the string + block_number_hex=$(echo "$block_number_hex" | tr -d '\n' | tr -d ' ') + block_number_hex=${block_number_hex#0x} + + if [ "$verbose" = true ]; then + echo "Extracted block number (hex): $block_number_hex" + fi + + # Convert hex to decimal with error checking + if ! block_number=$(printf "%d" $((16#${block_number_hex})) 2>/dev/null); then + echo "Failed to convert hex to decimal" + echo "false" + exit 1 + fi + + if [ "$verbose" = true ]; then + echo "Converted block number (decimal): $block_number" + fi + + # Check if even or odd using modulo + is_even=$((block_number % 2)) + + if [ $is_even -eq 0 ]; then + echo "Block number $block_number is even" + echo "Verbose mode: $verbose" + echo "true" + exit 0 + else + echo "Block number $block_number is odd" + echo "Verbose mode: $verbose" + echo "false" + exit 0 + fi +} + +# Call main function +main +``` + +### Example Filter Script (JavaScript) + +```bash +#!/bin/bash + +try { + let inputData = ''; + // Read from stdin + process.stdin.on('data', chunk => { + inputData += chunk; + }); + + process.stdin.on('end', () => { + const data = JSON.parse(inputData); + const monitorMatch = data.monitor_match; + const args = data.args; + + // Extract block_number + let blockNumber = null; + if (monitorMatch.EVM) { + const hexBlock = monitorMatch.EVM.transaction?.blockNumber; + if (hexBlock) { + // Convert hex string to integer + blockNumber = parseInt(hexBlock, 16); + } + } + + if (blockNumber === null) { + console.log('false'); + return; + } + + const result = blockNumber % 2 === 0; + console.log(`Block number ${blockNumber} is ${result ? 'even' : 'odd'}`); + console.log(result.toString()); + }); +} catch (e) { + console.log(`Error processing input: ${e}`); + console.log('false'); +} + +``` + +### Example Filter Script (Python) + +```bash +#!/bin/bash + +import sys +import json + +def main(): + try: + # Read input from stdin + input_data = sys.stdin.read() + if not input_data: + print("No input JSON provided", flush=True) + return False + + # Parse input JSON + try: + data = json.loads(input_data) + monitor_match = data['monitor_match'] + args = data['args'] + except json.JSONDecodeError as e: + print(f"Invalid JSON input: {e}", flush=True) + return False + + # Extract block_number + block_number = None + if "EVM" in monitor_match: + hex_block = monitor_match['EVM']['transaction'].get('blockNumber') + if hex_block: + # Convert hex string to integer + block_number = int(hex_block, 16) + + if block_number is None: + print("Block number is None") + return False + + result = block_number % 2 == 0 + print(f"Block number {block_number} is {'even' if result else 'odd'}", flush=True) + return result + + except Exception as e: + print(f"Error processing input: {e}", flush=True) + return False + +if __name__ == "__main__": + result = main() + # Print the final boolean result + print(str(result).lower(), flush=True) + +``` + +This examples script filters EVM transactions based on their block number: + +* Returns `true` (filter out) for transactions in even-numbered blocks +* Returns `false` (allow) for transactions in odd-numbered blocks +* Accepts a `--verbose` flag for detailed logging +* Explore other examples in the [`examples/config/filters` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/filters). + +### Integration + +Integrate your custom filter script with the monitor by following the [configuration guidelines](/monitor/1.1.x#trigger_conditions_custom_filters). + + + + +Trigger conditions are executed sequentially based on their position in the trigger conditions array. Every filter must return `false` for the match to be included and are only considered if they were executed successfully. + + + +## Custom Notification Scripts + +Custom notification scripts allow you to define how alerts are delivered when specific conditions are met. This can include sending alerts to different channels or formatting notifications in a particular way. + +### Implementation Guide + +1. Create a script in one of the supported languages: + * Bash + * Python + * JavaScript +2. Your script will receive the same JSON input format as [filter scripts](#implementation_guide) + +### Script Output Requirements + +* A non-zero exit code indicates an error occurred +* Error messages should be written to `stderr` +* A zero exit code indicates successful execution + +### Example Notification Script (Bash) + +```bash +#!/bin/bash + +main() { + # Read JSON input from stdin + input_json=$(cat) + + # Parse arguments from the input JSON and initialize verbose flag + verbose=false + args=$(echo "$input_json" | jq -r '.args[]? // empty') + if [ ! -z "$args" ]; then + while IFS= read -r arg; do + if [ "$arg" = "--verbose" ]; then + verbose=true + echo "Verbose mode enabled" + fi + done <<< "$args" + fi + + # Extract the monitor match data from the input + monitor_data=$(echo "$input_json" | jq -r '.monitor_match') + + # Validate input + if [ -z "$input_json" ]; then + echo "No input JSON provided" + exit 1 + fi + + # Validate JSON structure + if ! echo "$input_json" | jq . >/dev/null 2>&1; then + echo "Invalid JSON input" + exit 1 + fi + + if [ "$verbose" = true ]; then + echo "Input JSON received:" + echo "$input_json" | jq '.' + echo "Monitor match data:" + echo "$monitor_data" | jq '.' + fi + + # Process args if they exist + args_data=$(echo "$input_json" | jq -r '.args') + if [ "$args_data" != "null" ]; then + echo "Args: $args_data" + fi + + # If we made it here, everything worked + echo "Verbose mode: $verbose" + # return a non zero exit code and an error message + echo "Error: This is a test error" >&2 + exit 1 +} + +# Call main function +main +``` + +### Example Notification Script (JavaScript) + +```bash +#!/bin/bash + +try { + let inputData = ''; + // Read from stdin + process.stdin.on('data', chunk => { + inputData += chunk; + }); + + process.stdin.on('end', () => { + // Parse input JSON + const data = JSON.parse(inputData); + const monitorMatch = data.monitor_match; + const args = data.args; + + // Log args if they exist + if (args && args.length > 0) { + console.log(`Args: ${JSON.stringify(args)}`); + } + + // Validate monitor match data + if (!monitorMatch) { + console.log("No monitor match data provided"); + return; + } + }); +} catch (e) { + console.log(`Error processing input: ${e}`); +} + +``` + +### Example Notification Script (Python) + +```bash +#!/bin/bash + +import sys +import json + +def main(): + try: + # Read input from stdin + input_data = sys.stdin.read() + if not input_data: + print("No input JSON provided", flush=True) + + # Parse input JSON + try: + data = json.loads(input_data) + monitor_match = data['monitor_match'] + args = data['args'] + if args: + print(f"Args: {args}") + except json.JSONDecodeError as e: + print(f"Invalid JSON input: {e}", flush=True) + + + except Exception as e: + print(f"Error processing input: {e}", flush=True) + +if __name__ == "__main__": + main() + +``` + +This examples demonstrates how to: + +* Process the input JSON data +* Handle verbose mode for debugging +* Return error messages via `stderr` +* Set appropriate exit codes +* Explore other examples in the [`examples/config/triggers/scripts` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/triggers/scripts). + +### Integration + +Integrate your custom notification script with the triggers by following the [configuration guidelines](/monitor/1.1.x#custom_script_notifications). + +## Performance Considerations + +* **File descriptor limits**: Each script execution requires file descriptors for `stdin`, `stdout`, and `stderr` + * Ensure your system allows at least 2,048 open file descriptors + * Check your current limit on Unix-based systems with `ulimit -n` + * Temporarily increase the limit with `ulimit -n 2048` + * For permanent changes, modify `/etc/security/limits.conf` or equivalent for your system +* **Script timeout**: Configure appropriate timeout values in your trigger conditions to prevent long-running scripts from blocking the pipeline + * The `timeout_ms` parameter controls how long a script can run before being terminated +* **Resource usage**: Complex scripts may consume significant CPU or memory resources + * Consider optimizing resource-intensive operations in your scripts + * Monitor system performance during high-volume periods +* **Script reloading**: Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect diff --git a/docs/content/monitor/1.1.x/testing.mdx b/docs/content/monitor/1.1.x/testing.mdx new file mode 100644 index 00000000..7f822d16 --- /dev/null +++ b/docs/content/monitor/1.1.x/testing.mdx @@ -0,0 +1,127 @@ +--- +title: Testing Guide +--- + +This document provides information about testing OpenZeppelin Monitor, including running tests, generating coverage reports, and understanding the test structure. + +## Test Organization + +The project includes comprehensive test suites organized into different categories: + +### Test Types + +* ***Unit Tests***: Located within `src/` modules alongside the code they test +* ***Integration Tests***: Located in `tests/integration/` directory +* ***Property-based Tests***: Located in `tests/properties/` directory +* ***Mock Implementations***: Located in `tests/integration/mocks/` + +### Test Structure + +``` +tests/ +├── integration/ # Integration tests +│ ├── blockchain/ # Blockchain client tests +│ ├── blockwatcher/ # Block monitoring tests +│ ├── filters/ # Filter logic tests +│ ├── fixtures/ # Test data and configurations +│ ├── mocks/ # Mock implementations +│ └── ... +├── properties/ # Property-based tests +│ ├── filters/ # Filter property tests +│ ├── notifications/ # Notification property tests +│ └── ... +└── integration.rs # Integration test entry point +``` + +## Running Tests + +### All Tests + +Run the complete test suite: + +```bash +RUST_TEST_THREADS=1 cargo test +``` + + + + +`RUST_TEST_THREADS=1` is required to prevent test conflicts when accessing shared resources like configuration files or network connections. + + + +### Specific Test Categories + +***Property-based Tests:*** +```bash +RUST_TEST_THREADS=1 cargo test properties +``` + +***Integration Tests:*** +```bash +RUST_TEST_THREADS=1 cargo test integration +``` + +***Unit Tests Only:*** +```bash +RUST_TEST_THREADS=1 cargo test --lib +``` + +## Coverage Reports + +### Prerequisites + +Install the coverage tool: +```bash +rustup component add llvm-tools-preview +cargo install cargo-llvm-cov +``` + +### Generating Coverage + +***HTML Coverage Report:*** +```bash +RUST_TEST_THREADS=1 cargo +stable llvm-cov --html --open +``` + +This generates an HTML report in `target/llvm-cov/html/` and opens it in your browser. + +***Terminal Coverage Report:*** +```bash +RUST_TEST_THREADS=1 cargo +stable llvm-cov +``` + +## Troubleshooting + +### Common Issues + +***Tests hanging or timing out:*** +- Ensure `RUST_TEST_THREADS=1` is set +- Verify mock setups are correct + +***Coverage tool not found:*** +- Install with `cargo install cargo-llvm-cov` +- Add component with `rustup component add llvm-tools-preview` + +***Permission errors:*** +- Ensure test directories are writable +- Check file permissions on test fixtures + +### Debug Output + +Enable debug logging for tests: +```bash +RUST_LOG=debug RUST_TEST_THREADS=1 cargo test -- --nocapture +``` + +## Contributing Tests + +When contributing new features: + +1. ***Add comprehensive tests*** for new functionality +2. ***Ensure all tests pass*** locally before submitting +3. ***Include both unit and integration tests*** where appropriate +4. ***Update test documentation*** if adding new test patterns +5. ***Maintain or improve code coverage*** + +For more information about contributing, see the project’s contributing guidelines. diff --git a/docs/content/monitor/architecture.mdx b/docs/content/monitor/architecture.mdx new file mode 100644 index 00000000..65e5aa3b --- /dev/null +++ b/docs/content/monitor/architecture.mdx @@ -0,0 +1,386 @@ +--- +title: Architecture Guide +--- + +This document describes the high-level architecture of OpenZeppelin Monitor, including the core components, their interactions, and the overall system design. It provides a technical overview of how the service processes blockchain data and triggers notifications based on configurable conditions. + +## System Overview + +OpenZeppelin Monitor is organized as a data processing pipeline that spans from blockchain data collection to notification delivery. The system follows a modular architecture with distinct components for each step in the monitoring process, designed for scalability and extensibility. + +### High-Level Architecture + +The diagram below shows the core processing pipeline of OpenZeppelin Monitor, from blockchain networks and configuration through to notification channels: + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +flowchart LR + subgraph "Blockchain Networks" + EVMN["EVM Networks"] + STLN["Stellar Networks"] + end + subgraph "Configuration" + NETCFG["Network Configs"] + MONCFG["Monitor Configs"] + TRICFG["Trigger Configs"] + end + subgraph "Core Processing Pipeline" + BWS["BlockWatcherService"] + FS["FilterService"] + TS["TriggerService"] + NS["NotificationService"] + end + subgraph "Notification Channels" + Slack + Email + Discord + Telegram + Webhook + Scripts["Custom Scripts"] + end + + %% Data and config flow + EVMN --> BWS + STLN --> BWS + NETCFG --> BWS + MONCFG --> FS + TRICFG --> TS + BWS --> FS + FS --> TS + TS --> NS + NS --> Slack + NS --> Email + NS --> Discord + NS --> Telegram + NS --> Webhook + NS --> Scripts + + %% Styling for major blocks and notification channels + classDef blockchain fill:#fff3e0,stroke:#ef6c00,stroke-width:2px; + classDef config fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px; + classDef mainBlock fill:#e1f5fe,stroke:#01579b,stroke-width:2px; + classDef notification fill:#fce4ec,stroke:#c2185b,stroke-width:2px; + class EVMN,STLN blockchain; + class NETCFG,MONCFG,TRICFG config; + class BWS,FS,TS,NS mainBlock; + class Slack,Email,Discord,Telegram,Webhook,Scripts notification; +``` + +## Component Architecture + +The system consists of several core services that are initialized at startup and work together to process blockchain data and trigger notifications. The service initialization and dependencies are managed through the bootstrap module. + +### Service Initialization Flow + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +graph TD + subgraph Entry Point + MAIN[main.rs] + end + + subgraph Bootstrap + BOOTSTRAP[Bootstrap::initialize_service] + end + + subgraph Block Processing + BT[BlockTracker] + BS[BlockStorage] + BWS[BlockWatcherService] + BH[create_block_handler] + end + + subgraph Core Services + MS[MonitorService] + NS[NetworkService] + TS[TriggerService] + FS[FilterService] + TES[TriggerExecutionService] + NOTS[NotificationService] + end + + subgraph Client Layer + CP[ClientPool] + EVMC[EVMClient] + SC[StellarClient] + end + + %% Initialization Flow + MAIN --> BOOTSTRAP + BOOTSTRAP --> CP + BOOTSTRAP --> NS + BOOTSTRAP --> MS + BOOTSTRAP --> TS + BOOTSTRAP --> FS + BOOTSTRAP --> TES + BOOTSTRAP --> NOTS + + %% Block Processing Setup + BOOTSTRAP --> BT + BOOTSTRAP --> BS + BOOTSTRAP --> BWS + BOOTSTRAP --> BH + + %% Client Dependencies + CP --> EVMC + CP --> SC + BWS --> CP + + %% Service Dependencies + BWS --> BS + BWS --> BT + MS --> NS + MS --> TS + FS --> TES + TES --> NOTS + + %% Block Handler Connection + BH --> FS + BWS --> BH + + style MAIN fill:#e1f5fe,stroke:#01579b + style BOOTSTRAP fill:#fff3e0,stroke:#ef6c00 + classDef blockProcessing fill:#e8f5e9,stroke:#2e7d32 + classDef coreServices fill:#f3e5f5,stroke:#7b1fa2 + classDef clients fill:#fce4ec,stroke:#c2185b + + class BT,BS,BWS,BH blockProcessing + class MS,NS,TS,FS,TES,NOTS coreServices + class CP,EVMC,SC clients +``` + +### Core Components + +#### Block Processing Components + +* ***BlockWatcherService***: Orchestrates the block monitoring process by polling blockchain networks for new blocks and coordinating the processing pipeline. +* ***BlockTracker***: Tracks processed block numbers to prevent duplicate processing and ensure data consistency across service restarts. +* ***BlockStorage***: Persists block processing state for recovery and maintains the last processed block number for each network. + +#### Client Layer Components + +* ***ClientPool***: Manages blockchain client instances and provides network connectivity with connection pooling and failover capabilities. +* ***EVMClient***: Handles communication with Ethereum Virtual Machine compatible networks (Ethereum, Polygon, BSC, etc.). +* ***StellarClient***: Manages connections to Stellar blockchain networks with protocol-specific optimizations. + +#### Processing Pipeline Components + +* ***FilterService***: Applies monitor filters to blockchain data, evaluating conditions and match expressions to identify relevant transactions and events. +* ***TriggerExecutionService***: Executes triggers based on matched monitor conditions, evaluating trigger logic and preparing notification payloads. +* ***NotificationService***: Delivers notifications through configured channels (Slack, Email, Discord, Telegram, Webhooks, Scripts). + +#### Configuration Management Components + +* ***MonitorService***: Manages monitor configurations and provides access to active monitors with validation and lifecycle management. +* ***NetworkService***: Manages network configurations and provides network details for client connections and monitoring operations. +* ***TriggerService***: Manages trigger configurations and provides trigger details for notification execution. + +### Service Responsibilities + +The following table describes the key responsibilities of each service in the OpenZeppelin Monitor architecture: + +| Service | Responsibility | +| --- | --- | +| **MonitorService** | Manages monitor configurations and provides access to active monitors | +| **NetworkService** | Manages network configurations and provides network details | +| **TriggerService** | Manages trigger configurations and provides trigger details | +| **FilterService** | Filters blockchain data based on monitor conditions and match expressions | +| **TriggerExecutionService** | Executes triggers based on matched monitor conditions | +| **NotificationService** | Delivers notifications through configured channels | +| **BlockWatcherService** | Polls blockchain networks for new blocks and coordinates processing | +| **BlockTracker** | Tracks processed block numbers to prevent duplicate processing | +| **BlockStorage** | Persists block processing state for recovery | +| **ClientPool** | Manages blockchain client instances and provides network connectivity | + +### Block Processing Workflow + +The following _runtime flow_ illustrates how data moves through the system, from blockchain networks to notification channels. This sequence represents the core monitoring loop that executes for each configured network. + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +sequenceDiagram + participant BWS as BlockWatcherService + participant BS as BlockStorage + participant BC as BlockchainClient + participant FS as FilterService + participant TES as TriggerExecutionService + participant NS as NotificationService + + rect rgb(232, 245, 233) + Note over BWS: Orchestrates block processing + BWS->>BS: Get last processed block + end + + rect rgb(225, 245, 254) + Note over BC: Blockchain interface + BWS->>BC: Get latest block number + BWS->>BC: Get blocks (last+1 to latest) + end + + loop For each block + rect rgb(243, 229, 245) + Note over FS: Applies monitor filters + BWS->>FS: filter_block(block) + FS->>FS: Apply monitor filters + FS-->>BWS: Monitor matches + end + + rect rgb(255, 248, 225) + Note over TES: Evaluates trigger conditions + BWS->>TES: Process monitor matches + TES->>TES: Run trigger conditions + end + + rect rgb(252, 228, 236) + Note over NS: Delivers notifications + TES->>NS: Execute notifications + end + end + + rect rgb(255, 243, 224) + Note over BS: Persists processing state + BWS->>BS: Store latest processed block + end +``` + +### Data Flow Architecture + +#### 1. Block Discovery Phase +The `BlockWatcherService` initiates the monitoring cycle by: + +* Retrieving the last processed block number from `BlockStorage` +* Querying the blockchain for the latest block number +* Calculating the range of new blocks to process + +#### 2. Data Retrieval Phase +The `BlockchainClient` fetches block data: + +* Connects to the appropriate blockchain network via RPC +* Retrieves full block data including transactions and events +* Handles network-specific data formats and protocols + +#### 3. Filtering Phase +The `FilterService` processes each block: + +* Applies monitor-specific filters to transactions and events +* Evaluates match expressions and conditions +* Identifies relevant data that matches monitoring criteria + +#### 4. Trigger Evaluation Phase +The `TriggerExecutionService` processes matches: + +* Evaluates trigger conditions for matched data +* Prepares notification payloads with relevant context +* Determines which notification channels to activate + +#### 5. Notification Delivery Phase +The `NotificationService` delivers alerts: + +* Formats messages for each notification channel +* Handles channel-specific delivery protocols +* Manages delivery retries and error handling + +#### 6. State Persistence Phase +The `BlockStorage` updates processing state: + +* Records the latest processed block number +* Ensures data consistency for recovery scenarios +* Maintains processing history for debugging + +### Error Handling and Resilience + +The architecture includes several resilience mechanisms: + +* ***Connection Pooling***: The `ClientPool` manages multiple connections to prevent single points of failure +* ***State Recovery***: `BlockStorage` enables the service to resume from the last known good state after restarts +* ***Retry Logic***: Notification delivery includes configurable retry mechanisms for transient failures +* ***Graceful Degradation***: Individual component failures don’t cascade to the entire system + +For detailed information about RPC logic and network communication, see the [RPC section](/monitor/rpc). + +## Configuration Architecture + +The system uses a JSON-based configuration system organized into distinct categories: + +### Configuration Categories + +* ***Network Configurations***: Define blockchain network connections, RPC endpoints, and network parameters +* ***Monitor Configurations***: Specify monitoring rules, conditions, and network/trigger references +* ***Trigger Configurations***: Define notification settings and script definitions +* ***Filter Configurations***: Contain match filter scripts for data filtering + +### Configuration Validation + +The system implements comprehensive validation: + +* Cross-reference validation between monitors, networks, and triggers +* Schema validation for all configuration files +* Runtime validation of configuration references during service startup + + + +For configuration examples and best practices, see the [Configuration Guidelines](#configuration-guidelines) section in the user documentation. + + +## Extensibility Points + +The architecture is designed for extensibility in several key areas: + +### Blockchain Support +* ***Client Layer***: New blockchain protocols can be added by implementing the `BlockchainClient` trait +* ***Transport Layer***: Protocol-specific transport clients handle network communication details +* ***Filter Layer***: Chain-specific filters process protocol-dependent data formats + +### Notification Channels +* ***Channel Plugins***: New notification channels can be added by implementing the notification interface +* ***Script Support***: Custom notification logic can be implemented using Python, JavaScript, or Bash scripts + +### Monitoring Logic +* ***Expression Engine***: Flexible expression evaluation for complex monitoring conditions +* ***Script Triggers***: Custom trigger logic can be implemented using supported scripting languages + +## Performance Considerations + +The architecture is optimized for: + +* ***Concurrent Processing***: Multiple networks can be monitored simultaneously +* ***Efficient Block Processing***: Batch processing of blocks to minimize RPC calls +* ***Memory Management***: Streaming processing of large blocks to prevent memory issues +* ***Connection Reuse***: Client pooling reduces connection overhead + +## Security Architecture + +The system implements several security measures: + +* ***Secure Protocols***: Support for HTTPS/WSS +* ***Secret Management***: Secure handling of API keys and sensitive configuration data +* ***Input Validation***: Comprehensive validation of all external inputs and configurations + +## Related Documentation + +For detailed information about the project structure, source code organization, and development resources, see the [Project Structure](/monitor/project-structure) guide. + +For information about RPC logic and network communication, see the [RPC section](/monitor/rpc). + +For configuration examples and best practices, see the [Configuration Guidelines](#configuration-guidelines) section in the user documentation. diff --git a/docs/content/monitor/changelog.mdx b/docs/content/monitor/changelog.mdx new file mode 100644 index 00000000..276fa8ad --- /dev/null +++ b/docs/content/monitor/changelog.mdx @@ -0,0 +1,300 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v1.1.0) - 2025-10-22 + +## [1.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v1.0.0...v1.1.0) (2025-10-22) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Add events and functions summary in notifications ([#339](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/339)) ([000ae24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/000ae24e896cd0867c6252111a71151942d820bc)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Enhance filter expression parsing and evaluation ([#222](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/222)) ([3cb0849](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3cb084919b3d477f329a85fbafce1ce6d696b16d)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Handle Stellar JSON-RPC outside of retention window error for `getTransactions` and `getEvents` ([#270](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/270)) ([ae116ff](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ae116ff10f393a04c19d3b845df656027c6be4b9)) +* Implement client pooling for Webhook-based notifiers ([#281](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/281)) ([4f480c6](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4f480c6a05aeb949cfd8e227c5c08f19a5e60180)) +* Introduce `TransportError` ([#259](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/259)) ([0e04cfb](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0e04cfb57109251095ef8ee526fb5e05f5792792)) +* Introduce centralized retryable HTTP client creation ([#273](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/273)) ([5f6edaf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5f6edaf5deb77a5d9dfead52a162e923aad6a2ab)) +* Introduce retry mechanism for Email notifier ([#282](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/282)) ([b6301aa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b6301aaac963ae904d93e07674d9d01543ecfcd0)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) +* Update payload builder to support formatted titles ([#336](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/336)) ([12213b3](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/12213b32d609bf6a1ba69ce548f70809971f9fb3)) +* Upgrade stellar crates and read events from specs ([#371](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/371)) ([7273a3f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7273a3f8d9249692db6b6ca53f4d8b28b21670f4)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* Bump MSRV ([#291](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/291)) ([f2d7953](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f2d795310cd1417ad2fac854ea5f80cf6296b761)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* correct env var value in semgrep.yml ([#317](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/317)) ([7a8253f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7a8253fd23ae27c73b3971e2a688c39051c08a84)) +* Deprecate Stellar `paging_token` in `GetEvents` response ([#344](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/344)) ([68d20f9](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/68d20f91b643ef3a7c85ee897308d4f92d43698b)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Duplicate name in triggers config ([#274](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/274)) ([00f58f4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/00f58f4be3f9452792f9fdcf5dd8696947a274cb)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Generate SBOM step in release pipeline ([#294](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/294)) ([327269d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/327269d1ce2a16e9c8419e872ca02503c318c480)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* Multi arch. docker images and binary mismatch ([#382](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/382)) ([a61701e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a61701e11a13af03cdf86689b58e670b7d984a38)) +* Pagination logic in stellar getEvents relies only on cursor data ([#265](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/265)) ([fca4057](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fca4057ff5847e04981e5903eebe6ccf3931726c)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* Release binaries and enable nightly workflows to create binary artifacts and images ([#313](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/313)) ([43a0091](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/43a0091ed7b57a4ca33ca25a73423a73929802f7)) +* Remove deprecated reviewers field from dependabot.yml ([#316](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/316)) ([152843d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/152843df396b089e1c6054221206097339502f1b)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* Replace automatic minor version bumps ([#285](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/285)) ([0c9e14a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0c9e14a542cae2d2c7ff580ff7de28b0d9aab22a)) +* Risk of key collision for monitor custom scripts ([#258](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/258)) ([2aa4cd7](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2aa4cd730dbcbbd1cf0892394cedc4ea06332375)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) +* Semgrep CI integration ([#315](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/315)) ([a2bc23b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a2bc23baa27630ba914fca12ac40b191cbbad525)) +* Stellar ledgers are deterministic ([#257](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/257)) ([56a9f9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/56a9f9e10e533ea96c01cb1f0f67024600ad89df)) +* syntax error in codeql.yml ([#322](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/322)) ([7068e9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/7068e9ee3845a007ed9d6c80157cbe86555ad14e)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Update the Semgrep config ([#306](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/306)) ([d4ed740](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d4ed7405e790098a0b1a0df3701feccb1908c56c)) +* Use unicode character for emoji ([#295](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/295)) ([bdccda5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bdccda5f2ca72612a4455a293c30647618476f95)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v1.0.0) - 2025-06-30 + +## [1.0.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.2.0...v1.0.0) (2025-06-30) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Enhance filter expression parsing and evaluation ([#222](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/222)) ([3cb0849](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3cb084919b3d477f329a85fbafce1ce6d696b16d)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Handle Stellar JSON-RPC outside of retention window error for `getTransactions` and `getEvents` ([#270](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/270)) ([ae116ff](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ae116ff10f393a04c19d3b845df656027c6be4b9)) +* Implement client pooling for Webhook-based notifiers ([#281](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/281)) ([4f480c6](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4f480c6a05aeb949cfd8e227c5c08f19a5e60180)) +* Introduce `TransportError` ([#259](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/259)) ([0e04cfb](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0e04cfb57109251095ef8ee526fb5e05f5792792)) +* Introduce centralized retryable HTTP client creation ([#273](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/273)) ([5f6edaf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5f6edaf5deb77a5d9dfead52a162e923aad6a2ab)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Duplicate name in triggers config ([#274](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/274)) ([00f58f4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/00f58f4be3f9452792f9fdcf5dd8696947a274cb)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* Pagination logic in stellar getEvents relies only on cursor data ([#265](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/265)) ([fca4057](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fca4057ff5847e04981e5903eebe6ccf3931726c)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* Replace automatic minor version bumps ([#285](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/285)) ([0c9e14a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/0c9e14a542cae2d2c7ff580ff7de28b0d9aab22a)) +* Risk of key collision for monitor custom scripts ([#258](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/258)) ([2aa4cd7](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2aa4cd730dbcbbd1cf0892394cedc4ea06332375)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) +* Stellar ledgers are deterministic ([#257](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/257)) ([56a9f9e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/56a9f9e10e533ea96c01cb1f0f67024600ad89df)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + +[Changes][v1.0.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v0.2.0) - 2025-05-14 + +## [0.2.0](https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.1.0...v0.2.0) (2025-05-14) + + +## ⚠️ ⚠️ Breaking Changes in v0.2.0 + +* Renamed abi to contract_spec in monitor configurations. +* Stellar function expressions now use named parameters instead of positional indexes, for example; + + ``` + (Transfer(address,address,amount)): + 2 > 1000 → amount > 1000 + ``` +* Template variables now follow dot notation rather than underscores, for example: + * monitor_name → monitor.name + * transaction_hash → transaction.hash + * function_0_amount → functions.0.args.amount + * event_0_signature → events.0.signature +* Sensitive configuration values (e.g., URLs, usernames, passwords, tokens) must now be defined using the SecretValue object structure, for example: + + * RPC URLs: + + ``` + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "https://eth.drpc.org" + }, + "weight": 100 + } + ] + ``` + + * Webhook URLs: + + ``` + "discord_url": { + "type": "plain", + "value": "https://discord.com/api/webhooks/123-456-789" + } + ``` + + +### 🚀 Features + +* Add environment vars and Hashicorp cloud vault support (breaking) ([#199](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/199)) ([558304f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/558304f335a645c1de2d348a041337ccba2c2a06)) +* Extend support for EVM transaction properties ([#187](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/187)) ([f20086b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f20086b0431a787dd55aa8928a09aece80b9a731)) +* Leverage contract spec (SEP-48) for Stellar functions (breaking) ([#208](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/208)) ([5ebc2a4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5ebc2a441b9ac6ed66a0807cac2795af2ae5b1c8)) +* Markdown for telegram, discord, slack and email ([#197](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/197)) ([791bf4b](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/791bf4b347d8cfe03ccd53e9797f179c15629a33)) +* Test execute the monitor against specific block ([#133](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/133)) ([563c34f](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/563c34fde3c0f334a7c5884de5510bf27e4fca48)) + + +### 🐛 Bug Fixes + +* Adding validation for unknown field names ([#223](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/223)) ([cadf4da](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cadf4dac293e2c24a02a2eb188540e1eb312b75f)) +* CLA labels and assistant ([#176](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/176)) ([b14f060](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b14f0600dc4cac5a5f00d3772328abe123114b2a)) +* Docs pipeline ([#167](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/167)) ([1e78ec4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1e78ec4f98f70ac12dea353c1605ac4ac2c5734b)) +* Environment adjustments and cargo lock file improvements ([#219](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/219)) ([1b4d5d8](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1b4d5d8dbe8cba26fbb84a8f847fc22b1a1dc096)) +* Event and function signatures from matched_on ([#198](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/198)) ([cdd9f1d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/cdd9f1d7333ee2f3ef9c476a08e918388b3c35f0)) +* Monitor match template var signature collission (breaking) ([#203](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/203)) ([283b724](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/283b724a88f45f82c3c5fc81742a564b70909d45)) +* remove the create-github-app-token action from the scorecard workflow ([#174](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/174)) ([48ca0b1](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/48ca0b106dbee225b5d4824013c2a28b773b23b3)) +* Running duplicate tests ([#181](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/181)) ([ad0f741](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ad0f741608b2719a1db16dd22bf8c457e5814f86)) + +[Changes][v0.2.0] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-monitor/releases/tag/v0.1.0) - 2025-04-07 + +## 0.1.0 (2025-04-07) + + +### 🚀 Features + +* add block tracker ([#11](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/11)) ([1d4d117](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d4d117aab56e2c31c0747d6bf681fe60b2d8b10)) +* Add CLA assistant bot ([#107](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/107)) ([47e490e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/47e490e4a5657a48bc60f85c38d72aca16334ac0)) +* Add client rpc pool ([#75](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/75)) ([28cd940](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/28cd940a8aea5c97fb15a4ca0d415debaa2864b1)) +* add email support ([#7](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/7)) ([decb56d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/decb56d45d3f1000346c24e137d1a5d952c4a9dd)) +* Add endpoint rotation manager ([#69](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/69)) ([454a630](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/454a630cf92c305ea5d9254b211a7b60abf8804d)) +* Add new error context ([#77](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/77)) ([612bb76](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/612bb76b9c8e9a470fc68685c2f06481663a9474)) +* Add rc workflow file ([#156](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/156)) ([8907591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/890759186570a64a9d0b0ef4dc9e512d0110d7a0)) +* Add support for webhook, telegram, discord notifications ([#65](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/65)) ([829967d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/829967da45062dc22ffb0cb3376e68101a46b3e9)) +* Plat 6187 write metrics to prometheus ([#95](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/95)) ([2dc08d5](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2dc08d51670834f453498299937debfca67fa1b7)) +* PLAT-6148 Adding post filter to monitor model ([#58](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/58)) ([920a0bf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/920a0bf27953b67eb722d17d5ebf50b51237d4d4)) +* PLAT-6151 Integrate custom script execution with notification service ([#79](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/79)) ([bd5f218](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/bd5f218507dfc30bd4b2182077e2997cf04b8877)) +* PLAT-6477 Adding rust toolchain file ([#117](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/117)) ([ea6fb1e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ea6fb1ee6bba46cfa66a0c81665e17930bbbed93)) +* Separate code test coverage into different categories of tests ([#84](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/84)) ([a3ad89c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/a3ad89cdcf0bab5883af7ec36b854fedc2f060cd)) +* spawn block-watcher per network ([#4](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/4)) ([d7a19ec](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d7a19ec57344e4fb28dffc6f2025e809d0f5d946)) + + +### 🐛 Bug Fixes + +* Add thread flag when running tests in CI ([#41](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/41)) ([4312669](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/4312669d8da84f5cf7e7817b10c377fe3a6992af)) +* Adjust netlify toml settings ([#47](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/47)) ([af9fe55](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/af9fe553a92cfc47a306a7dcfc43be0b2257f835)) +* Docs link ([#106](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/106)) ([f12d95d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f12d95d85ad9230bece0342c39cb5c3c1cd62832)) +* Documentation name for antora ([#105](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/105)) ([5a8c4bd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5a8c4bd8315e62bb2dedb066f6b6bfcaa09c2d37)) +* Fix cargo lock ([#110](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/110)) ([c440ca4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/c440ca43542e919cd473a7d533b0820cf5474d3e)) +* Fix cargo lock file ([#116](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/116)) ([1bd3658](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1bd3658ab507c2dde90a2132b6eaec6d849e0e3c)) +* Fix the codecov yaml syntax ([#97](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/97)) ([fcafcbf](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/fcafcbf5765014a65c3f2c8718ee0f24a4531ebe)) +* fixed check ([1d36aaa](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/1d36aaa63ca12b4a660ec7e7bfcb18f722d8adf2)) +* Linter ([b0e27ca](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b0e27ca21f8e39b3a3c16d356df00dfcd0a868e5)) +* Netlify integration & Release workflow doc ([#162](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/162)) ([3b77025](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3b7702569e7c5828ca55fb67f7eec2672bf768b2)) +* PLAT-6301 Remove logic for checking file descriptors open and fixing readme ([#90](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/90)) ([71dbd24](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/71dbd24a9ba5ab4c37cf4be432a4614c2e68166b)) +* Reduce USDC ABI and fix trailing comma ([#62](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/62)) ([92e343c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/92e343c09dc2da565912b6cd5bc83fbdc591cdb5)) +* rename docker binaries ([#2](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/2)) ([78d438a](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/78d438a1ca4931651d3ca106c5dbda1ea1357574)) +* rename import ([#6](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/6)) ([745e591](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/745e591faba06f557b2f6a091434250ed559df6e)) +* trigger execution order ([#24](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/24)) ([26581fe](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/26581fec9ec1078ea4284fd6b43509616c66ad64)) +* Variable resolving ([#49](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/49)) ([e26d173](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e26d17314e9b2e78c0772a46f3139da70c6ca144)) + + +### 📚 Documentation + +* Add Antora documentation ([#48](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/48)) ([2f737c4](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2f737c4c040090bd3acd0af90d3f24045b8ff173)) +* add link to contributing in README ([#33](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/33)) ([5abb548](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5abb548c199f3a033860b027461e5fb3cd60e565)) +* Add list of RPC calls ([#67](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/67)) ([aae9577](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/aae9577f4e011eaca12adb7997bf5fd28a558f83)) +* Add quickstart guide ([#56](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/56)) ([e422353](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/e422353873335540afce5a9a5702c786c71eea75)) +* add readme documentation ([#8](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/8)) ([357006d](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/357006d98f6cc8d160920e702dc78662008d39a3)) +* add rust documentation ([#5](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/5)) ([3832570](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/3832570adf4854279fcda215fbbba5eb0d5396a1)) +* Adding node to docker images - custom scripts ([#76](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/76)) ([da6516c](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/da6516c6f3afccb297cb1c1251f673e02ceaeaa5)) +* Custom scripts documentation to antora and readme ([#91](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/91)) ([2b81058](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/2b81058f810e6b4d18a2c79e96002fb77890e9e0)) +* Fix quickstart closing tag ([#118](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/118)) ([d360379](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/d3603796f39c15ed5247efab90ab95c5537c76d2)) +* Fix telegram channel ([9899259](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/98992599ab8998113b6202781787a48ce0aab3db)) +* Implement README feedback ([#50](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/50)) ([5b6ba64](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/5b6ba6419a06b9abd60412fa02b09da2a416e38c)) +* Improve docs ([#100](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/100)) ([9586a25](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/9586a253f2a76993bbf82d4834b37863edabab60)) +* improve readme section and examples ([#9](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/9)) ([009db37](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/009db3719e1be03120733755ade3c1c45e13f8a5)) +* Improvements to custom scripts ([#98](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/98)) ([69047d9](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/69047d90a2fe057446f7c1b3f3526ab31bc6afcb)) +* Re-order example and fix test flag ([#52](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/52)) ([f90b6df](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/f90b6df73ef7a6040eab59d71402b34877c88fc5)) +* Readability improvements ([#109](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/109)) ([8e23389](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/8e23389ea0dcb3b221227a6cddd17de39603acbb)) +* Update project structure ([#101](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/101)) ([207edd2](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/207edd28f3fb0a805d40d6ba9109abe9e6553d23)) +* Update README and antora docs ([#57](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/57)) ([6a2299e](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/6a2299e0c41052ef9523aec1aa6f5852990e9179)) +* Update RPC documentation after client pool feature ([#96](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/96)) ([ade2811](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/ade2811431c07c6b46730cbce5e357934df14cd5)) +* Update telegram channel in docs ([#99](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/99)) ([9899259](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/98992599ab8998113b6202781787a48ce0aab3db)) +* Updated Quickstart guide ([#108](https://github.com/OpenZeppelin/openzeppelin-monitor/issues/108)) ([b81c7cd](https://github.com/OpenZeppelin/openzeppelin-monitor/commit/b81c7cd22143a7d2854ef496ab59e114d70c360f)) + +[Changes][v0.1.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.2.0...v1.0.0 +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/compare/v0.1.0...v0.2.0 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-monitor/tree/v0.1.0 diff --git a/docs/content/monitor/contribution.mdx b/docs/content/monitor/contribution.mdx new file mode 100644 index 00000000..08416ea1 --- /dev/null +++ b/docs/content/monitor/contribution.mdx @@ -0,0 +1,335 @@ +--- +title: Contribution Guidelines +--- + +Welcome to the OpenZeppelin Monitor project! We appreciate your interest in contributing. This guide outlines the requirements and processes for contributing to the project. + +## Getting Started + +The OpenZeppelin Monitor project has comprehensive contribution guidelines documented in the [`CONTRIBUTING.md`](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) file. This documentation provides a summary of key requirements, but for complete details including GitHub workflow, labeling guidelines, and advanced topics, please refer to the full CONTRIBUTING.md file. + + + +For the most up-to-date and comprehensive contribution guidelines, always refer to the [CONTRIBUTING.md](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) file in the repository. + + +## Key Requirements + +### Contributor License Agreement (CLA) + +***You must sign the CLA before contributing.*** The CLA process is automated through GitHub workflows that check and label PRs accordingly. + +* All contributors must complete the CLA process +* The CLA bot will automatically check your PR status +* PRs cannot be merged without a signed CLA + +### Signed Commits + +***All commits must be GPG-signed*** as a security requirement. + +* Configure GPG signing for your commits +* Unsigned commits will not be accepted +* This helps ensure code integrity and authenticity + +## Development Environment Setup + +### Prerequisites + +Before contributing, ensure you have: + +* ***Rust 2021 edition*** - Required for development +* ***Git*** - For version control +* ***Python/pip*** - For pre-commit hooks + +### System Dependencies (Linux) + +For Ubuntu 22.04+ or Debian-based systems (both x86 and ARM64 architectures), install required packages: + +***Note:*** Python 3.9+ is required for pre-commit hooks compatibility. + +```bash +# Install required packages directly +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + git \ + pkg-config \ + libssl-dev \ + libffi-dev \ + libyaml-dev \ + python3 \ + python3-venv \ + python3-pip +``` + +Or use the provided system package script (automatically ensures Python 3.9+ compatibility): + +```bash +chmod +x ./scripts/linux/sys_pkgs_core.sh +chmod +x ./scripts/linux/sys_pkgs_dev.sh +# Installs required packages and ensures compatible Python version +./scripts/linux/sys_pkgs_core.sh // For runtime dependencies only +./scripts/linux/sys_pkgs_dev.sh // For Python/dev dependencies (calls core script) +``` + +### Initial Setup + +```bash +# Clone and set up the repository +git clone https://github.com/openzeppelin/openzeppelin-monitor +cd openzeppelin-monitor + +# Build the project +cargo build + +# Set up environment variables +cp .env.example .env +``` + +### Running Tests + +```bash +# All tests +RUST_TEST_THREADS=1 cargo test + +# Integration tests +RUST_TEST_THREADS=1 cargo test integration + +# Property-based tests +RUST_TEST_THREADS=1 cargo test properties +``` + +## Development Workflow + +### 1. Pre-commit Hooks + +***Required for code quality checks*** including `rustfmt`, `clippy`, and commit message validation. + +* Install and configure pre-commit hooks +* Automatic formatting and linting checks +* Commit message format validation + +#### Installing Pre-commit Hooks + +Install and configure pre-commit hooks to ensure code quality: + +```bash +# Install pre-commit (use pipx for global installation if preferred) +pip install pre-commit + +# Install and configure hooks for commit-msg, pre-commit, and pre-push +pre-commit install --install-hooks -t commit-msg -t pre-commit -t pre-push +``` + + + +If you encounter issues with pip install, you may need to install [pipx](https://github.com/pypa/pipx) for global installation. Use `pipx install pre-commit` instead. + + +The pre-commit hooks will automatically run on every commit and push, checking for: +* Code formatting with `rustfmt` +* Linting with `clippy` +* Commit message format validation +* Other code quality checks + +### 2. GitHub Workflow + +#### Fork and Clone + +1. ***Fork the repository*** on GitHub +2. ***Clone your fork*** locally: + +```bash +# Set up your working directory +export working_dir="${HOME}/repos" +export user= + +# Clone your fork +mkdir -p $working_dir +cd $working_dir +git clone https://github.com/$user/openzeppelin-monitor.git + +# Add upstream remote +cd openzeppelin-monitor +git remote add upstream https://github.com/openzeppelin/openzeppelin-monitor.git +git remote set-url --push upstream no_push +``` + +#### Branch Management + +* Create feature branches from an up-to-date main branch +* Regularly sync with upstream +* Use descriptive branch names + +```bash +# Keep main updated +git fetch upstream +git checkout main +git rebase upstream/main + +# Create feature branch +git checkout -b feature/your-feature-name + +# Keep branch updated +git fetch upstream +git rebase upstream/main +``` + + + +Use `git rebase` instead of `git pull` to avoid merge commits and maintain a clean history. + + +### 3. Pull Request Process + +#### Creating a Pull Request + +1. ***Push your changes*** to your fork: + + ```bash + git push -f origin feature/your-feature-name + ``` +2. ***Create a Pull Request*** on GitHub +3. ***Add appropriate labels*** (see Labeling Guidelines below) +4. ***Include a clear description*** of your changes + +#### Best Practices for PRs + +* Write clear and meaningful commit messages +* Include `fixes #123` in PR body (not commit messages) to auto-close issues +* Break large changes into smaller, logical commits +* Ensure all tests pass +* Include sufficient information for reviewers + +## Code Standards + +### Rust Standards + +Rust API Guidelines: + +* Format code with `rustfmt` +* Pass all `clippy` linting checks +* Follow Rust naming conventions + +```bash +# Format code +cargo fmt + +# Check linting +cargo clippy --all-targets --all-features + +# Run tests +RUST_TEST_THREADS=1 cargo test +``` + +### Testing Requirements + +***All contributions must pass existing tests*** and include new tests when applicable: + +* Write unit tests for new functionality +* Add integration tests for complex features +* Ensure all tests pass before submitting +* Maintain or improve code coverage + +For detailed testing information, see the [Testing Guide](/monitor/testing). + +### Commit Message Format + +***Follow conventional commit format*** with types like: + +* `feat:` - New features +* `fix:` - Bug fixes +* `docs:` - Documentation changes +* `test:` - Test additions or modifications +* `refactor:` - Code refactoring +* `chore:` - Maintenance tasks + +## Issue and Pull Request Labeling + +The project uses a structured labeling system to organize issues and PRs. Key label categories include: + +### Area Labels (`A-`) +* `A-arch` - Architectural concerns +* `A-blocks` - Block processing +* `A-clients` - Blockchain clients +* `A-configs` - Configuration issues +* `A-docs` - Documentation +* `A-tests` - Testing + +### Type Labels (`T-`) +* `T-bug` - Bug reports +* `T-feature` - New features +* `T-task` - General tasks +* `T-documentation` - Documentation updates + +### Priority Labels (`P-`) +* `P-high` - Critical tasks +* `P-medium` - Important tasks +* `P-low` - Low priority + +### Difficulty Labels (`D-`) +* `D-easy` - Beginner-friendly +* `D-medium` - Intermediate +* `D-hard` - Complex issues + + + +For complete labeling guidelines and all available labels, see the [labeling section](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md#issue-and-pull-request-labeling-guidelines) in CONTRIBUTING.md. + + +## Code Review Process + +### Review Requirements + +* All PRs require review and approval +* At least one Reviewer and one Approver must approve +* Address all review comments before merging +* Commits are automatically squashed when merging + +### Review Guidelines + +Reviewers should focus on: + +1. ***Soundness*** - Is the idea behind the contribution sound? +2. ***Architecture*** - Is the contribution architected correctly? +3. ***Polish*** - Is the contribution polished and ready? + +### Getting Reviews + +If your PR isn’t getting attention: + +* Contact the team on [Telegram](https://t.me/openzeppelin_tg/4) +* Ensure your PR has appropriate labels +* Keep PRs focused and reasonably sized + +## Security + +* Follow the [Security Policy](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/SECURITY.md) +* Report security vulnerabilities through the proper channels +* Never commit sensitive information or credentials + +## Community Guidelines + +### Code of Conduct + +Contributors must follow the [Code of Conduct](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CODE_OF_CONDUCT.md), which: + +* Establishes standards for respectful collaboration +* Outlines enforcement procedures +* Promotes an inclusive environment + +## Getting Help + +### Community Support + +* ***GitHub Discussions***: For questions and community interaction +* ***Issues***: For bug reports and feature requests +* ***Telegram***: [Join our community chat](https://t.me/openzeppelin_tg/4) +* ***Good First Issues***: [Find beginner-friendly issues](https://github.com/openzeppelin/openzeppelin-monitor/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) + +### Additional Resources + +* ***Full CONTRIBUTING.md***: [Complete contribution guidelines](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/CONTRIBUTING.md) +* ***User Documentation***: [Monitor documentation](https://docs.openzeppelin.com/monitor) +* ***OpenZeppelin Website***: [Main website](https://openzeppelin.com/) diff --git a/docs/content/monitor/error.mdx b/docs/content/monitor/error.mdx new file mode 100644 index 00000000..0bc23a81 --- /dev/null +++ b/docs/content/monitor/error.mdx @@ -0,0 +1,167 @@ +--- +title: Error Handling +--- + +## Overview + +The OpenZeppelin Monitor uses a structured error handling system that provides rich context and tracing capabilities across service boundaries. Let’s start with a real-world example of how errors flow through our system. + +## Error Flow Example + +Let’s follow how an error propagates through our blockchain monitoring system: + +**Low-level Transport (`endpoint_manager.rs`)** + +```rust +// Creates basic errors with specific context +async fn send_raw_request(...) -> Result { + let response = client.post(...) + .await + .map_err(|e| anyhow::anyhow!("Failed to send request: {}", e))?; + + if !status.is_success() { + return Err(anyhow::anyhow!("HTTP error {}: {}", status, error_body)); + } +} +``` + +**Client Layer (`evm/client.rs`)** + +```rust +// Adds business context to low-level errors +async fn get_transaction_receipt(...) -> Result { + let response = self.alloy_client + .send_raw_request(...) + .await + .with_context(|| format!("Failed to get transaction receipt: {}", tx_hash))?; + + if receipt_data.is_null() { + return Err(anyhow::anyhow!("Transaction receipt not found")); + } +} +``` + +**Filter Layer (`evm/filter.rs`)** + +```rust +// Converts to domain-specific errors +async fn filter_block(...) -> Result, FilterError> { + let receipts = match futures::future::join_all(receipt_futures).await { + Ok(receipts) => receipts, + Err(e) => { + return Err(FilterError::network_error( + format!("Failed to get transaction receipts for block {}", block_num), + Some(e.into()), + None, + )); + } + }; +} +``` + +When this error occurs, it produces the following log: + +```text +ERROR filter_block: openzeppelin_monitor::utils::error: Error occurred, + error.message: Failed to get transaction receipts for block 15092829, + error.trace_id: a464d73c-5992-4cb5-a002-c8d705bfef8d, + error.timestamp: 2025-03-14T09:42:03.412341+00:00, + error.chain: Failed to get receipt for transaction 0x7722194b65953085fe1e9ec01003f1d7bdd6258a0ea5c91a59da80419513d95d + Caused by: HTTP error 429 Too Many Requests: {"code":-32007,"message":"[Exceeded request limit per second]"} + network: ethereum_mainnet +``` + +## Error Structure + +### Error Context +Every error in our system includes detailed context information: + +```rust +pub struct ErrorContext { + /// The error message + pub message: String, + /// The source error (if any) + pub source: Option>, + /// Unique trace ID for error tracking + pub trace_id: String, + /// Timestamp when the error occurred + pub timestamp: DateTime, + /// Optional key-value metadata + pub metadata: HashMap, +} +``` + +### Domain-Specific Error Types + +| Module | Error Type | Description | +| --- | --- | --- | +| `**Configuration**` | `ConfigError` | * `ValidationError` - Configuration validation failures * `ParseError` - Configuration parsing issues * `FileError` - File system related errors * `Other` - Unclassified errors | +| `**Security**` | `SecurityError` | * `ValidationError` - Security validation failures * `ParseError` - Security data parsing issues * `NetworkError` - Security service connectivity issues * `Other` - Unclassified security-related errors | +| `**Blockchain Service**` | `BlockChainError` | * `ConnectionError` - Network connectivity issues * `RequestError` - Malformed requests or invalid responses * `BlockNotFound` - Requested block not found * `TransactionError` - Transaction processing failures * `InternalError` - Internal client errors * `ClientPoolError` - Client pool related issues * `Other` - Unclassified errors | +| `**Block Watcher Service**` | `BlockWatcherError` | * `SchedulerError` - Block watching scheduling issues * `NetworkError` - Network connectivity problems * `ProcessingError` - Block processing failures * `StorageError` - Storage operation failures * `BlockTrackerError` - Block tracking issues * `Other` - Unclassified errors | +| `**Filter Service**` | `FilterError` | * `BlockTypeMismatch` - Block type validation failures * `NetworkError` - Network connectivity issues * `InternalError` - Internal processing errors * `Other` - Unclassified errors | +| `**Notification Service**` | `NotificationError` | * `NetworkError` - Network connectivity issues * `ConfigError` - Configuration problems * `InternalError` - Internal processing errors * `ExecutionError` - Script execution failures * `Other` - Unclassified errors | +| `**Repository**` | `RepositoryError` | * `ValidationError` - Data validation failures * `LoadError` - Data loading issues * `InternalError` - Internal processing errors * `Other` - Unclassified errors | +| `**Script Utils**` | `ScriptError` | * `NotFound` - Resource not found errors * `ExecutionError` - Script execution failures * `ParseError` - Script parsing issues * `SystemError` - System-level errors * `Other` - Unclassified errors | +| `**Trigger Service**` | `TriggerError` | * `NotFound` - Resource not found errors * `ExecutionError` - Trigger execution failures * `ConfigurationError` - Trigger configuration issues * `Other` - Unclassified errors | +| `**Monitor Executor**` | `MonitorExecutionError` | * `NotFound` - Resource not found errors * `ExecutionError` - Monitor execution failures * `Other` - Unclassified errors | + +## Error Handling Guidelines + +### When to Use Each Pattern + +| Scenario | Approach | +| --- | --- | +| Crossing Domain Boundaries | Convert to domain-specific error type using custom error constructors | +| Within Same Domain | Use `.with_context()` to add information while maintaining error type | +| External API Boundaries | Always convert to your domain’s error type to avoid leaking implementation details | + +### Error Creation Examples + +**Creating a Configuration Error without a source** + +```rust +let error = ConfigError::validation_error( + "Invalid network configuration", + None, + Some(HashMap::from([ + ("network", "ethereum"), + ("field", "rpc_url") + ])) +); +``` + +**Creating a Configuration Error with a source** + +```rust + +let io_error = std::io::Error::new(std::io::ErrorKind::Other, "Failed to read file"); + +let error = ConfigError::validation_error( + "Invalid network configuration", + Some(io_error.into()), + None +); +``` + +### Tracing with #[instrument] + +```rust +#[instrument(skip_all, fields(network = %_network.slug))] +async fn filter_block( + &self, + client: &T, + _network: &Network, + block: &BlockType, + monitors: &[Monitor], +) -> Result, FilterError> { + tracing::debug!("Processing block {}", block_number); + // ... +} +``` + +Key aspects: + +1. `skip_all` - Skips automatic instrumentation of function parameters for performance +2. `fields(...)` - Adds specific fields we want to track (like network slug) +3. `tracing::debug!` - Adds debug-level spans for important operations diff --git a/docs/content/monitor/index.mdx b/docs/content/monitor/index.mdx new file mode 100644 index 00000000..9078ae07 --- /dev/null +++ b/docs/content/monitor/index.mdx @@ -0,0 +1,1533 @@ +--- +title: OpenZeppelin Monitor +--- + +## Overview + +In the rapidly evolving world of blockchain technology, effective monitoring is crucial for ensuring security and performance. OpenZeppelin Monitor is a blockchain monitoring service that watches for specific on-chain activities and triggers notifications based on configurable conditions. The service offers multi-chain support with configurable monitoring schedules, flexible trigger conditions, and an extensible architecture for adding new chains. + +### Key Capabilities + +* ***Real-time Monitoring***: Watch blockchain networks in real-time for specific events and transactions +* ***Smart Filtering***: Use flexible expressions to define exactly what you want to monitor +* ***Multi-notification Support***: Send alerts via Slack, Discord, Email, Telegram, Webhooks, or custom scripts +* ***Configurable Scheduling***: Set custom monitoring schedules using cron expressions +* ***Data Persistence***: Store monitoring data and resume from checkpoints +* ***Extensible Architecture***: Easy to add support for new blockchains and notification types + +### Supported Networks + +* ***EVM-Compatible Networks*** +* ***Stellar*** + +### Notification Channels + +* ***Slack*** - Send formatted messages to Slack channels +* ***Discord*** - Post alerts to Discord channels via webhooks +* ***Email*** - Send email notifications with SMTP support +* ***Telegram*** - Send messages to Telegram chats via bot API +* ***Webhooks*** - Send HTTP requests to custom endpoints +* ***Custom Scripts*** - Execute Python, JavaScript, or Bash scripts + + + + +To get started immediately, see [Quickstart](/monitor/quickstart). + + + +## Installation + +### Prerequisites + +* Use ***Rust 2021 edition***, version `1.86` or later. +* ***Docker*** (optional, for containerized deployment) + +#### System Dependencies (Linux) + +For Ubuntu 22.04+ or Debian-based systems (both x86 and ARM64 architectures), install required packages: + +***Note:*** Python 3.9+ is required for pre-commit hooks compatibility. + +```bash +# Install required packages directly +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + git \ + pkg-config \ + libssl-dev \ + libffi-dev \ + libyaml-dev \ + python3 \ + python3-venv \ + python3-pip +``` + +Or use the provided system package script (automatically ensures Python 3.9+ compatibility): + +```bash +chmod +x ./scripts/linux/sys_pkgs_core.sh +chmod +x ./scripts/linux/sys_pkgs_dev.sh +# Installs required packages and ensures compatible Python version +./scripts/linux/sys_pkgs_core.sh // For runtime dependencies only +./scripts/linux/sys_pkgs_dev.sh // For Python/dev dependencies (calls core script) +``` + +### Local Installation + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Build the application:*** + + ```bash + cargo build --release + ``` +3. ***Move binary to project root:*** + + ```bash + mv ./target/release/openzeppelin-monitor . + ``` +4. ***Verify installation:*** + + ```bash + ./openzeppelin-monitor --help + ``` +5. ***View available options:*** + + ```bash + ./openzeppelin-monitor --help + + # Enable logging to file + ./openzeppelin-monitor --log-file + + # Enable metrics server + ./openzeppelin-monitor --metrics + + # Validate configuration files without starting the service + ./openzeppelin-monitor --check + ``` + +### Docker Installation + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Set up environment:*** + + ```bash + cp .env.example .env + # Edit .env file with your configuration + ``` +3. ***Start with Docker Compose:*** + + ```bash + cargo make docker-compose-up + ``` + +#### Metrics Configuration + +The metrics server, Prometheus, and Grafana can be enabled by setting `METRICS_ENABLED=true` in your `.env` file. + +You can start services directly with Docker Compose: + +```bash +# without metrics profile ( METRICS_ENABLED=false by default ) +docker compose up -d + +# With metrics enabled +docker compose --profile metrics up -d +``` + +To view prometheus metrics in a UI, you can use `http://localhost:9090` on your browser. + +To view grafana dashboard, you can use `http://localhost:3000` on your browser. + +By default, predefined metrics within a dashboard is populated in grafana. + +### Configuration Guidelines + +#### Recommended File Naming Conventions + +* Network configurations: `_.json` + * Example: `ethereum_mainnet.json`, `stellar_testnet.json` + * Should match the `slug` property inside the file +* Monitor configurations: `__monitor.json` + * Example: `usdc_transfer_monitor.json`, `dai_liquidation_monitor.json` + * Referenced by monitors using their `name` property +* Trigger configurations: `_.json` + * Example: `slack_notifications.json`, `email_alerts.json` + * Individual triggers referenced by their configuration key + +#### Configuration References + +* Monitor, network, and trigger names ***must be unique*** across all configurations files +* Monitor’s `networks` array must contain valid network `slug` values from network configuration files +* Monitor’s `triggers` array must contain valid trigger configuration keys +* Example valid references: + + ```json + // networks/ethereum_mainnet.json + { + "slug": "ethereum_mainnet", + ... + } + + // triggers/slack_notifications.json + { + "large_transfer_slack": { + ... + } + } + + // monitors/usdc_transfer_monitor.json + { + "networks": ["ethereum_mainnet"], + "triggers": ["large_transfer_slack"], + ... + } + + ``` + + + + +Ensure all referenced slugs and trigger keys exist in their respective configuration files. The monitor will fail to start if it cannot resolve these references. + + + +#### Safe Protocol Guidelines + +The monitor implements protocol security validations across different components and will issue warnings when potentially insecure configurations are detected. While insecure protocols are not blocked, we strongly recommend following these security guidelines: + +##### Network Protocols + +###### RPC URLs +* **HTTPS Recommended**: Using `https://` for RPC endpoints is strongly recommended +* **WSS Recommended**: For WebSocket connections, `wss://` (secure WebSocket) is strongly recommended +* **Warning**: Using `http://` or `ws://` will trigger security warnings as they transmit data unencrypted + +##### Notification Protocols + +###### Webhook Notifications +* **HTTPS Recommended**: URLs should use HTTPS protocol +* **Authentication Recommended**: Including either: + * `X-API-Key` header + * `Authorization` header +* **Optional Secret**: Can include a secret for HMAC authentication + * When a secret is provided, the monitor will: + * Generate a timestamp in milliseconds + * Create an HMAC-SHA256 signature of the payload and timestamp + * Add the signature in the `X-Signature` header + * Add the timestamp in the `X-Timestamp` header + * The signature is computed as: `HMAC-SHA256(secret, payload + timestamp)` +* **Warning**: Non-HTTPS URLs or missing authentication headers will trigger security warnings + +###### Slack Notifications +* **HTTPS Recommended**: Webhook URLs should start with `https://hooks.slack.com/` +* **Warning**: Non-HTTPS URLs will trigger security warnings + +###### Discord Notifications +* **HTTPS Recommended**: Webhook URLs should start with `https://discord.com/api/webhooks/` +* **Warning**: Non-HTTPS URLs will trigger security warnings + +###### Telegram Notifications +* ***Protocol:*** `POST` request with a `application/json` payload to the `sendMessage` method. +* ***Endpoint:*** `https://api.telegram.org/bot/sendMessage` +* ***Security:*** + * ***HTTPS Required:*** The API endpoint uses HTTPS. + * Authentication is handled via the ***Bot Token*** in the URL. Keep this token secure. +* ***Formatting:*** Messages are sent with `parse_mode` set to `MarkdownV2`. Special characters in the message title and body are automatically escaped to prevent formatting errors. + +###### Email Notifications +* **Secure Ports Recommended**: The following ports are considered secure: + * 465: SMTPS (SMTP over SSL) + * 587: SMTP with STARTTLS + * 993: IMAPS (IMAP over SSL) +* **Warning**: Using other ports will trigger security warnings +* **Valid Format**: Email addresses must follow RFC 5322 format + +###### Notifications Retry Policy + +Following notification protocols support retry policies: + +* Slack +* Discord +* Telegram +* Webhook +* Email + +Default retry policy is using exponential backoff with the following parameters: +| | | | +| --- | --- | --- | +| Parameter | Default Value | Description | +| `max_retries` | `3` | Maximum number of retries before giving up | +| `base_for_backoff` | `2` | Base duration for exponential backoff calculations in seconds | +| `initial_backoff` | `250` | Initial backoff duration in milliseconds | +| `max_backoff` | `10` | Maximum backoff duration in seconds | +| `jitter` | `Full` | Jitter strategy to apply to the backoff duration, currently supports `Full` and `None` | + +These parameters can be overridden by providing custom `RetryConfig` struct in `retry_policy` field in trigger configuration. + +##### Script Security + +###### File Permissions (Unix Systems) +* **Restricted Write Access**: Script files should not have overly permissive write permissions +* **Recommended Permissions**: Use `644` (`rw-r--r--`) for script files +* **Warning**: Files with mode `022` or more permissive will trigger security warnings + +**Example Setting Recommended Permissions** + +```bash +chmod 644 ./config/filters/my_script.sh +``` + +#### Secret Management + +The monitor implements a secure secret management system with support for multiple secret sources and automatic memory zeroization. + +##### Secret Sources + +The monitor supports three types of secret sources: + +* **Plain Text**: Direct secret values (wrapped in `SecretString` for secure memory handling) +* **Environment Variables**: Secrets stored in environment variables +* **Hashicorp Cloud Vault**: Secrets stored in Hashicorp Cloud Vault + +##### Security Features + +* **Automatic Zeroization**: Secrets are automatically zeroized from memory when no longer needed +* **Type-Safe Resolution**: Secure handling of secret resolution with proper error handling +* **Configuration Support**: Serde support for configuration files + +##### Configuration + +Secrets can be configured in the JSON files using the following format: + +```json +{ + "type": "Plain", + "value": "my-secret-value" +} +``` + +```json +{ + "type": "Environment", + "value": "MY_SECRET_ENV_VAR" +} +``` + +```json +{ + "type": "HashicorpCloudVault", + "value": "my-secret-name" +} +``` + +##### Hashicorp Cloud Vault Integration + +To use Hashicorp Cloud Vault, configure the following environment variables: + +| Environment Variable | Description | +| --- | --- | +| `HCP_CLIENT_ID` | Hashicorp Cloud Vault client ID | +| `HCP_CLIENT_SECRET` | Hashicorp Cloud Vault client secret | +| `HCP_ORG_ID` | Hashicorp Cloud Vault organization ID | +| `HCP_PROJECT_ID` | Hashicorp Cloud Vault project ID | +| `HCP_APP_NAME` | Hashicorp Cloud Vault application name | + +##### Best Practices + +* Use environment variables or vault for production secrets +* Avoid storing plain text secrets in configuration files +* Use appropriate access controls for vault secrets +* Monitor vault access patterns for suspicious activity + +#### Basic Configuration + +* Set up environment variables: + +Copy the example environment file and update values according to your needs + +```bash +cp .env.example .env +``` + +This table lists the environment variables and their default values. + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `RUST_LOG` | `info` | `info, debug, warn, error, trace` | Log level. | +| `LOG_MODE` | `stdout` | `stdout, file` | Write logs either to console or to file. | +| `LOG_DATA_DIR` | `logs/` | `` | Directory to write log files on host. | +| `MONITOR_DATA_DIR` | `null` | `` | Persist monitor data between container restarts. | +| `LOG_MAX_SIZE` | `1073741824` | `` | Size after which logs needs to be rolled. Accepts both raw bytes (e.g., "1073741824") or human-readable formats (e.g., "1GB", "500MB"). | +| `METRICS_ENABLED` | `false` | `true`, `false` | Enable metrics server for external tools to scrape metrics. | +| `METRICS_PORT` | `8081` | `` | Port to use for metrics server. | +| `HCP_CLIENT_ID` | - | `` | Hashicorp Cloud Vault client ID for secret management. | +| `HCP_CLIENT_SECRET` | - | `` | Hashicorp Cloud Vault client secret for secret management. | +| `HCP_ORG_ID` | - | `` | Hashicorp Cloud Vault organization ID for secret management. | +| `HCP_PROJECT_ID` | - | `` | Hashicorp Cloud Vault project ID for secret management. | +| `HCP_APP_NAME` | - | `` | Hashicorp Cloud Vault application name for secret management. | +* Copy and configure some example files: + +```bash +# EVM Configuration +cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json +cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json + +# Stellar Configuration +cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json +cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json + +# Notification Configuration +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json + +# Filter Configuration +cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh +cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh +``` +### Command Line Options + +The monitor supports several command-line options for configuration and control: + +| **Option** | **Default** | **Description** | +| --- | --- | --- | +| `**--log-file**` | `false` | Write logs to file instead of stdout | +| `**--log-level**` | `info` | Set log level (trace, debug, info, warn, error) | +| `**--log-path**` | `logs/` | Path to store log files | +| `**--log-max-size**` | `1GB` | Maximum log file size before rolling | +| `**--metrics-address**` | `127.0.0.1:8081` | Address to start the metrics server on | +| `**--metrics**` | `false` | Enable metrics server | +| `**--monitor-path**` | - | Path to the monitor to execute (for testing) | +| `**--network**` | - | Network to execute the monitor for (for testing) | +| `**--block**` | - | Block number to execute the monitor for (for testing) | +| `**--check**` | `false` | Validate configuration files without starting the service | + +## Data Storage Configuration + +The monitor uses file-based storage by default. + +### File Storage + +When `store_blocks` is enabled in the network configuration, the monitor stores: + +* Processed blocks: `./data/_blocks_.json` +* Missed blocks: `./data/_missed_blocks.txt` (used to store missed blocks) + +The content of the `missed_blocks.txt` file may help to determine the right `max_past_blocks` value based on the network’s block time and the monitor’s cron schedule. + +Additionally, the monitor will always store: + +* Last processed block: `./data/_last_block.txt` (enables resuming from last checkpoint) + +## Configuration Files + +### Network Configuration + +A Network configuration defines connection details and operational parameters for a specific blockchain network, supporting both EVM and Stellar-based chains. + +**Example Network Configuration** + +```json +{ + "network_type": "Stellar", + "slug": "stellar_mainnet", + "name": "Stellar Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "https://soroban.stellar.org" + }, + "weight": 100 + } + ], + "network_passphrase": "Public Global Stellar Network ; September 2015", + "block_time_ms": 5000, + "confirmation_blocks": 2, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 20, + "store_blocks": true +} +``` + +#### Available Fields + +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**network_type**` | `String` | Type of blockchain (**"EVM"** or **"Stellar"**) | +| `**slug**` | `String` | **Required** - **_Unique_** identifier for the network | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable network name | +| `**rpc_urls**` | `Array[Object]` | List of RPC endpoints with weights for load balancing | +| `**chain_id**` | `Number` | Network chain ID (**EVM only**) | +| `**network_passphrase**` | `String` | Network identifier (**Stellar only**) | +| `**block_time_ms**` | `Number` | Average block time in milliseconds | +| `**confirmation_blocks**` | `Number` | Number of blocks to wait for confirmation | +| `**cron_schedule**` | `String` | Monitor scheduling in cron format | +| `**max_past_blocks**` | `Number` | Maximum number of past blocks to process | +| `**store_blocks**` | `Boolean` | Whether to store processed blocks (defaults output to `./data/` directory) | + +#### Important Considerations + +* We strongly recommend using private RPC providers for improved reliability. + +### Trigger Configuration + +A Trigger defines actions to take when monitored conditions are met. Triggers can send notifications, make HTTP requests, or execute scripts. + +**Example Trigger Configuration** + +```json +{ + "evm_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "https://hooks.slack.com/services/A/B/C" + }, + "message": { + "title": "${monitor.name} triggered", + "body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog" + } + } + }, + "stellar_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "environment", + "value": "SLACK_WEBHOOK_URL" + }, + "message": { + "title": "large_transfer_usdc_slack triggered", + "body": "${monitor.name} triggered because of a large transfer of ${functions.0.args.amount} USDC to ${functions.0.args.to} | https://stellar.expert/explorer/testnet/tx/${transaction.hash}" + } + } + } +} +``` + +#### Trigger Types + +##### Slack Notifications +```json +{ + "slack_url": { + "type": "HashicorpCloudVault", + "value": "slack-webhook-url" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Slack Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"slack"** for Slack notifications | +| `**config.slack_url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.slack_url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Title that appears in the Slack message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Email Notifications +```json +{ + "host": "smtp.gmail.com", + "port": 465, + "username": { + "type": "plain", + "value": "sender@example.com" + }, + "password": { + "type": "environment", + "value": "SMTP_PASSWORD" + }, + "message": { + "title": "Alert Subject", + "body": "Alert message for ${transaction.hash}", + }, + "sender": "sender@example.com", + "recipients": ["recipient@example.com"] +} +``` + +##### Email Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"email"** for email notifications | +| `**config.host**` | `String` | SMTP server hostname | +| `**config.port**` | `Number` | SMTP port (defaults to **465**) | +| `**config.username.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.username.value**` | `String` | Secret value (username, environment variable name, or vault secret name) | +| `**config.password.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.password.value**` | `String` | Secret value (password, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Email subject line | +| `**config.message.body**` | `String` | Email body template with variable substitution | +| `**config.sender**` | `String` | Sender email address | +| `**config.recipients**` | `Array[String]` | List of recipient email addresses | + +##### Webhook Notifications +```json +{ + "url": { + "type": "HashicorpCloudVault", + "value": "webhook-url" + }, + "method": "POST", + "secret": { + "type": "environment", + "value": "WEBHOOK_SECRET" + }, + "headers": { + "Content-Type": "application/json" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Webhook Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"webhook"** for webhook notifications | +| `**config.url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.method**` | `String` | HTTP method (POST, GET, etc.) defaults to POST | +| `**config.secret.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.secret.value**` | `String` | Secret value (HMAC secret, environment variable name, or vault secret name) | +| `**config.headers**` | `Object` | Headers to include in the webhook request | +| `**config.message.title**` | `String` | Title that appears in the webhook message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Discord Notifications +```json +{ + "discord_url": { + "type": "plain", + "value": "https://discord.com/api/webhooks/123-456-789" + }, + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Discord Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"discord"** for Discord notifications | +| `**config.discord_url.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.discord_url.value**` | `String` | Secret value (URL, environment variable name, or vault secret name) | +| `**config.message.title**` | `String` | Title that appears in the Discord message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Telegram Notifications +```json +{ + "token": { + "type": "HashicorpCloudVault", + "value": "telegram-bot-token" + }, + "chat_id": "9876543210", + "message": { + "title": "Alert Title", + "body": "Alert message for ${transaction.hash}" + } +} +``` + +##### Telegram Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"telegram"** for Telegram notifications | +| `**config.token.type**` | `String` | Secret type (**"Plain"**, **"Environment"**, or **"HashicorpCloudVault"**) | +| `**config.token.value**` | `String` | Secret value (bot token, environment variable name, or vault secret name) | +| `**config.chat_id**` | `String` | Telegram chat ID | +| `**config.disable_web_preview**` | `Boolean` | Whether to disable web preview in Telegram messages (defaults to false) | +| `**config.message.title**` | `String` | Title that appears in the Telegram message | +| `**config.message.body**` | `String` | Message template with variable substitution | + +##### Custom Script Notifications +```json +{ + "language": "Bash", + "script_path": "./config/triggers/scripts/custom_notification.sh", + "arguments": ["--verbose"], + "timeout_ms": 1000 +} +``` + +##### Script Notification Fields +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** Human-readable name for the notification | +| `**trigger_type**` | `String` | Must be **"script"** for Custom Script notifications | +| `**language**` | `String` | The language of the script | +| `**script_path**` | `String` | The path to the script | +| `**arguments**` | `Array[String]` | The arguments of the script (optional). | +| `**timeout_ms**` | `Number` | The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed. | + +For more information about custom scripts, see [Custom Scripts Section](/monitor/scripts). + + + +***Security Risk***: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +#### Available Template Variables + +The monitor uses a structured JSON format with nested objects for template variables. The data is flattened into dot notation for template use. + +##### Common Variables +| **Variable** | **Description** | +| --- | --- | +| `**monitor.name**` | Name of the triggered monitor | +| `**transaction.hash**` | Hash of the transaction | +| `**functions**` | All functions matched and their parameters | +| `**events**` | All events matched and their parameters | + +##### Network-Specific Variables + +###### EVM Variables +| **Variable** | **Description** | +| --- | --- | +| `**transaction.from**` | Sender address | +| `**transaction.to**` | Recipient address | +| `**transaction.value**` | Transaction value | +| `**events.[index].signature**` | Event signature | +| `**events.[index].args.[param]**` | Event parameters by name | +| `**functions.[index].signature**` | Function signature | +| `**functions.[index].args.[param]**` | Function parameters by name | + +###### Stellar Variables +| **Variable** | **Description** | +| --- | --- | +| `**events.[index].args.[position]**` | Event parameters by position | +| `**events.[index].args.[param]**` | Event parameters by name (only in case the contract supports event parameters name) | +| `**functions.[index].args.[param]**` | Function parameters by name | + + + + +Transaction-related variables (`transaction.from`, `transaction.to`, `transaction.value`) are not available for Stellar networks. + + + +#### Message Formatting + +Slack, Discord, Telegram, Email and Webhook support Markdown formatting in their message bodies. You can use Markdown syntax to enhance your notifications. + +##### Example Email Notification with Markdown +```json +{ + "email_notification": { + "name": "Formatted Alert", + "trigger_type": "email", + "config": { + "host": "smtp.example.com", + "port": 465, + "username": {"type": "plain", "value": "alerts@example.com"}, + "password": {"type": "plain", "value": "password"}, + "message": { + "title": "**High Value Transfer Alert**", + "body": "### Transaction Details\n\n* **Amount:** ${events.0.args.value} USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n> Transaction Hash: ${transaction.hash}\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})" + }, + "sender": "alerts@example.com", + "recipients": ["recipient@example.com"] + } + } +} +``` + +##### Example Slack Notification with Markdown +```json +{ + "slack_notification": { + "name": "Formatted Alert", + "trigger_type": "slack", + "config": { + "slack_url": {"type": "plain", "value": "https://hooks.slack.com/services/XXX/YYY/ZZZ"}, + "message": { + "title": "*🚨 High Value Transfer Alert*", + "body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n>Transaction Hash: `${transaction.hash}`\n\n" + } + } + } +} +``` + +##### Example Discord Notification with Markdown +```json +{ + "discord_notification": { + "name": "Formatted Alert", + "trigger_type": "discord", + "config": { + "discord_url": {"type": "plain", "value": "https://discord.com/api/webhooks/XXX/YYY"}, + "message": { + "title": "**🚨 High Value Transfer Alert**", + "body": "# Transaction Details\n\n* **Amount:** `${events.0.args.value}` USDC\n* **From:** `${events.0.args.from}`\n* **To:** `${events.0.args.to}`\n\n>>> Transaction Hash: `${transaction.hash}`\n\n**[View on Explorer](https://etherscan.io/tx/${transaction.hash})" + } + } + } +} +``` + +##### Example Telegram Notification with Markdown +```json +{ + "telegram_notification": { + "name": "Formatted Alert", + "trigger_type": "telegram", + "config": { + "token": {"type": "plain", "value": "1234567890:ABCDEFGHIJKLMNOPQRSTUVWXYZ"}, + "chat_id": "9876543210", + "message": { + "title": "*🚨 High Value Transfer Alert*", + "body": "*Transaction Details*\n\n• *Amount:* `${events.0.args.value}` USDC\n• *From:* `${events.0.args.from}`\n• *To:* `${events.0.args.to}`\n\n`Transaction Hash: ${transaction.hash}`\n\n[View on Explorer](https://etherscan.io/tx/${transaction.hash})" + } + } + } +} +``` + +#### Important Considerations + +* Email notification port defaults to 465 if not specified. +* Template variables are context-dependent: + * Event-triggered notifications only populate event variables. + * Function-triggered notifications only populate function variables. + * Mixing contexts results in empty values. +* Credentials in configuration files should be properly secured. +* Consider using environment variables for sensitive information. + +### Monitor Configuration + +A Monitor defines what blockchain activity to watch and what actions to take when conditions are met. Each monitor combines: + +* Network targets (which chains to monitor) +* Contract addresses to watch +* Conditions to match (functions, events, transactions) +* Trigger conditions (custom scripts that act as filters for each monitor match to determine whether a trigger should be activated). +* Triggers to execute when conditions are met + +**Example Monitor Configuration** + +```json +{ + "name": "Large USDC Transfers", + "networks": ["ethereum_mainnet"], + "paused": false, + "addresses": [ + { + "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "contract_spec": [ ... ] + } + ], + "match_conditions": { + "functions": [ + { + "signature": "transfer(address,uint256)", + "expression": "value > 1000000" + } + ], + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 1000000" + } + ], + "transactions": [ + { + "status": "Success", + "expression": "value > 1500000000000000000" + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "bash", + "arguments": "--verbose", + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"] +} +``` + +#### Available Fields + +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**name**` | `String` | **Required** - **_Unique_** identifier for this monitor | +| `**networks**` | `Array[String]` | List of network slugs this monitor should watch | +| `**paused**` | `Boolean` | Whether this monitor is currently paused | +| `**addresses**` | `Array[Object]` | Contract addresses to monitor with optional ABIs | +| `**match_conditions**` | `Object` | Collection of conditions that can trigger the monitor | +| `**trigger_conditions**` | `Array[Object]` | Collection of filters to apply to monitor matches before executing triggers | +| `**triggers**` | `Array[String]` | IDs of triggers to execute when conditions match | + +#### Match Conditions + +Monitors support three types of match conditions that can be combined: + +##### Function Conditions +Match specific function calls to monitored contracts: + +```json +{ + "functions": [ + { + "signature": "transfer(address,uint256)", + "expression": "value > 1000" + } + ] +} +``` + +##### Event Conditions +Match events emitted by monitored contracts: + +```json +{ + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 1000000" + } + ] +} +``` + +##### Transaction Conditions +Match transaction properties. The available fields and expression syntax depend on the network type (EVM/Stellar) + +```json +{ + "transactions": [ + { + "status": "Success", // Only match successful transactions + "expression": "value > 1500000000000000000" // Match transactions with value greater than 1.5 ETH + } + ] +} +``` + +#### Available Transaction Fields (EVM) +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**value**` | `uint256` | Transaction value in wei | +| `**from**` | `address` | Sender address (case-insensitive comparison) | +| `**to**` | `address` | Recipient address (case-insensitive comparison) | +| `**hash**` | `string` | Transaction hash | +| `**gas_price**` | `uint256` | Gas price in wei (legacy transactions) | +| `**max_fee_per_gas**` | `uint256` | EIP-1559 maximum fee per gas | +| `**max_priority_fee_per_gas**` | `uint256` | EIP-1559 priority fee | +| `**gas_limit**` | `uint256` | Gas limit for transaction | +| `**nonce**` | `uint256` | Sender nonce | +| `**input**` | `string` | Hex-encoded input data (e.g., **"0xa9059cbb..."**) | +| `**gas_used**` | `uint256` | Actual gas used (from receipt) | +| `**transaction_index**` | `uint64` | Position in block | + +#### Available Transaction Fields (Stellar) +| **Field** | **Type** | **Description** | +| --- | --- | --- | +| `**hash**` | `string` | Transaction hash | +| `**ledger**` | `i64` | Ledger sequence number where the transaction was included | +| `**value**` | `i64` | Value associated with the **first** relevant operation (e.g., payment amount). Defaults to 0 if no relevant operation or value is found. | +| `**from**` | `address` | Source account address of the **first** relevant operation (e.g., payment sender). Case-insensitive comparison. | +| `**to**` | `address` | Destination account address of the **first** relevant operation (e.g., payment recipient or invoked contract). Case-insensitive comparison. | + +#### Matching Rules + +* If no conditions are specified, all transactions match +* For multiple condition types: + * Transaction conditions are checked first + * Then either function OR event conditions must match + * Both transaction AND (function OR event) must match if both specified + +### Expressions + +Expressions allow for condition checking of function arguments, event parameters, and transaction fields. + +**Supported Parameter/Field Types and Basic Operations:** + +| Type | Description | Example Operators | Notes | +| --- | --- | --- | --- | +| `**Numeric (uint/int variants)**` | Integer values (e.g., `42`, `-100`) or decimal values (e.g., `3.14`, `-0.5`). | `>`, `>=`, `<`, `<=`, `==`, `!=` | Numbers must have digits before and after a decimal point if one is present (e.g., `.5` or `5.` are not valid standalone numbers). | +| `**Address**` | Blockchain addresses. | `==`, `!=` | Comparisons (e.g., `from == '0xABC...'`) are typically case-insensitive regarding the hex characters of the address value itself. | +| `**String**` | Text values. Can be single-quoted (e.g., ’hello'`) or, on the right-hand side of a comparison, unquoted (e.g., `active`). | `==`, `!=`, `starts_with`, `ends_with`, `contains` | Quoted strings support `\'` to escape a single quote and `\\` to escape a backslash. All string comparison operations (e.g., `name == 'Alice'`, `description contains 'error'`) are performed case-insensitively during evaluation. See the dedicated "String Operations" section for more examples and details. | +| `**Boolean**` | True or false values. | `==`, `!=` | Represented as `true` or `false`. These keywords are parsed case-insensitively (e.g., `TRUE`, `False` are also valid in expressions). | +| `**Hex String Literal**` | A string literal starting with `0x` or `0X` followed by hexadecimal characters (0-9, a-f, A-F). | `==`, `!=`, `starts_with`, `ends_with`, `contains` | Treated as a string for comparison purposes (e.g., `input_data starts_with '0xa9059cbb'`). Comparison is case-sensitive for the hex characters after `0x`. | +| `**Array (EVM/Stellar)**` | Ordered list of items. For Stellar, often a JSON string in config (e.g., ’["a", "id":1]'`). For EVM, typically decoded from ABI parameters. | `contains`, `==`, `!=`, `[index]` | Detailed operations, including indexed access and behavior of `contains`, vary by network. See "Operations on Complex Types" below. | +| `**Object/Map (Stellar)**` | Key-value pairs, typically represented as a JSON string in config (e.g., ’"key": "value", "id": 123'`). | `.key_access`, `==`, `!=` | Supports dot notation for field access (e.g., `data.id`). See "Operations on Complex Types" for details. | +| `**Vec (Stellar)**` | Ordered list, where the parameter’s value can be a CSV string (e.g., `"foo,bar"`) or a JSON array string (e.g., ’["foo","bar"]'`). | `contains`, `==`, `!=` | Behavior of `contains` and `==` differs based on whether the value is CSV or a JSON array string. See "Operations on Complex Types" for details. | +| `**Tuple (EVM)**` | Ordered list represented as a JSON array string (e.g., ’["Alice", 0x1234..., 25, true, [12,34]]'`). | `contains`, `==`, `!=` | The `contains` operation performs a case-insensitive deep search through all tuple elements. `==` and `!=` perform comparisons against the entire tuple values. See "Operations on Complex Types" for details. | + +**Logical Operators:** + +* AND - All conditions must be true +* OR - At least one condition must be true +* () - Parentheses for grouping +* AND has higher precedence than OR (i.e., AND operations are evaluated before OR operations if not grouped by parentheses) + +**Variable Naming and Access (Left-hand side of conditions):** + +The left-hand side (LHS) of a condition specifies the data field or parameter whose value you want to evaluate. + +**Base Names:** + +* These are the direct names of parameters or fields, such as `amount`, `from`, `status`, or event parameter indices like `0`, `1` (common in Stellar events). +* Base names can consist of alphanumeric characters (a-z, A-Z, 0-9) and underscores (`_`). +* They can start with a letter, an underscore, or a digit. Starting with a digit is primarily relevant for numerically indexed parameters (e.g., Stellar event parameters). +* **Important:** Variable names are case-sensitive during evaluation. The name used in the expression must exactly match the casing of the field name in the source data (e.g., from an ABI or blockchain data structure). For example, if a field is named `TotalValue` in the data, an expression using `totalvalue` will not find it. +* Variable names cannot be keywords (e.g., `true`, `AND`, `OR`, `contains`). Keywords themselves are parsed case-insensitively. + +**Path Accessors (for complex types):** + +If a base parameter is a complex type like an object, map, or array, you can access its internal data using accessors: + +**Key Access:** Use dot notation (`.`) to access properties of an object or map. + +* Examples: `transaction.value`, `user.name`, `data.0` (if `0` is a valid key name as a string). +* Keys typically consist of alphanumeric characters and underscores. They usually start with a letter or underscore, but purely numeric keys (e.g., `.0`, `.123`) are also supported for map-like structures where keys might be strings representing numbers. +* Keys cannot contain hyphens (`-`). + +**Index Access:** Use bracket notation (`[]`) to access elements of an array by their zero-based integer index. + +* Examples: `my_array[0]`, `log_entries[3]`. +* The index must be a non-negative integer. + +**Combined Access:** You can combine key and index accessors to navigate nested structures. + +* Example: `event.data_array[0].property` (accesses the `property` field of the first object in `data_array`, which is part of `event`). +* Example: `map.numeric_key_as_string_0[1].name` (accesses the `name` property of the second element of an array stored under the key `0` in `map`). + +**String Operations:** + +Several operators are available for matching patterns and comparing string values. These are particularly useful for EVM transaction `input` data, Stellar parameters defined with `kind: "string"`, or any other field that contains text. + +* `string_param starts_with 'prefix'`:: + Checks if the string parameter’s value begins with the specified `prefix`. + Example: `transaction.input starts_with '0xa9059cbb'` (checks for ERC20 transfer function selector). +* `string_param ends_with 'suffix'`:: + Checks if the string parameter’s value ends with the specified `suffix`. + Example: `file_name ends_with '.txt'` +* `string_param contains 'substring'`:: + Checks if the string parameter’s value contains the specified `substring` anywhere within it. + Example: `message contains 'error'` +* `string_param == 'exact_string'`:: + Checks if the string parameter’s value is exactly equal to `exact_string`. +* `string_param != 'different_string'`:: + Checks if the string parameter’s value is not equal to `different_string`. + +**Important Notes on String Operations:** + +* **Operator Keywords:** The operator keywords themselves (`starts_with`, `ends_with`, `contains`, `AND`, `OR`, `true`, `false`, comparison symbols like `==`, `>`) are parsed case-insensitively. For example, `CONTAINS` is treated the same as `contains`, and `TRUE` is the same as `true`. +* **Case-Insensitive Evaluation for String Comparisons:** When comparing string data (e.g., from event parameters, transaction fields, or function arguments) with literal string values in your expression, all standard string operations perform a ***case-insensitive*** comparison during evaluation. + * Equality (`==`) and Inequality (`!=`) + * Pattern matching (`starts_with`, `ends_with`, `contains`) +* **Variable Name Case Sensitivity:** It is important to distinguish this from variable names (the left-hand side of your condition, e.g., `status`). Variable names **are** case-sensitive and must exactly match the field names in your source data (ABI, etc.). + +**Whitespace Handling:** +Flexible whitespace is generally allowed around operators, parentheses, and keywords for readability. However, whitespace within quoted string literals is significant and preserved. + +#### Operations on Complex Types + +Beyond simple primitive types, expressions can also interact with more complex data structures like arrays, objects, and vectors. + +##### EVM Specifics + +**Array Operations (`kind: "array"`)** + +When an EVM parameter is an array (often represented internally or configured with `kind: "array"` and its value being a JSON string representation if manually configured), the following operations are supported: + +* `array_param contains 'value'` checks if the string ’value'` exists within the array. +* `array_param == '["raw_json_array_string"]'` string comparison of the array’s entire JSON string representation against the provided string +* `array_param != '["raw_json_array_string"]'` the negation of the above +* `array_param[0]` indexed access + +**Tuple Operations (`kind: "tuple"`)** + +* `tuple_param contains 'value'` checks if the string ’value'` exists within the tuple. +* `tuple_param == (12, "hello", "testing", 34)` checks if the tuple is equal. +* `tuple_param != (12, "hello", "testing", 34)` checks if the tuple is not equal. + +Where `tuple_param` is the name of tuple param (we should have only one param for tuples). + +**Note on Solidity Structs:** When working with Solidity smart contracts, struct types are automatically converted to tuples during ABI encoding/decoding. For example, a Solidity struct like: + +```solidity +struct User { + uint256 id; + string name; + string email; + uint256 age; +} +``` + +Will be represented as a tuple `**(12, "user_name", "user_email", 34)**` where the values correspond to the struct fields in their declaration order. This conversion is handled transparently by the Solidity compiler and Web3 libraries, allowing you to use the tuple operations above to work with struct data returned from smart contract calls. + +##### Stellar Specifics + +**Object (`kind: "object"`) / Map (`kind: "Map"`) Operations** + +* `object_param.key == 'value'` checks if the object or map has a key named `key` with the value ’value'`. +* `object_param.nested_key.another_key > 100` checks if the nested key `another_key` within `nested_key` has a value greater than 100. +* `object_param == '"raw_json_object_string"'` checks if the object or map matches the provided JSON string representation. +* `object_param != '"raw_json_object_string"'` the negation of the above + +**Array (`kind: "array"`) Operations** + +* `array_param[index]` accesses the element at the specified `index` in the array. +* `array_param[0] == 'value'` checks if the first element in the array is equal to ’value'`. +* `array_param[1].property == 'value'` checks if the second element in the array has a property named `property` with the value ’value'`. +* `array_param contains 'value'` checks if the array contains the string ’value'`. +* `array_param == '["raw_json_array_string"]'` checks if the array matches the provided JSON string representation. +* `array_param != '["raw_json_array_string"]'` the negation of the above + +**Vector (`kind: "vec"`) Operations** +When a Stellar parameter has `kind: "vec"`, its value can be either a CSV string or a JSON array string. + +* `vec_param contains 'item'` checks if the vector contains the string ’item'`. This works for both CSV and JSON array strings. +* `vec_param == 'raw_string_value'` checks if the vector matches the provided raw string value. This works for both CSV and JSON array strings. +* `vec_param != 'raw_string_value'` the negation of the above + +**Event Parameter Access (Stellar)** + +Starting with Stellar Protocol 23, Soroban contracts can include event definitions in their contract specifications. This enables two ways to access event parameters: + +**Access by Name (Protocol 23+):** +When a contract specification includes event definitions, you can access event parameters by their defined names: + +* If an event has a parameter named `recipient` (kind: "Address"): + * `recipient == 'GBBD...ABCD'` +* If an event has a parameter named `amount` (kind: "U128"): + * `amount > 1000000` +* If an event has a parameter named `metadata` (kind: "Map") containing ’"id": 123, "name": "Test"'`: + * `metadata.id == 123` + * `metadata.name contains 'test'` (case-insensitive) +* If an event has a parameter named `items` (kind: "array") containing ’["alpha", "beta"]'`: + * `items contains 'beta'` (case-insensitive deep search) + +**Access by Index (Legacy):** +For contracts without event definitions in their specification, or when working with older contracts, event parameters can be accessed by their numeric index (e.g., `0`, `1`, `2`): + +* If event parameter `0` (kind: "Map") is ’"id": 123, "name": "Test"'`: + * `0.id == 123` + * `0.name contains 'est'` (case-insensitive) +* If event parameter `1` (kind: "array") is ’["alpha", "val": "beta"]'`: + * `1[0] == 'ALPHA'` (case-insensitive) + * `1[1].val == 'Beta'` (case-insensitive) + * `1 contains 'beta'` (case-insensitive deep search) + + + +Named access is recommended when available as it makes expressions more readable and maintainable. The monitor will automatically use named parameters if they’re available in the contract specification, otherwise it falls back to indexed access. + + +##### EVM Examples + +These examples assume common EVM event parameters or transaction fields. + +**Basic Comparisons** + +```json +// Numeric +"transaction.value > 1000000000000000000" // Value greater than 1 ETH +"event.amount <= 500" +"block.number == 12345678" + +// String (case-insensitive evaluation for '==' and 'contains') +"transaction.to == '0xdeadbeef...'" // Address check (address value comparison itself is case-insensitive) +"event.token_name == 'mytoken'" +"transaction.input contains 'a9059cbb'" // Checks for ERC20 transfer selector + +// Boolean +"receipt.status == true" // or simply "receipt.status" if boolean field can be evaluated directly +"event.isFinalized == false" +``` + +**Logical Operators** + +```json +"transaction.value > 1000 AND event.type == 'Deposit'" +"(receipt.status == true OR event.fallback_triggered == true) AND user.is_whitelisted == false" +``` + +**String Operations** + +```json +"transaction.input starts_with '0xa9059cbb'" // Case-insensitive for the operation +"event.message ends_with 'failed'" +"event.details contains 'critical alert'" +``` + +**Array Operations** + +Assume `event.ids` is `[10, 20, 30]` and `event.participants` is `["user": "Alice", "role": "admin", "user": "Bob", "role": "editor"]`. +```json +"event.ids[0] == 10" +"event.ids contains '20'" // Checks for string '20' (case-insensitive) + +"event.participants contains 'Alice'" // True (deep search, case-insensitive) +"event.participants contains 'editor'" // True (deep search, case-insensitive) +"event.participants == '[{\"user\": \"Alice\", \"role\": \"admin\"}, {\"user\": \"Bob\", \"role\": \"editor\"}]'" // Raw JSON match (case-sensitive for structure and keys) +``` + +##### Stellar Examples + +**Basic Comparisons** + +```json +// Numeric +"event.params.amount > 10000000" // Accessing 'amount' field in an object 'params' +"ledger.sequence >= 123456" + +// String (case-insensitive evaluation for '==' and 'contains') +"event.params.recipient == 'GBD22...'" // Address check +"event.type == 'payment_processed'" + +// Boolean +"transaction.successful == true" +"event.data.is_verified == false" +``` + +**Logical Operators** + +```json +"event.data.value > 500 AND event.source_account == 'GCA7Z...'" +"(event.type == 'TRANSFER' OR event.type == 'PAYMENT') AND event.params.asset_code == 'XLM'" +``` + +**String Operations** + +```json +"event.contract_id starts_with 'CA23...'" +"event.memo ends_with '_TEST'" +"event.params.description contains 'urgent'" +``` + +**Object (`kind: "object"`) / Map (`kind: "Map"`) Operations** + +Assume `event.details` (kind: "Map") is ’"id": 123, "user": "name": "CHarlie", "status": "Active", "tags": ["new"]'`. +```json +"event.details.id == 123" +"event.details.user.name == 'charlie'" // Case-insensitive string comparison +"event.details.user.status contains 'act'" // Case-insensitive contains +"event.details.tags == '[\"new\"]'" // Raw JSON string match for the 'tags' field +``` + +**Array (`kind: "array"`) Operations** + +Assume `event.items` (kind: "array") is ’["sku": "A1", "qty": 10, "sku": "B2", "qty": 5, "notes":"Rush order"]'`. +```json +"event.items[0].sku == 'a1'" +"event.items[1].qty < 10" +"event.items contains 'A1'" // Deep search (case-insensitive) +"event.items contains 'rush order'" // Deep search (case-insensitive) +``` + +**Vector (`kind: "vec"`) Operations** + +Assume `csv_data` (kind: "vec") is `"ALPHA,Bravo,Charlie"` and `json_array_data` (kind: "vec") is ’["Delta", "id": "ECHO", "Foxtrot"]'`. +```json +"csv_data contains 'bravo'" // Case-insensitive CSV element match +"csv_data == 'ALPHA,Bravo,Charlie'" // Raw string match + +"json_array_data contains 'delta'" // Case-insensitive deep search (like array) +"json_array_data contains 'ECHO'" // Case-insensitive deep search (like array) +``` + +**Event Parameter Access (Numerically Indexed)** + +Assume event parameter `0` is `12345` (u64), `1` (kind: "array") is ’["Val1", "VAL2"]'`, and `2` (kind: "Map") is ’"keyA": "dataX", "keyB": 789'`. +```json +"0 > 10000" +"1[0] == 'val1'" +"1 contains 'val2'" +"2.keyA == 'DATAX'" +"2.keyB < 1000" +``` + +Now, after Stellar Protocol 23 we can access to Event parameters by name (first, make sure the contract supports the feature) + +``` +"0 > 10000" +"param_name[0] == 'val1'" +"param_name contains 'val2'" +"param_name.keyA == 'DATAX'" +"param_name.keyB < 1000" +``` + + + + +With SEP-48 support, Stellar functions can now reference parameters by name (e.g., `amount > 1000`) instead of position (e.g., `2 > 1000`). Events still use indexed parameters until SEP-48 support is added for events. + +You can find the contract specification through Stellar contract explorer tool. For example: +[Stellar DEX Contract Interface](https://lab.stellar.org/smart-contracts/contract-explorer?$=network$id=mainnet&label=Mainnet&horizonUrl=https:////horizon.stellar.org&rpcUrl=https:////mainnet.sorobanrpc.com&passphrase=Public%20Global%20Stellar%20Network%20/;%20September%202015;&smartContracts$explorer$contractId=CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE;;) + + + +#### Trigger Conditions (Custom filters) + +Custom filters allow you to create sophisticated filtering logic for processing monitor matches. These filters act as additional validation layers that determine whether a match should trigger the execution of a trigger or not. + +For more information about custom scripts, see [Custom Scripts Section](/monitor/scripts). + + + +***Security Risk***: Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +**Example Trigger Conditions Configuration** + +```json +{ + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "Bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 +} +``` + +#### Available Fields + +##### Trigger Conditions Fields +| Field | Type | Description | +| --- | --- | --- | +| `**script_path**` | String | The path to the script | +| `**language**` | String | The language of the script | +| `**arguments**` | Array[String] | The arguments of the script (optional). | +| `**timeout_ms**` | Number | The timeout of the script is important to avoid infinite loops during the execution. If the script takes longer than the timeout, it will be killed and the match will be included by default. | + +#### Important Considerations + +* Network slugs in the monitor must match valid network configurations. +* Trigger IDs must match configured triggers. +* Expression syntax and available variables differ between EVM and Stellar networks. +* ABIs can be provided in two ways: + * For EVM networks: Through the monitor configuration using standard Ethereum ABI format + * For Stellar networks: Through the monitor configuration using SEP-48 format, or automatically fetched from the chain if not provided +* The monitoring frequency is controlled by the network’s `cron_schedule`. +* Each monitor can watch multiple networks and addresses simultaneously. +* Monitors can be paused without removing their configuration. + +## Running the Monitor + +### Local Execution + +1. ***Basic startup:*** + + ```bash + ./openzeppelin-monitor + ``` +2. ***With logging to file:*** + + ```bash + ./openzeppelin-monitor --log-file + ``` +3. ***With metrics enabled:*** + + ```bash + ./openzeppelin-monitor --metrics + ``` +4. ***Validate configuration without starting:*** + + ```bash + ./openzeppelin-monitor --check + ``` + +### Docker Execution + +1. ***Start all services:*** + + ```bash + cargo make docker-compose-up + ``` +2. ***With metrics and monitoring (Prometheus + Grafana):*** + + ```bash + # Set METRICS_ENABLED=true in .env file, then: + docker compose --profile metrics up -d + ``` +3. ***View logs:*** + + ```bash + docker compose logs -f monitor + ``` +4. ***Stop services:*** + + ```bash + cargo make docker-compose-down + ``` + +### Command Line Options + +| | | | +| --- | --- | --- | +| Option | Default | Description | +| `--log-file` | `false` | Write logs to file instead of stdout | +| `--log-level` | `info` | Set log level (trace, debug, info, warn, error) | +| `--metrics` | `false` | Enable metrics server on port 8081 | +| `--check` | `false` | Validate configuration files only | +| `--help` | - | Show all available options | + +### Testing your configuration + +#### Network Configuration +The `validate_network_config.sh` script helps ensure your network configuration is properly set up and operational. The script: + +* Tests the health of all configured RPC endpoints +* Validates connectivity using network-specific methods +* Provides clear visual feedback for each endpoint + +```bash +# Test default networks directory (/config/networks/) +./scripts/validate_network_config.sh + +# Test a specific configuration directory +./scripts/validate_network_config.sh -f /path/to/configs +``` + + +Run this script when setting up new networks, before deploying configuration changes, or when troubleshooting connectivity issues. + + +#### Validating Configuration Files + +Before starting the monitor service, you can validate your configuration files using the `--check` option: + +```bash +./openzeppelin-monitor --check +``` + +This command will: + +* Parse and validate all configuration files +* Check for syntax errors +* Verify references between monitors, networks, and triggers +* Report any issues without starting the service + +It’s recommended to run this check after making changes to any configuration files. + +#### Monitor Configuration +The monitor can be tested in two modes: + +#### 1. Latest Block Mode + +This mode processes the most recent blocks across all configured networks. + +```bash +./openzeppelin-monitor --monitor-path="config/monitors/evm_transfer_usdc.json" +``` + +What this does: + +* Runs the "Large Transfer of USDC Token" monitor +* Targets all networks specified in the configuration +* Processes only the latest block for each network +* Sends a notification to all associated channels for every match that is found + +#### 2. Specific Block Mode + +This mode allows you to analyze a particular block on a specific network, which is useful for debugging specific transactions, verifying monitor behavior on known events, and testing monitor performance on historical data. + +```bash +./openzeppelin-monitor \ + --monitor-path="config/monitors/evm_transfer_usdc.json" \ + --network=ethereum_mainnet \ + --block=12345678 +``` + +What this does: + +* Runs the "Large Transfer of USDC Token" monitor +* Targets only the specified network (`ethereum_mainnet`) +* Processes only the specified block (`12345678`) +* Sends a notification to all associated channels for every match that is found + + + + +Specific Block Mode requires both parameters: + +* `--network`: The network to analyze +* `--block`: The block number to process + + + +#### Data Persistence (Optional) + +* Set `LOG_MODE` as file will persist the log data in `logs/` on host. To change it to a different directory use `LOG_DATA_DIR`. +* Set `MONITOR_DATA_DIR` to specific dir on your host system which will persist data between container restarts. + +## Error Handling + +The monitor implements a comprehensive error handling system with rich context and tracing capabilities. For detailed information about error handling, see [Error Handling Guide](/monitor/error). + +## Important Considerations + +### Performance Considerations + +* Monitor performance depends on network congestion and RPC endpoint reliability. + * View the [list of RPC calls](/monitor/rpc#list-of-rpc-calls) made by the monitor. +* The `max_past_blocks` configuration is critical: + * Calculate as: `(cron_interval_ms/block_time_ms) + confirmation_blocks + 1` (defaults to this calculation if not specified). + * Example for 1-minute Ethereum cron: `(60000/12000) + 12 + 1 = 18 blocks`. + * Too low settings may result in missed blocks. +* Trigger conditions are executed sequentially based on their position in the trigger conditions array. Proper execution also depends on the number of available file descriptors on your system. To ensure optimal performance, it is recommended to increase the limit for open file descriptors to at least 2048 or higher. On Unix-based systems you can check the current limit by running `ulimit -n` and _***temporarily***_ increase it with `ulimit -n 2048`. +* Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect. +* See performance considerations about custom scripts [here](/monitor/scripts#performance-considerations). + +### Notification Considerations + +* Template variables are context-dependent: + * Event-triggered notifications only populate event variables. + * Function-triggered notifications only populate function variables. + * Mixing contexts results in empty values. +* Custom script notifications have additional considerations: + * Scripts receive monitor match data and arguments as JSON input + * Scripts must complete within their configured timeout_ms or they will be terminated + * Script modifications require monitor restart to take effect + * Supported languages are limited to Python, JavaScript, and Bash + +## Support + +For support or inquiries, contact us on [Telegram](https://t.me/openzeppelin_tg/4). + +Have feature requests or want to contribute? Join our community on [GitHub](https://github.com/OpenZeppelin/openzeppelin-monitor/) + +## License +This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details. + +## Security +For security concerns, please refer to our [Security Policy](https://github.com/OpenZeppelin/openzeppelin-monitor/blob/main/SECURITY.md). diff --git a/docs/content/monitor/latest-versions.js b/docs/content/monitor/latest-versions.js new file mode 100644 index 00000000..faf19278 --- /dev/null +++ b/docs/content/monitor/latest-versions.js @@ -0,0 +1,31 @@ +/** + * @typedef {Object} VersionConfig + * @property {string} label - Display label for the version + * @property {string} value - Internal value identifier + * @property {string} path - URL path for the version + * @property {boolean} isStable - Whether this is a stable release + */ + +export const latestStable = "1.1.x"; + +/** @type {VersionConfig[]} */ +export const allVersions = [ + { + label: "Development", + value: "development", + path: "/monitor", + isStable: false, + }, + { + label: "v1.1.x (latest stable)", + value: "1.1.x", + path: "/monitor/1.1.x", + isStable: true, + }, + { + label: "v1.0.x", + value: "1.0.x", + path: "/monitor/1.0.x", + isStable: true, + }, +]; diff --git a/docs/content/monitor/project-structure.mdx b/docs/content/monitor/project-structure.mdx new file mode 100644 index 00000000..e3c160dd --- /dev/null +++ b/docs/content/monitor/project-structure.mdx @@ -0,0 +1,208 @@ +--- +title: Project Structure +--- + +This document describes the project structure and organization of the OpenZeppelin Monitor codebase, including the source code layout, configuration files, and development resources. + +## Project Layout + +The project follows a standard Rust project layout with additional directories for configuration, documentation, and operational resources: + +``` +openzeppelin-monitor/ +├── src/ # Source code +│ ├── bootstrap/ # Bootstrap functions for the application +│ ├── models/ # Data structures and types +│ ├── repositories/ # Configuration storage +│ ├── services/ # Core business logic +│ ├── utils/ # Helper functions +│ +├── config/ # Configuration files +├── tests/ # Integration and property-based tests +├── data/ # Runtime data storage +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── cmd/ # Metrics and monitoring +├── examples/ # Example configuration files +└── ... other root files (Cargo.toml, README.md, etc.) +``` + +## Source Code Organization + +### `src/` Directory + +The main source code directory contains the core implementation files organized into several modules: + +#### `bootstrap/` +Application initialization and setup for `main.rs`: + +* Handles service initialization and dependency injection +* Manages the startup sequence and service lifecycle + +#### `models/` +Core data structures and types: + +* `blockchain/`: Platform-specific implementations + * `evm/`: Ethereum Virtual Machine specific types + * `stellar/`: Stellar blockchain specific types +* `config/`: Configuration loading and validation +* `core/`: Core domain models +* `security/`: Security and secret management + +#### `repositories/` +Configuration storage: + +* Handles loading and validating configuration files +* Provides storage interfaces for monitors, networks, and triggers +* Implements validation of configuration references + +#### `services/` +Core business logic: + +* `blockchain/`: Blockchain client interfaces + * `transports/`: Transport clients + * `evm/`: Ethereum Virtual Machine transport client + * `stellar/`: Stellar transport client + * `clients/`: Client implementations + * `evm/`: Ethereum Virtual Machine client + * `stellar/`: Stellar client +* `blockwatcher/`: Block monitoring and processing +* `filter/`: Transaction and event filtering + * `filters/`: Filter implementations + * `evm/`: Ethereum Virtual Machine filter + * `stellar/`: Stellar filter +* `notification/`: Alert handling +* `trigger/`: Trigger evaluation and execution + * `script/`: Script execution utilities + +#### `utils/` +Helper functions: + +* `cron_utils`: Cron schedule utilities +* `expression`: Expression evaluation +* `logging/`: Logging utilities +* `macros/`: Macros for common functionality +* `metrics/`: Metrics utilities +* `monitor/`: Monitor configuration test utilities +* `tests/`: Contains test utilities and helper functions + * `builders/`: Test builder patterns implementing fluent interfaces for creating test fixtures + * `evm/`: Builder implementations specific to Ethereum Virtual Machine (EVM) testing + * `stellar/`: Builder implementations specific to Stellar blockchain testing + +## Configuration and Data + +### `config/` Directory + +Contains JSON configuration files for: + +* ***Network configurations*** (`networks/`) + * Connection details for blockchain networks + * RPC endpoints and network parameters +* ***Monitor configurations*** (`monitors/`) + * Monitoring rules and conditions + * Network and trigger references +* ***Trigger configurations*** (`triggers/`) + * Notification settings + * Script definitions +* ***Filter configurations*** (`filters/`) + * Match filter scripts + + + +The `examples/config/` directory contains example JSON configuration files for each (network, monitor, trigger and filters). + + +### `data/` Directory + +Runtime data storage: + +* Block processing state +* Operational data +* Temporary files + + + + +The `data/`, `logs/` and `config/` directories are gitignored except for example files. These directories are mounted to persist the configs and runtime data. + + + +## Examples and Resources + +### `examples/` Directory + +Provides practical examples and sample configurations to help users get started: + +* Demonstrates typical service configurations for various networks +* Acts as a quick-start guide for customizing the monitor +* Serves as a reference for best practices in configuration + +## Metrics and Monitoring + +### `cmd/prometheus/` Directory + +Prometheus exporters and monitoring infrastructure: + +* `dashboards/`: Grafana dashboards +* `datasources/`: Prometheus datasources +* `prometheus.yml`: Prometheus configuration +* `grafana.ini`: Grafana configuration + +## Testing and Documentation + +### `tests/` Directory + +Contains comprehensive test suites: + +* Integration tests +* Property-based tests +* Mock implementations +* Test utilities and helpers + +### `docs/` Directory + +Project documentation: + +* User guides +* API documentation +* Configuration examples +* Architecture diagrams + +### `scripts/` Directory + +Utility scripts for: + +* Development workflows +* Documentation generation +* Build processes +* Deployment helpers + +## Development Tools + +### Pre-commit Hooks + +Located in the project root: + +* Code formatting checks +* Linting rules +* Commit message validation + +### Build Configuration + +Core build files: + +* `Cargo.toml`: Project dependencies and metadata +* `rustfmt.toml`: Code formatting rules +* `rust-toolchain.toml`: Rust version and components + +## Docker Support + +The project includes Docker configurations for different environments: + +* `Dockerfile.development`: Development container setup +* `Dockerfile.production`: Production-ready container +* Before running the docker compose set your env variables in `.env` according to your needs + + + +For detailed information about running the monitor in containers, see the Docker deployment [section](/monitor#docker-installation) in the user documentation. diff --git a/docs/content/monitor/quickstart.mdx b/docs/content/monitor/quickstart.mdx new file mode 100644 index 00000000..2f4631aa --- /dev/null +++ b/docs/content/monitor/quickstart.mdx @@ -0,0 +1,654 @@ +--- +title: Quick Start Guide +--- + +OpenZeppelin Monitor is a powerful tool for monitoring blockchain events and transactions. This guide will help you get up and running quickly with practical examples for both EVM and Stellar networks. + +## What You'll Learn + +* How to set up OpenZeppelin Monitor locally or with Docker +* How to configure monitoring for USDC transfers on Ethereum +* How to monitor DEX swaps on Stellar +* How to set up notifications via Slack and email + +## Prerequisites + +Before you begin, ensure you have the following installed: + +* ***Rust 2021 edition*** - Required for building from source +* ***Docker*** - Optional, for containerized deployment +* ***Git*** - For cloning the repository + + + +If you don’t have Rust installed, visit https://rustup.rs/ to install it. + + +### System Dependencies (Linux) + +For Ubuntu 22.04+ or Debian-based systems (both x86 and ARM64 architectures), install required packages: + +***Note:*** Python 3.9+ is required for pre-commit hooks compatibility. + +```bash +# Install required packages directly +sudo apt update +sudo apt install -y \ + build-essential \ + curl \ + git \ + pkg-config \ + libssl-dev \ + libffi-dev \ + libyaml-dev \ +``` + +Or use the provided system packages script: + +```bash +chmod +x ./scripts/linux/sys_pkgs_core.sh +./scripts/linux/sys_pkgs_core.sh // For runtime dependencies only +``` + +## Quick Setup Options + +We provide two setup paths to get you started: + +### Option 1: Automated Setup (Recommended) + +For the fastest setup experience, use our automated script that handles everything for you. + +#### What the Automated Setup Does + +The `setup_and_run.sh` script provides a complete solution that: + +* ***Builds the monitor application*** from source +* ***Copies example configurations*** from `examples/` to `config/` + * Network configurations for major blockchains + * Pre-configured monitor examples (USDC transfers, Stellar DEX swaps) + * Required filter scripts and basic trigger notifications +* ***Validates all configurations*** to ensure proper setup +* ***Optionally runs the monitor*** to verify everything works + +#### Running the Automated Setup + +1. ***Clone the repository:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + ``` +2. ***Make the script executable:*** + + ```bash + chmod +x setup_and_run.sh + ``` +3. ***Run the automated setup:*** + + ```bash + ./setup_and_run.sh + ``` + +The script provides colored output and clear guidance throughout the process. + +#### After Automated Setup + +Once complete, you’ll have: + +* A fully built OpenZeppelin Monitor +* Example configurations ready for customization +* Clear guidance on next steps + +***Next Steps:*** +. Customize the copied configurations in `config/` directories +. Update RPC URLs and notification credentials +. Run the monitor with `./openzeppelin-monitor` + + + +The setup script creates working configurations with placeholder values. ***Remember to update your files with actual RPC endpoints and notification credentials*** before starting real monitoring. + + +### Option 2: Manual Setup + +For users who prefer more control over the setup process. + +#### Building from Source + +1. ***Clone and build:*** + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-monitor + cd openzeppelin-monitor + cargo build --release + ``` +2. ***Move the binary to project root:*** + + ```bash + mv ./target/release/openzeppelin-monitor . + ``` + +#### Docker Setup + +For containerized deployment: + +1. ***Start services:*** + + ```bash + docker compose up + ``` + + + + +By default, Docker Compose uses `Dockerfile.development`. For production, set: +`DOCKERFILE=Dockerfile.production` before running the command. + + + +#### Docker Management Commands + +| Command | Description | +| --- | --- | +| `docker ps -a` | Verify container status | +| `docker compose down` | Stop services (without metrics) | +| `docker compose --profile metrics down` | Stop services (with metrics) | +| `docker compose logs -f` | View logs (follow mode) | + +## Environment Configuration + +### Logging Configuration + +Configure logging verbosity by setting the `RUST_LOG` environment variable: + +| Level | Description | +| --- | --- | +| `error` | Only error messages | +| `warn` | Warnings and errors | +| `info` | General information (recommended) | +| `debug` | Detailed debugging information | +| `trace` | Very detailed trace information | + +```bash +export RUST_LOG=info +``` + +### Local Configuration + +Copy the example environment file and customize it: + +```bash +cp .env.example .env +``` + +For detailed configuration options, see [Basic Configuration](/monitor#basic_configuration). + +## Practical Examples + +Now let’s set up real monitoring scenarios. Choose the example that matches your needs: + +### Example 1: Monitor USDC Transfers (Ethereum) + +This example monitors large USDC transfers on Ethereum mainnet and sends notifications when transfers exceed 10,000 USDC. + +#### Step 1: Network Configuration + +Create the Ethereum mainnet configuration: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/networks/ethereum_mainnet.json config/networks/ethereum_mainnet.json +``` + +***Key Configuration Details:*** + +```json +{ + "network_type": "EVM", + "slug": "ethereum_mainnet", + "name": "Ethereum Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "YOUR_RPC_URL_HERE" + }, + "weight": 100 + } + ], + "chain_id": 1, + "block_time_ms": 12000, + "confirmation_blocks": 12, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 18, + "store_blocks": false +} +``` + + + + +***Important:*** Replace `YOUR_RPC_URL_HERE` with your actual Ethereum RPC endpoint. You can use providers like Infura, Alchemy, or run your own node. + + + +#### Step 2: Monitor Configuration + +Set up the USDC transfer monitor: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/monitors/evm_transfer_usdc.json config/monitors/evm_transfer_usdc.json +cp examples/config/filters/evm_filter_block_number.sh config/filters/evm_filter_block_number.sh +``` + +***Monitor Configuration Overview:*** + +```json +{ + "name": "Large Transfer of USDC Token", + "paused": false, + "networks": ["ethereum_mainnet"], + "addresses": [ + { + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + "contract_spec": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + } + ] + } + ], + "match_conditions": { + "functions": [], + "events": [ + { + "signature": "Transfer(address,address,uint256)", + "expression": "value > 10000000000" + } + ], + "transactions": [ + { + "status": "Success", + "expression": null + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/evm_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_slack", "evm_large_transfer_usdc_email"] +} +``` + + + + +* The `expression: "value > 10000000000"` monitors transfers over 10,000 USDC (USDC has 6 decimals) +* Remove the `trigger_conditions` array to disable additional filtering +* The USDC contract address `0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48` is the official USDC contract on Ethereum mainnet + + + +#### Step 3: Notification Setup + +##### Slack Notifications + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +``` + +***Slack Configuration:*** + +```json +{ + "evm_large_transfer_usdc_slack": { + "name": "Large Transfer Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "SLACK_WEBHOOK_URL" + }, + "message": { + "title": "large_transfer_slack triggered", + "body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog" + } + } + } +} +``` + + + +To get a Slack webhook URL: + +1. Go to https://api.slack.com/apps +2. Create a new app or select existing one +3. Enable "Incoming Webhooks" +4. Create a webhook for your channel + + +##### Email Notifications + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/email_notifications.json config/triggers/email_notifications.json +``` + +***Email Configuration:*** + +```json +{ + "evm_large_transfer_usdc_email": { + "name": "Large Transfer Email Notification", + "trigger_type": "email", + "config": { + "host": "smtp.gmail.com", + "port": 465, + "username": { + "type": "plain", + "value": "your_email@gmail.com" + }, + "password": { + "type": "plain", + "value": "SMTP_PASSWORD" + }, + "message": { + "title": "large_transfer_usdc_email triggered", + "body": "Large transfer of ${events.0.args.value} USDC from ${events.0.args.from} to ${events.0.args.to} | https://etherscan.io/tx/${transaction.hash}#eventlog" + }, + "sender": "your_email@gmail.com", + "recipients": [ + "recipient1@example.com", + "recipient2@example.com" + ] + } + } +} +``` + + + +For Gmail, you’ll need to use an "App Password" instead of your regular password. Enable 2FA and generate an app password in your Google Account settings. + + +#### Step 4: Run the Monitor + +***Local Deployment:*** + +```bash +./openzeppelin-monitor +``` + +***Docker Deployment:*** + +```bash +cargo make docker-compose-up +``` + +#### What Happens Next + +Once running, the monitor will: + +1. Check for new Ethereum blocks every minute +2. Watch for USDC transfers over 10,000 USDC +3. Send notifications via Slack and email when large transfers occur + +#### Customization Options + +* ***Adjust threshold:*** Modify `"value > 10000000000"` to change the minimum transfer amount +* ***Monitor other tokens:*** Create new monitor configurations for different ERC20 tokens +* ***Add more networks:*** Configure additional EVM networks (Polygon, BSC, etc.) + +### Example 2: Monitor DEX Swaps (Stellar) + +This example monitors large DEX swaps on Stellar mainnet. + +#### Step 1: Network Configuration + +Create the Stellar mainnet configuration: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/networks/stellar_mainnet.json config/networks/stellar_mainnet.json +``` + +***Key Configuration Details:*** + +```json +{ + "network_type": "Stellar", + "slug": "stellar_mainnet", + "name": "Stellar Mainnet", + "rpc_urls": [ + { + "type_": "rpc", + "url": { + "type": "plain", + "value": "YOUR_RPC_URL_HERE" + }, + "weight": 100 + } + ], + "network_passphrase": "Public Global Stellar Network ; September 2015", + "block_time_ms": 5000, + "confirmation_blocks": 2, + "cron_schedule": "0 */1 * * * *", + "max_past_blocks": 20, + "store_blocks": true +} +``` + +#### Step 2: Monitor Configuration + +Set up the DEX swap monitor: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/monitors/stellar_swap_dex.json config/monitors/stellar_swap_dex.json +cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar_filter_block_number.sh +``` + +***Monitor Configuration Overview:*** + +```json +{ + "name": "Large Swap By Dex", + "paused": false, + "networks": ["stellar_mainnet"], + "addresses": [ + { + "address": "CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE", + "contract_spec": [ + { + "function_v0": { + "doc": "", + "name": "swap", + "inputs": [ + { + "doc": "", + "name": "user", + "type_": "address" + }, + { + "doc": "", + "name": "in_idx", + "type_": "u32" + }, + { + "doc": "", + "name": "out_idx", + "type_": "u32" + }, + { + "doc": "", + "name": "in_amount", + "type_": "u128" + }, + { + "doc": "", + "name": "out_min", + "type_": "u128" + } + ], + "outputs": ["u128"] + } + } + ] + } + ], + "match_conditions": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [ + { + "status": "Success", + "expression": null + } + ] + }, + "trigger_conditions": [ + { + "script_path": "./config/filters/stellar_filter_block_number.sh", + "language": "bash", + "arguments": ["--verbose"], + "timeout_ms": 1000 + } + ], + "triggers": ["stellar_large_swap_by_dex_slack"] +} +``` + + + + +* The `contract_spec` field is optional for Stellar contracts. If not provided, the monitor automatically fetches the contract’s SEP-48 interface from the chain +* You can explore Stellar contract interfaces using the [Stellar Contract Explorer](https://lab.stellar.org/smart-contracts/contract-explorer) +* The expression `"out_min > 1000000000"` monitors swaps with minimum output over 1 billion tokens +* Now, you can also filter by parameters event’s name (Stellar Protocol 23 has introduced this new feature). See this [section](/monitor#stellar_specifics) for more details + + + +#### Step 3: Notification Setup + +Set up Slack notifications for Stellar swaps: + +```bash +# Only necessary if you haven't already run the automated setup script (Option 1: Automated Setup) +cp examples/config/triggers/slack_notifications.json config/triggers/slack_notifications.json +``` + +***Slack Configuration:*** + +```json +{ + "stellar_large_swap_by_dex_slack": { + "name": "Large Swap By Dex Slack Notification", + "trigger_type": "slack", + "config": { + "slack_url": { + "type": "plain", + "value": "slack-webhook-url" + }, + "message": { + "title": "large_swap_by_dex_slack triggered", + "body": "${monitor.name} triggered because of a large swap of ${functions.0.args.out_min} tokens | https://stellar.expert/explorer/public/tx/${transaction.hash}" + } + } + } +} +``` + +* If you want to include event details in the notification message body, you can also access event parameters by name. Here’s an example: +```bash +"body": "${monitor.name} triggered because of a large swap from ${events.0.args.from} tokens | https://stellar.expert/explorer/public/tx/${transaction.hash}" +``` + +#### Step 4: Run the Monitor + +***Local Deployment:*** + +```bash +./openzeppelin-monitor +``` + +***Docker Deployment:*** + +```bash +cargo make docker-compose-up +``` + +#### What Happens Next + +Once running, the monitor will: + +1. Check for new Stellar blocks every minute +2. Watch for large DEX swaps +3. Send notifications via Slack when large swaps occur + +## Next Steps + +Now that you have OpenZeppelin Monitor running, here are some suggestions for what to do next: + +### Testing and Validation + +* [Test your configuration](/monitor#testing_your_configuration) against specific block numbers +* Verify your RPC endpoints are working correctly +* Test notification channels with small transactions + +### Security and Best Practices + +* [Configure secure secret management](/monitor#secret_management) for sensitive data +* Use environment variables or Hashicorp Cloud Vault for credentials +* Regularly update your RPC endpoints and monitor configurations + +### Advanced Configuration + +* Explore additional examples in the [`examples/config/monitors` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/monitors) +* Set up monitoring for multiple networks simultaneously +* Configure custom filter scripts for complex conditions + +### Getting Help + +* Check the [GitHub Issues](https://github.com/OpenZeppelin/openzeppelin-monitor/issues) for known problems +* Review the [User Documentation](/monitor) for detailed configuration options +* Join the OpenZeppelin community for support + + + +Start with simple monitoring scenarios and gradually add complexity. This helps you understand how the system works and makes troubleshooting easier. diff --git a/docs/content/monitor/rpc.mdx b/docs/content/monitor/rpc.mdx new file mode 100644 index 00000000..9b1cdf4f --- /dev/null +++ b/docs/content/monitor/rpc.mdx @@ -0,0 +1,307 @@ +--- +title: RPC Client +--- + +## Overview + +The OpenZeppelin Monitor includes a robust RPC client implementation with automatic endpoint rotation and fallback capabilities. This ensures reliable blockchain monitoring even when individual RPC endpoints experience issues. + +* Multiple RPC endpoint support with weighted load balancing +* Automatic fallback on endpoint failures +* Rate limit handling (429 responses) +* Connection health checks +* Thread-safe endpoint rotation + +## Configuration + +### RPC URLs + +RPC endpoints are configured in the network configuration files with weights for load balancing: + +```json +{ + "rpc_urls": [ + { + "type_": "rpc", + "url": {"type": "plain", "value": "https://primary-endpoint.example.com"}, + "weight": 100 + }, + { + "type_": "rpc", + "url": {"type": "plain", "value": "https://backup-endpoint.example.com"}, + "weight": 50 + } + ] +} +``` + + + +For high-availability setups, configure at least 3 (private) RPC endpoints with appropriate weights to ensure continuous operation even if multiple endpoints fail. + + +### Configuration Fields + +| Field | Type | Description | +| --- | --- | --- | +| `**type_**` | `String` | Type of endpoint (currently only "rpc" is supported) | +| `**url.type**` | `String` | Secret type ("Plain", "Environment", or "HashicorpCloudVault") | +| `**url.value**` | `String` | The RPC endpoint URL | +| `**weight**` | `Number` | Load balancing weight (0-100) | + +## Endpoint Management + +The endpoint manager handles + +* Initial endpoint selection based on weights +* Automatic rotation on failures +* Connection health checks +* Thread-safe endpoint updates + +Each blockchain network type has its own specialized transport client that wraps the base `HttpTransportClient`. +The transport clients are implemented as: + +1. **Core HTTP Transport**: `HttpTransportClient` provides core HTTP functionality, including the integrated retryable client. +2. **Network-Specific Transports**: + * `EVMTransportClient` for EVM networks + * `StellarTransportClient` for Stellar networks + +### Rotation Strategy + +The RPC client includes an automatic rotation strategy for handling specific types of failures: + +* For 429 (Too Many Requests) responses: + * Immediately rotates to a fallback URL + * Retries the request with the new endpoint + * Continues this process until successful or all endpoints are exhausted + +#### Configuration Options + +The error codes that trigger RPC endpoint rotation can be customized in the `src/services/blockchain/transports/mod.rs` file. + +```rust +pub const ROTATE_ON_ERROR_CODES: [u16; 1] = [429]; +``` + +### Retry Strategy + +The transport layer uses a combination of same-endpoint retries and endpoint rotation to handle transient failures and maintain service availability. + +#### Same-Endpoint Retry (via `reqwest-retry`) + +The `HttpTransportClient` (and by extension, EVM and Stellar clients) utilizes a `reqwest_middleware::ClientWithMiddleware`. This client is configured during initialization using the `utils::http::create_retryable_http_client` utility. This utility layers `reqwest_retry::RetryTransientMiddleware` on top of a shared base `reqwest::Client`. + +This middleware handles: + +* Automatic retries for transient HTTP errors (e.g., 5xx server errors, network timeouts) for requests made to the **currently active RPC URL**. +* An exponential backoff policy between these retry attempts. +* Parameters like the number of retries, backoff durations, and jitter are defined in an `RetryConfig` struct (see [Configuration Options](#configuration-options)). +* This same-endpoint retry mechanism is independent of, and operates before, the endpoint rotation logic. If all same-endpoint retries fail for the current URL, the error is then processed by the `EndpointManager`. + +#### Endpoint Rotation (via `EndpointManager`) + +If all same-endpoint retries fail for the currently active RPC URL, or if certain HTTP status codes (e.g., 429 Too Many Requests, as defined in `ROTATE_ON_ERROR_CODES`) are received, the `EndpointManager` (used by `HttpTransportClient`) will attempt to rotate to a healthy fallback URL. This ensures that if one endpoint becomes persistently unavailable, the system can switch to an alternative. The health check for a fallback URL also benefits from the same-endpoint retry mechanism. + +#### Configuration Options + +The same-endpoint retry behavior is configured via the `RetryConfig` struct, which is used by `create_retryable_http_client` to set up the `ExponentialBackoff` policy for `reqwest-retry`. + +The default settings for `RetryConfig` result in an `ExponentialBackoff` policy approximately equivalent to: +```rust +// This illustrates the default policy created by RetryConfig::default() +// and create_retryable_http_client. +let http_retry_config = RetryConfig::default(); +let retry_policy = ExponentialBackoff::builder() + .base(http_retry_config.base_for_backoff) + .retry_bounds(http_retry_config.initial_backoff, http_retry_config.max_backoff) + .jitter(http_retry_config.jitter) + .build_with_max_retries(http_retry_config.max_retries); +``` + +The configurable options are defined in the `RetryConfig` struct: +```rust +// In utils::http +pub struct RetryConfig { + /// Maximum number of retries for transient errors (after the initial attempt). + pub max_retries: u32, + /// Initial backoff duration before the first retry. + pub initial_backoff: Duration, + /// Maximum backoff duration for retries. + pub max_backoff: Duration, + /// Base for the exponential backoff calculation (e.g., 2). + pub base_for_backoff: u64, + /// Jitter to apply to the backoff duration. + pub jitter: Jitter, +} +``` + +The client architecture ensures efficient resource use and consistent retry behavior: + +1. A single base `reqwest::Client` is created by `HttpTransportClient` with optimized connection pool settings. This base client is shared. +2. The `create_retryable_http_client` utility takes this base client and an `RetryConfig` to produce a `ClientWithMiddleware`. +3. This `ClientWithMiddleware` (the "retryable client") is then used for all HTTP operations within `HttpTransportClient`, including initial health checks, requests sent via `EndpointManager`, and `try_connect` calls during rotation. This ensures all operations benefit from the configured retry policy and the shared connection pool. + +Each transport client may define its own retry policy: + +```rust +// src/services/transports/http.rs +pub struct HttpTransportClient { + pub client: ClientWithMiddleware, + endpoint_manager: EndpointManager, + test_connection_payload: Option, +} + +// Example of client creation with retry mechanism +// Use default retry policy +let http_retry_config = RetryConfig::default(); + +// Create the base HTTP client +let base_http_client = reqwest::ClientBuilder::new() + .pool_idle_timeout(Duration::from_secs(90)) + .pool_max_idle_per_host(32) + .timeout(Duration::from_secs(30)) + .connect_timeout(Duration::from_secs(20)) + .build() + .context("Failed to create base HTTP client")?; + +// Create a retryable HTTP client with the base client and retry policy +let retryable_client = create_retryable_http_client( + &http_retry_config, + base_http_client, + Some(TransientErrorRetryStrategy), // Use custom or default retry strategy +); + +``` + +### Implementation Details +The `EndpointManager` uses the retry-enabled `ClientWithMiddleware` provided by `HttpTransportClient` for its attempts on the primary URL. If these attempts (including internal `reqwest-retry` retries) ultimately fail with an error that warrants rotation (e.g., a 429 status code, or persistent network errors), then `EndpointManager` initiates the URL rotation sequence. + +```mermaid +sequenceDiagram + participant User as User/Application + participant HTC as HttpTransportClient + participant EM as EndpointManager + participant RetryClient as ClientWithMiddleware (reqwest-retry) + participant RPC_Primary as Primary RPC + participant RPC_Fallback as Fallback RPC + + User->>HTC: send_raw_request() + HTC->>EM: send_raw_request(self, ...) + EM->>RetryClient: POST to RPC_Primary + Note over RetryClient, RPC_Primary: RetryClient handles same-endpoint retries internally (e.g., for 5xx) + alt Retries on RPC_Primary succeed + RPC_Primary-->>RetryClient: Success + RetryClient-->>EM: Success + EM-->>HTC: Success + HTC-->>User: Response + else All retries on RPC_Primary fail (e.g. network error or 429) + RPC_Primary-->>RetryClient: Final Error (e.g. 429 or network error) + RetryClient-->>EM: Final Error from RPC_Primary + EM->>EM: Decide to Rotate (based on error type) + EM->>HTC: try_connect(Fallback_URL) (HTC uses its RetryClient for this) + HTC->>RetryClient: POST to RPC_Fallback (health check) + alt Fallback health check succeeds + RPC_Fallback-->>RetryClient: Success (health check) + RetryClient-->>HTC: Success (health check) + HTC-->>EM: Success (health check) + EM->>EM: Update active URL to RPC_Fallback + EM->>RetryClient: POST to RPC_Fallback (actual request) + RPC_Fallback-->>RetryClient: Success + RetryClient-->>EM: Success + EM-->>HTC: Success + HTC-->>User: Response + else Fallback health check fails + RPC_Fallback-->>RetryClient: Error (health check) + RetryClient-->>HTC: Error (health check) + HTC-->>EM: Error (health check) + EM-->>HTC: Final Error (all URLs failed) + HTC-->>User: Error Response + end + end +``` + +## List of RPC Calls + +Below is a list of RPC calls made by the monitor for each network type for each iteration of the cron schedule. +As the number of blocks being processed increases, the number of RPC calls grows, potentially leading to rate limiting issues or increased costs if not properly managed. + +```mermaid +graph TD + subgraph Common Operations + A[Main] --> D[Process New Blocks] + end + + subgraph EVM Network Calls + B[Network Init] -->|net_version| D + D -->|eth_blockNumber| E[For every block in range] + E -->|eth_getBlockByNumber| G1[Process Block] + G1 -->|eth_getLogs| H[Get Block Logs] + H -->|Only when needed| J[Get Transaction Receipt] + J -->|eth_getTransactionReceipt| I[Complete] + end + + subgraph Stellar Network Calls + C[Network Init] -->|getNetwork| D + D -->|getLatestLedger| F[In batches of 200 blocks] + F -->|getLedgers| G2[Process Block] + G2 -->|For each monitored contract without ABI| M[Fetch Contract Spec] + M -->|getLedgerEntries| N[Get WASM Hash] + N -->|getLedgerEntries| O[Get WASM Code] + O --> G2 + G2 -->|In batches of 200| P[Fetch Block Data] + P -->|getTransactions| L1[Get Transactions] + P -->|getEvents| L2[Get Events] + L1 --> Q[Complete] + L2 --> Q + end +``` + +**EVM** + +* RPC Client initialization (per active network): `net_version` +* Fetching the latest block number (per cron iteration): `eth_blockNumber` +* Fetching block data (per block): `eth_getBlockByNumber` +* Fetching block logs (per block): `eth_getLogs` +* Fetching transaction receipt (only when needed): + * When monitor condition requires receipt-specific fields (e.g., `gas_used`) + * When monitoring transaction status and no logs are present to validate status + +**Stellar** + +* RPC Client initialization (per active network): `getNetwork` +* Fetching the latest ledger (per cron iteration): `getLatestLedger` +* Fetching ledger data (batched up to 200 in a single request): `getLedgers` +* During block filtering, for each monitored contract without an ABI in config: + * Fetching contract instance data: `getLedgerEntries` + * Fetching contract WASM code: `getLedgerEntries` +* Fetching transactions (batched up to 200 in a single request): `getTransactions` +* Fetching events (batched up to 200 in a single request): `getEvents` + +## Best Practices + +* Configure multiple private endpoints with appropriate weights +* Use geographically distributed endpoints when possible +* Monitor endpoint health and adjust weights as needed +* Set appropriate retry policies based on network characteristics + +## Troubleshooting + +### Common Issues + +* **429 Too Many Requests**: Increase the number of fallback URLs, adjust weights or reduce monitoring frequency +* **Connection Timeouts**: Check endpoint health and network connectivity +* **Invalid Responses**: Verify endpoint compatibility with your network type + +### Logging + +Enable debug logging for detailed transport information: + +```bash +RUST_LOG=debug +``` + +This will show: + +* Endpoint rotations +* Connection attempts +* Request/response details diff --git a/docs/content/monitor/scripts.mdx b/docs/content/monitor/scripts.mdx new file mode 100644 index 00000000..60d9dbb6 --- /dev/null +++ b/docs/content/monitor/scripts.mdx @@ -0,0 +1,617 @@ +--- +title: Custom Scripts +--- + +OpenZeppelin Monitor allows you to implement custom scripts for additional filtering of monitor matches and custom notification handling. + + + +***Security Risk:*** Only run scripts that you trust and fully understand. Malicious scripts can harm your system or expose sensitive data. Always review script contents and verify their source before execution. + + +## Custom Filter Scripts + +Custom filter scripts allow you to apply additional conditions to matches detected by the monitor. This helps you refine the alerts you receive based on criteria specific to your use case. + +### Implementation Guide + +1. Create a script in one of the supported languages: + * Bash + * Python + * JavaScript +2. Your script will receive a JSON object with the following structure: + * EVM + + ```json + { + "args": ["--verbose"], + "monitor_match": { + "EVM": { + "matched_on": { + "events": [], + "functions": [ + { + "expression": null, + "signature": "transfer(address,uint256)" + } + ], + "transactions": [ + { + "expression": null, + "status": "Success" + } + ] + }, + "matched_on_args": { + "events": null, + "functions": [ + { + "args": [ + { + "indexed": false, + "kind": "address", + "name": "to", + "value": "0x94d953b148d4d7143028f397de3a65a1800f97b3" + }, + { + "indexed": false, + "kind": "uint256", + "name": "value", + "value": "434924400" + } + ], + "hex_signature": "a9059cbb", + "signature": "transfer(address,uint256)" + } + ] + }, + "monitor": { + "addresses": [ + { + "contract_spec": null, + "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + } + ], + "match_conditions": { + "events": [ + { + "expression": "value > 10000000000", + "signature": "Transfer(address,address,uint256)" + } + ], + "functions": [ + { + "expression": null, + "signature": "transfer(address,uint256)" + } + ], + "transactions": [ + { + "expression": null, + "status": "Success" + } + ] + }, + "name": "Large Transfer of USDC Token", + "networks": ["ethereum_mainnet"], + "paused": false, + "trigger_conditions": [ + { + "arguments": ["--verbose"], + "language": "Bash", + "script_path": "./config/filters/evm_filter_block_number.sh", + "timeout_ms": 1000 + } + ], + "triggers": ["evm_large_transfer_usdc_script"] + }, + "receipt": { + "blockHash": "0x...", + "blockNumber": "0x...", + "contractAddress": null, + "cumulativeGasUsed": "0x...", + "effectiveGasPrice": "0x...", + "from": "0x...", + "gasUsed": "0xb068", + "status": "0x1", + "to": "0x...", + "transactionHash": "0x...", + "transactionIndex": "0x1fc", + "type": "0x2" + }, + "logs": [ + { + "address": "0xd1f2586790a5bd6da1e443441df53af6ec213d83", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x00000000000000000000000060af8cf92e5aa9ead4a592d657cd6debecfbc616", + "0x000000000000000000000000d1f2586790a5bd6da1e443441df53af6ec213d83" + ], + "data": "0x00000000000000000000000000000000000000000000106015728793d21f77ac", + "blockNumber": "0x1451aca", + "transactionHash": "0xa39d1b9b3edda74414bd6ffaf6596f8ea12cf0012fd9a930f71ed69df6ff34d0", + "transactionIndex": "0x0", + "blockHash": "0x9432868b7fc57e85f0435ca3047f6a76add86f804b3c1af85647520061e30f80", + "logIndex": "0x2", + "removed": false + }, + ], + "transaction": { + "accessList": [], + "blockHash": "0x...", + "blockNumber": "0x1506545", + "chainId": "0x1", + "from": "0x...", + "gas": "0x7a120", + "gasPrice": "0x...", + "hash": "0x...", + "maxFeePerGas": "0x...", + "maxPriorityFeePerGas": "0x...", + "nonce": "0x14779f", + "to": "0x...", + "transactionIndex": "0x...", + "type": "0x2", + "value": "0x0" + } + } + } + } + ``` + * Stellar + + ```json + { + "args": ["--verbose"], + "monitor_match": { + "Stellar": { + "monitor": { + "name": "Large Swap By Dex", + "networks": ["stellar_mainnet"], + "paused": false, + "addresses": [ + { + "address": "GCXYK...", + "contract_spec": null + } + ], + "match_conditions": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [] + }, + "trigger_conditions": [ + { + "arguments": ["--verbose"], + "language": "Bash", + "script_path": "./config/filters/stellar_filter_block_number.sh", + "timeout_ms": 1000 + } + ], + "triggers": ["stellar_large_transfer_usdc_script"] + }, + "transaction": { + "status": "SUCCESS", + "txHash": "2b5a0c...", + "applicationOrder": 3, + "feeBump": false, + "envelopeXdr": "AAAAAA...", + "envelopeJson": { + "type": "ENVELOPE_TYPE_TX", + "tx": {/* transaction details */} + }, + "resultXdr": "AAAAAA...", + "resultJson": {/* result details */}, + "resultMetaXdr": "AAAAAA...", + "resultMetaJson": {/* metadata details */}, + "diagnosticEventsXdr": ["AAAAAA..."], + "diagnosticEventsJson": [{/* event details */}], + "ledger": 123456, + "createdAt": 1679644800, + "decoded": { + "envelope": {/* decoded envelope */}, + "result": {/* decoded result */}, + "meta": {/* decoded metadata */} + } + }, + "ledger": { + "hash": "abc1...", + "sequence": 123456, + "ledgerCloseTime": "2024-03-20T10:00:00Z", + "headerXdr": "AAAAAA...", + "headerJson": {/* header details */}, + "metadataXdr": "AAAAAA...", + "metadataJSON": {/* metadata details */} + }, + "matched_on": { + "functions": [ + { + "signature": "swap(Address,U32,U32,U128,U128)", + "expression": "out_min > 1000000000" + } + ], + "events": [], + "transactions": [] + }, + "matched_on_args": { + "functions": [], + "events": null + } + } + } + } + ``` + +### Script Output Requirements + +* Your script should print a boolean value indicating whether the match should be filtered. +* Print `true` if the match should be filtered out (not trigger an alert). +* Print `false` if the match should be processed (trigger an alert). +* Only the **last** printed line will be considered for evaluation. + +### Example Filter Script (Bash) + +```bash +#!/bin/bash + +main() { + # Read JSON input from stdin + input_json=$(cat) + + # Parse arguments from the input JSON and initialize verbose flag + verbose=false + args=$(echo "$input_json" | jq -r '.args[]? // empty') + if [ ! -z "$args" ]; then + while IFS= read -r arg; do + if [ "$arg" = "--verbose" ]; then + verbose=true + echo "Verbose mode enabled" + fi + done <<< "$args" + fi + + # Extract the monitor match data from the input + monitor_data=$(echo "$input_json" | jq -r '.monitor_match') + + if [ "$verbose" = true ]; then + echo "Input JSON received:" + fi + + # Extract blockNumber from the EVM receipt or transaction + block_number_hex=$(echo "$monitor_data" | jq -r '.EVM.transaction.blockNumber' || echo "") + + # Validate that block_number_hex is not empty + if [ -z "$block_number_hex" ]; then + echo "Invalid JSON or missing blockNumber" + echo "false" + exit 1 + fi + + # Remove 0x prefix if present and clean the string + block_number_hex=$(echo "$block_number_hex" | tr -d '\n' | tr -d ' ') + block_number_hex=${block_number_hex#0x} + + if [ "$verbose" = true ]; then + echo "Extracted block number (hex): $block_number_hex" + fi + + # Convert hex to decimal with error checking + if ! block_number=$(printf "%d" $((16#${block_number_hex})) 2>/dev/null); then + echo "Failed to convert hex to decimal" + echo "false" + exit 1 + fi + + if [ "$verbose" = true ]; then + echo "Converted block number (decimal): $block_number" + fi + + # Check if even or odd using modulo + is_even=$((block_number % 2)) + + if [ $is_even -eq 0 ]; then + echo "Block number $block_number is even" + echo "Verbose mode: $verbose" + echo "true" + exit 0 + else + echo "Block number $block_number is odd" + echo "Verbose mode: $verbose" + echo "false" + exit 0 + fi +} + +# Call main function +main +``` + +### Example Filter Script (JavaScript) + +```bash +#!/bin/bash + +try { + let inputData = ''; + // Read from stdin + process.stdin.on('data', chunk => { + inputData += chunk; + }); + + process.stdin.on('end', () => { + const data = JSON.parse(inputData); + const monitorMatch = data.monitor_match; + const args = data.args; + + // Extract block_number + let blockNumber = null; + if (monitorMatch.EVM) { + const hexBlock = monitorMatch.EVM.transaction?.blockNumber; + if (hexBlock) { + // Convert hex string to integer + blockNumber = parseInt(hexBlock, 16); + } + } + + if (blockNumber === null) { + console.log('false'); + return; + } + + const result = blockNumber % 2 === 0; + console.log(`Block number ${blockNumber} is ${result ? 'even' : 'odd'}`); + console.log(result.toString()); + }); +} catch (e) { + console.log(`Error processing input: ${e}`); + console.log('false'); +} + +``` + +### Example Filter Script (Python) + +```bash +#!/bin/bash + +import sys +import json + +def main(): + try: + # Read input from stdin + input_data = sys.stdin.read() + if not input_data: + print("No input JSON provided", flush=True) + return False + + # Parse input JSON + try: + data = json.loads(input_data) + monitor_match = data['monitor_match'] + args = data['args'] + except json.JSONDecodeError as e: + print(f"Invalid JSON input: {e}", flush=True) + return False + + # Extract block_number + block_number = None + if "EVM" in monitor_match: + hex_block = monitor_match['EVM']['transaction'].get('blockNumber') + if hex_block: + # Convert hex string to integer + block_number = int(hex_block, 16) + + if block_number is None: + print("Block number is None") + return False + + result = block_number % 2 == 0 + print(f"Block number {block_number} is {'even' if result else 'odd'}", flush=True) + return result + + except Exception as e: + print(f"Error processing input: {e}", flush=True) + return False + +if __name__ == "__main__": + result = main() + # Print the final boolean result + print(str(result).lower(), flush=True) + +``` + +This examples script filters EVM transactions based on their block number: + +* Returns `true` (filter out) for transactions in even-numbered blocks +* Returns `false` (allow) for transactions in odd-numbered blocks +* Accepts a `--verbose` flag for detailed logging +* Explore other examples in the [`examples/config/filters` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/filters). + +### Integration + +Integrate your custom filter script with the monitor by following the [configuration guidelines](/monitor#trigger_conditions_custom_filters). + + + + +Trigger conditions are executed sequentially based on their position in the trigger conditions array. Every filter must return `false` for the match to be included and are only considered if they were executed successfully. + + + +## Custom Notification Scripts + +Custom notification scripts allow you to define how alerts are delivered when specific conditions are met. This can include sending alerts to different channels or formatting notifications in a particular way. + +### Implementation Guide + +1. Create a script in one of the supported languages: + * Bash + * Python + * JavaScript +2. Your script will receive the same JSON input format as [filter scripts](#implementation_guide) + +### Script Output Requirements + +* A non-zero exit code indicates an error occurred +* Error messages should be written to `stderr` +* A zero exit code indicates successful execution + +### Example Notification Script (Bash) + +```bash +#!/bin/bash + +main() { + # Read JSON input from stdin + input_json=$(cat) + + # Parse arguments from the input JSON and initialize verbose flag + verbose=false + args=$(echo "$input_json" | jq -r '.args[]? // empty') + if [ ! -z "$args" ]; then + while IFS= read -r arg; do + if [ "$arg" = "--verbose" ]; then + verbose=true + echo "Verbose mode enabled" + fi + done <<< "$args" + fi + + # Extract the monitor match data from the input + monitor_data=$(echo "$input_json" | jq -r '.monitor_match') + + # Validate input + if [ -z "$input_json" ]; then + echo "No input JSON provided" + exit 1 + fi + + # Validate JSON structure + if ! echo "$input_json" | jq . >/dev/null 2>&1; then + echo "Invalid JSON input" + exit 1 + fi + + if [ "$verbose" = true ]; then + echo "Input JSON received:" + echo "$input_json" | jq '.' + echo "Monitor match data:" + echo "$monitor_data" | jq '.' + fi + + # Process args if they exist + args_data=$(echo "$input_json" | jq -r '.args') + if [ "$args_data" != "null" ]; then + echo "Args: $args_data" + fi + + # If we made it here, everything worked + echo "Verbose mode: $verbose" + # return a non zero exit code and an error message + echo "Error: This is a test error" >&2 + exit 1 +} + +# Call main function +main +``` + +### Example Notification Script (JavaScript) + +```bash +#!/bin/bash + +try { + let inputData = ''; + // Read from stdin + process.stdin.on('data', chunk => { + inputData += chunk; + }); + + process.stdin.on('end', () => { + // Parse input JSON + const data = JSON.parse(inputData); + const monitorMatch = data.monitor_match; + const args = data.args; + + // Log args if they exist + if (args && args.length > 0) { + console.log(`Args: ${JSON.stringify(args)}`); + } + + // Validate monitor match data + if (!monitorMatch) { + console.log("No monitor match data provided"); + return; + } + }); +} catch (e) { + console.log(`Error processing input: ${e}`); +} + +``` + +### Example Notification Script (Python) + +```bash +#!/bin/bash + +import sys +import json + +def main(): + try: + # Read input from stdin + input_data = sys.stdin.read() + if not input_data: + print("No input JSON provided", flush=True) + + # Parse input JSON + try: + data = json.loads(input_data) + monitor_match = data['monitor_match'] + args = data['args'] + if args: + print(f"Args: {args}") + except json.JSONDecodeError as e: + print(f"Invalid JSON input: {e}", flush=True) + + + except Exception as e: + print(f"Error processing input: {e}", flush=True) + +if __name__ == "__main__": + main() + +``` + +This examples demonstrates how to: + +* Process the input JSON data +* Handle verbose mode for debugging +* Return error messages via `stderr` +* Set appropriate exit codes +* Explore other examples in the [`examples/config/triggers/scripts` directory](https://github.com/OpenZeppelin/openzeppelin-monitor/tree/main/examples/config/triggers/scripts). + +### Integration + +Integrate your custom notification script with the triggers by following the [configuration guidelines](/monitor#custom_script_notifications). + +## Performance Considerations + +* **File descriptor limits**: Each script execution requires file descriptors for `stdin`, `stdout`, and `stderr` + * Ensure your system allows at least 2,048 open file descriptors + * Check your current limit on Unix-based systems with `ulimit -n` + * Temporarily increase the limit with `ulimit -n 2048` + * For permanent changes, modify `/etc/security/limits.conf` or equivalent for your system +* **Script timeout**: Configure appropriate timeout values in your trigger conditions to prevent long-running scripts from blocking the pipeline + * The `timeout_ms` parameter controls how long a script can run before being terminated +* **Resource usage**: Complex scripts may consume significant CPU or memory resources + * Consider optimizing resource-intensive operations in your scripts + * Monitor system performance during high-volume periods +* **Script reloading**: Since scripts are loaded at startup, any modifications to script files require restarting the monitor to take effect diff --git a/docs/content/monitor/testing.mdx b/docs/content/monitor/testing.mdx new file mode 100644 index 00000000..7f822d16 --- /dev/null +++ b/docs/content/monitor/testing.mdx @@ -0,0 +1,127 @@ +--- +title: Testing Guide +--- + +This document provides information about testing OpenZeppelin Monitor, including running tests, generating coverage reports, and understanding the test structure. + +## Test Organization + +The project includes comprehensive test suites organized into different categories: + +### Test Types + +* ***Unit Tests***: Located within `src/` modules alongside the code they test +* ***Integration Tests***: Located in `tests/integration/` directory +* ***Property-based Tests***: Located in `tests/properties/` directory +* ***Mock Implementations***: Located in `tests/integration/mocks/` + +### Test Structure + +``` +tests/ +├── integration/ # Integration tests +│ ├── blockchain/ # Blockchain client tests +│ ├── blockwatcher/ # Block monitoring tests +│ ├── filters/ # Filter logic tests +│ ├── fixtures/ # Test data and configurations +│ ├── mocks/ # Mock implementations +│ └── ... +├── properties/ # Property-based tests +│ ├── filters/ # Filter property tests +│ ├── notifications/ # Notification property tests +│ └── ... +└── integration.rs # Integration test entry point +``` + +## Running Tests + +### All Tests + +Run the complete test suite: + +```bash +RUST_TEST_THREADS=1 cargo test +``` + + + + +`RUST_TEST_THREADS=1` is required to prevent test conflicts when accessing shared resources like configuration files or network connections. + + + +### Specific Test Categories + +***Property-based Tests:*** +```bash +RUST_TEST_THREADS=1 cargo test properties +``` + +***Integration Tests:*** +```bash +RUST_TEST_THREADS=1 cargo test integration +``` + +***Unit Tests Only:*** +```bash +RUST_TEST_THREADS=1 cargo test --lib +``` + +## Coverage Reports + +### Prerequisites + +Install the coverage tool: +```bash +rustup component add llvm-tools-preview +cargo install cargo-llvm-cov +``` + +### Generating Coverage + +***HTML Coverage Report:*** +```bash +RUST_TEST_THREADS=1 cargo +stable llvm-cov --html --open +``` + +This generates an HTML report in `target/llvm-cov/html/` and opens it in your browser. + +***Terminal Coverage Report:*** +```bash +RUST_TEST_THREADS=1 cargo +stable llvm-cov +``` + +## Troubleshooting + +### Common Issues + +***Tests hanging or timing out:*** +- Ensure `RUST_TEST_THREADS=1` is set +- Verify mock setups are correct + +***Coverage tool not found:*** +- Install with `cargo install cargo-llvm-cov` +- Add component with `rustup component add llvm-tools-preview` + +***Permission errors:*** +- Ensure test directories are writable +- Check file permissions on test fixtures + +### Debug Output + +Enable debug logging for tests: +```bash +RUST_LOG=debug RUST_TEST_THREADS=1 cargo test -- --nocapture +``` + +## Contributing Tests + +When contributing new features: + +1. ***Add comprehensive tests*** for new functionality +2. ***Ensure all tests pass*** locally before submitting +3. ***Include both unit and integration tests*** where appropriate +4. ***Update test documentation*** if adding new test patterns +5. ***Maintain or improve code coverage*** + +For more information about contributing, see the project’s contributing guidelines. diff --git a/docs/content/relayer/1.0.x/api_reference.mdx b/docs/content/relayer/1.0.x/api_reference.mdx new file mode 100644 index 00000000..7d235ad1 --- /dev/null +++ b/docs/content/relayer/1.0.x/api_reference.mdx @@ -0,0 +1,995 @@ +--- +title: API Reference +--- + +This document provides information on the implemented API, along with usage examples. + +## Pre-requisites +1. API key +This key is essential for most API calls. It can be set in `.env` file, under the `API_KEY` variable. +In case of a change on the key, there is no need to rebuild docker images, as `docker compose` will pick up the changes the next time the container is started. + +This key should be sent as a header for most of the API calls: + +```json +Authorization: Bearer +``` + +## Request format +By default, the container listens on port `8080`. Calls should follow call the URL: +`http://:8080/api/v1/relayers//rpc` + + +`relayer_id` is the name given to a relayer configuration in `./config/config.json` file. + + +## Environments +* Solana +* EVM +* Stellar + +## API Reference +* [Common Actions](#common-actions) +* [Solana](#solana) +* [EVM](#evm) +* [Stellar](#stellar) + +### Common actions +These are a set of REST calls common to both Solana and EVM relayers. + + +We are assuming a base url of `http://localhost:8080/` for these examples. + + +#### List relayers + +Request: `GET http://localhost:8080/api/v1/relayers/` + +Example request: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": [ + { + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": false, + "network_type": "evm", + "signer_id": "local-signer", + "policies": { + "eip1559_pricing": false, + "private_transactions": false, + "min_balance": 1 + }, + "address": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "notification_id": "notification-example", + "system_disabled": false + } + ], + "error": null, + "pagination": { + "current_page": 1, + "per_page": 10, + "total_items": 1 + } +} +``` + +#### Get relayer details + +Request: `GET http://localhost:8080/api/v1/relayers/` + +* `relayer_id` can be found by listing relayers or checking the config file (`./config/config.json`) + +Example request: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": { + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "network_type": "evm", + "paused": false, + "policies": { + "eip1559_pricing": false, + "private_transactions": false, + "min_balance": 1 + }, + "address": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "system_disabled": false + }, + "error": null +} +``` + +#### Update Relayer + +Request: `PATCH http://localhost:8080/api/v1/relayers/` + +Example request to pause a relayer: +```bash +curl --location --request PATCH 'http://localhost:8080/api/v1/relayers/' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "paused": true +}' +``` + +Example response: +```json +{ + "success": true, + "data": { + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": true, + "network_type": "evm", + "signer_id": "local-signer", + "policies": { + "eip1559_pricing": false, + "private_transactions": false, + "min_balance": 1 + }, + "address": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "notification_id": "notification-example", + "system_disabled": false + }, + "error": null +} +``` + +#### Get relayer balance +Request: `GET http://localhost:8080/api/v1/relayers//balance` + +Example request to pause a relayer: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/sepolia-example/balance' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": { + "balance": 1000000000000000, + "unit": "wei" + }, + "error": null +} +``` + +#### Get relayer status +Request: `GET http://localhost:8080/api/v1/relayers//status` + +Example request: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/sepolia-example/status' \ +--header 'Authorization: Bearer ' +``` + +Example response for EVM relayer: +```json +{ + "success": true, + "data": { + "balance": "1000000000000000000", + "pending_transactions_count": 2, + "last_confirmed_transaction_timestamp": "2025-02-26T13:28:55.838812+00:00", + "system_disabled": false, + "paused": false, + "nonce": "42" + }, + "error": null +} +``` + +Example response for Stellar relayer: +```json +{ + "success": true, + "data": { + "balance": "100000000", + "pending_transactions_count": 0, + "last_confirmed_transaction_timestamp": null, + "system_disabled": false, + "paused": false, + "sequence_number": "12345678901234567890" + }, + "error": null +} +``` + +Response fields: +| Field | Type | Description | +| --- | --- | --- | +| `balance` | String | Current relayer balance in the smallest unit (wei for EVM, lamports for Solana, stroops for Stellar) | +| `pending_transactions_count` | Number | Number of transactions that are pending, submitted, or mined but not yet confirmed | +| `last_confirmed_transaction_timestamp` | String (optional) | ISO 8601 timestamp of the most recent confirmed transaction, or `null` if no transactions have been confirmed | +| `system_disabled` | Boolean | Whether the relayer has been disabled by the system due to errors or configuration issues | +| `paused` | Boolean | Whether the relayer has been manually paused | +| `nonce` | String (EVM only) | Current transaction nonce for EVM relayers | +| `sequence_number` | String (Stellar only) | Current sequence number for Stellar relayers | + + + + +* For Solana relayers, this endpoint is currently not supported and will return an error +* Network-specific fields (`nonce` for EVM, `sequence_number` for Stellar) are included directly in the response using JSON flattening +* The `balance` field represents the raw balance value as a string to avoid precision loss with large numbers + + + +### Solana + +The Solana API implementation conforms to the [Paymaster Spec, window="_blank"](https://docs.google.com/document/d/1lweO5WH12QJaSAu5RG_wUistyk_nFeT6gy1CdvyCEHg/edit?tab=t.0#heading=h.4yldgprkuvav). + +Solana API + +| Method Name | Required Parameters | Result | Description | +| --- | --- | --- | --- | +| feeEstimate | `transaction`, `fee_token` | `estimated_fee`, `conversion_rate` | Estimate the fee for an arbitrary transaction using a specified token. | +| transferTransaction | `amount`, `token`, `source`, `destination` | `transaction`, `fee_in_spl`, `token`, `fee_in_lamports`, `valid_until_blockheight` | Create a transfer transaction for a specified token, sender, and recipient. The token supplied will be assumed to be the token to also be used for fees. Returns a partially signed transaction. | +| prepareTransaction | `transaction`, `fee_token` | `transaction`, `fee_in_spl`, `fee_token`, `fee_in_lamports`, `valid_until_blockheight` | Prepare a transaction by adding relayer-specific instructions. Returns a partially signed transaction. | +| signTransaction | `transaction` | `transaction`, `signature` | Sign a prepared transaction without submitting it to the blockchain. | +| signAndSendTransaction | `transaction` | `transaction`, `signature` | Sign and submit a transaction to the blockchain. | +| getSupportedTokens | (none) | `tokens[]` (list of token metadata) | Retrieve a list of tokens supported by the relayer for fee payments. | +| getFeaturesEnabled | (none) | `features[]` (list of enabled features) | Retrieve a list of features supported by the relayer. | + +Key terminology +| Key | Description | +| --- | --- | +| `transaction` | Base64-encoded serialized Solana transaction. This could be a signed or unsigned transaction. | +| `signature` | Unique "transaction hash" that can be used to look up transaction status on-chain. | +| `source` | Source wallet address. The relayer is responsible for deriving and the TA. | +| `destination` | Destination wallet address. The relayer is responsible for deriving and creating the TA if necessary. | +| `fee_token` | Token mint address for the fee payment. | +| `fee_in_spl` | Fee amount the end user will pay to the relayer to process the transaction in spl tokens in the smallest unit of the spl token (no decimals) | +| `fee_in_lamports` | Fee amount in Lamports the Relayer estimates it will pay for the transaction. | +| `valid_until_block_height` | Expiration block height for time-sensitive operations. | +| `tokens[]` | Array of supported token metadata (e.g., symbol, mint, decimals). | +| `features[]` | Array of features enabled by the relayer (e.g., bundle support, sponsorship). | + + +We are assuming a base url of `http://localhost:8080/` for these examples. + + +#### Get supported tokens + +Request: +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "getSupportedTokens", + "params": {}, + "id": 2 +}' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "tokens": [ + { + "decimals": 6, + "max_allowed_fee": 100000000, + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "symbol": "USDC" + }, + { + "decimals": 9, + "max_allowed_fee": null, + "mint": "So11111111111111111111111111111111111111112", + "symbol": "SOL" + } + ] + }, + "id": 2 +} +``` + +#### Fee estimate + + +The fee estimation method returns mocked values on devnet and testnet because the Jupiter service is available only on mainnet-beta. + + +Request: +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw ' +{ + "jsonrpc": "2.0", + "method": "feeEstimate", + "params": { + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZzDKeiaRTZZ3ipAtgJOOmqCGhz1iUHo8A9xynrbleugBAgIAAQwCAAAAQEIPAAAAAAA=", + "fee_token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + }, + "id": 3 +}' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "conversion_rate": "142.6", + "estimated_fee": "0.000713" + }, + "id": 3 +} +``` + +#### Sign transaction + +Request: +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "signTransaction", + "params": { + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bKmYrYtPWWI7zwiXWqAC5iFnkAkRL2D8s6lPkoJJokBAgIAAQwCAAAAQEIPAAAAAAA=" + + }, + "id": 4 +}' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "signature": "2jg9xbGLtZRsiJBrDWQnz33JuLjDkiKSZuxZPdjJ3qrJbMeTEerXFAKynkPW63J88nq63cvosDNRsg9VqHtGixvP", + "transaction": "AVbRgFoUlj0XdlLP4gJJ2zwmr/2g2LOdeNqGPYTl4VFzY7lrX+nKNXUEU0DLJEA+2BW3uHvudQSXz5YBqd5d9gwBAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bKmYrYtPWWI7zwiXWqAC5iFnkAkRL2D8s6lPkoJJokBAgIAAQwCAAAAQEIPAAAAAAA=" + }, + "id": 4 +} +``` + +#### Sign and send transaction +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "signAndSendTransaction", + "params": { + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bKmYrYtPWWI7zwiXWqAC5iFnkAkRL2D8s6lPkoJJokBAgIAAQwCAAAAQEIPAAAAAAA=" + }, + "id": 5 +}' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "signature": "2jg9xbGLtZRsiJBrDWQnz33JuLjDkiKSZuxZPdjJ3qrJbMeTEerXFAKynkPW63J88nq63cvosDNRsg9VqHtGixvP", + "transaction": "AVbRgFoUlj0XdlLP4gJJ2zwmr/2g2LOdeNqGPYTl4VFzY7lrX+nKNXUEU0DLJEA+2BW3uHvudQSXz5YBqd5d9gwBAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bKmYrYtPWWI7zwiXWqAC5iFnkAkRL2D8s6lPkoJJokBAgIAAQwCAAAAQEIPAAAAAAA=" + }, + "id": 5 +} +``` + +#### Prepare Transaction + + +The prepare transaction method returns a mocked value for the fee_in_spl response field on devnet and testnet, because the Jupiter service is available only on mainnet-beta. + + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "prepareTransaction", + "params": { + "transaction": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/bKmYrYtPWWI7zwiXWqAC5iFnkAkRL2D8s6lPkoJJokBAgIAAQwCAAAAQEIPAAAAAAA=", + "fee_token": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr" + + }, + "id": 6 +}' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "fee_in_lamports": "5000", + "fee_in_spl": "5000", + "fee_token": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "transaction": "Ae7kEB+DOH8vhFDlV6SqTCcaf0mJI/Yrn1Zr/WFh8kEfdD0c99wJ1bYV3FDjt/qtwxRa5LxuVDlHR2CT+M5BIgYBAAEDpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAuTJfv3pxOOfvB3SHRW0ArtL0kkx6rVqN+d+tGrRgLIMBAgIAAQwCAAAAQEIPAAAAAAA=", + "valid_until_blockheight": 351723643 + }, + "id": 6 +} +``` + +#### Transfer Transaction + + +The transfer transaction method returns a mocked value for the fee_in_spl response field on devnet and testnet, because the Jupiter service is available only on mainnet-beta. + + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "transferTransaction", + "params": { + "token": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "amount": 1, + "source": "C6VBV1EK2Jx7kFgCkCD5wuDeQtEH8ct2hHGUPzEhUSc8", + "destination": "D6VBV1EK2Jx7kFgCkCD5wuDeQtEH8ct2hHGUPzEhUSc8" + + }, + "id": 7 +}' +``` + +Result: +```json +{ + "jsonrpc": "2.0", + "result": { + "fee_in_lamports": "5000", + "fee_in_spl": "5000", + "fee_token": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "transaction": "AaQ8y7r1eIuwrmhuIWSJ7iWVJ5gAhZaZ9vd2I9wQ0PFs79GPYejdVrsVgMLm3t1c7g/WsoYhoPdt83ST1xcwdggBAAIEpNhTBS0w2fqEkg0sAghld4KIZNFW3kt5Co2TA75icpEMsnnyKbZZ5yUtDsJ/8r0KO7Li3BEwZoWs+nOJzoXwvgbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCp6Sg5VQll/9TWSsqvRtRd9zGOW09XyQxIfWBiXYKbg3tDrlnF1KFvUS/T47LoVLV2lUyLS2zrfs8g57jdLLGvWwECBAEDAQAKDAEAAAAAAAAABg==", + "valid_until_blockheight": 351724045 + }, + "id": 7 +} +``` + +### EVM + +| Method | Required Parameters | Result | Description | +| --- | --- | --- | --- | +| send transaction | `value`, `data`, `to`, `gas_limit` | | Submit transaction to blockchain. | +| list transactions | (none) | | List relayer transactions. | +| get transaction by id | `id` | | Retrieve transaction by id. | +| get transaction by nonce | `nonce` | | Retrieve transaction by nonce. | + +#### Send transaction +Request: `POST http://localhost:8080/api/v1/relayers//transactions` + +Example request to send transaction: +```bash + curl --location --request POST 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "gas_limit": 21000, + "speed": "average" +}' +``` + +Example response: +```json +{ + "success": true, + "data": { + "id": "47f440b3-f4ce-4441-9489-55fc83be12cf", + "hash": null, + "status": "pending", + "created_at": "2025-02-26T13:24:35.560593+00:00", + "sent_at": null, + "confirmed_at": null, + "gas_price": null, + "gas_limit": 21000, + "nonce": 0, + "value": "0x1", + "from": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "to": "0x5e87fD270D40C47266B7E3c822f4a9d21043012D", + "relayer_id": "sepolia-example" + }, + "error": null +} +``` + +#### List Transactions +Request: `GET http://localhost:8080/api/v1/relayers//transactions` + +Example request to list relayer transactions: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": [ + { + "id": "bfa362dc-a84a-4466-93d0-b8487bfd40cc", + "hash": "0xca349b67fad7b64239f4682a231c5398b0b52a93b626d1d67cb9ec037cdd290c", + "status": "confirmed", + "created_at": "2025-02-26T13:28:46.838812+00:00", + "sent_at": "2025-02-26T13:28:48.838812+00:00", + "confirmed_at": "2025-02-26T13:28:55.838812+00:00", + "gas_price": 12312313123, + "gas_limit": 21000, + "nonce": 8, + "value": "0x1", + "from": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "to": "0x5e87fD270D40C47266B7E3c822f4a9d21043012D", + "relayer_id": "sepolia-example" + }, + ], + "error": null, + "pagination": { + "current_page": 1, + "per_page": 10, + "total_items": 0 + } +} +``` + +#### Get transaction by id +Request: `GET http://localhost:8080/api/v1/relayers//transactions/id` + +Example request fetch relayer transaction by id: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions/47f440b3-f4ce-4441-9489-55fc83be12cf' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": { + "id": "47f440b3-f4ce-4441-9489-55fc83be12cf", + "hash": "0xa5759c99e99a1fc3b6e66bca75688659d583ee2556c7d185862dc8fcdaa4d5d7", + "status": "confirmed", + "created_at": "2025-02-26T13:28:46.838812+00:00", + "sent_at": "2025-02-26T13:28:48.838812+00:00", + "confirmed_at": "2025-02-26T13:28:55.838812+00:00", + "gas_price": 35843464006, + "gas_limit": 21000, + "nonce": 0, + "value": "0x1", + "from": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "to": "0x5e87fD270D40C47266B7E3c822f4a9d21043012D", + "relayer_id": "sepolia-example" + }, + "error": null +} +``` + +#### Get transaction by nonce +Request: `GET http://localhost:8080/api/v1/relayers//transactions/by-nonce/0` + +Example request fetch relayer transaction by nonce: +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions/by-nonce/0' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": { + "id": "47f440b3-f4ce-4441-9489-55fc83be12cf", + "hash": "0xa5759c99e99a1fc3b6e66bca75688659d583ee2556c7d185862dc8fcdaa4d5d7", + "status": "confirmed", + "created_at": "2025-02-26T13:28:46.838812+00:00", + "sent_at": "2025-02-26T13:28:48.838812+00:00", + "confirmed_at": "2025-02-26T13:28:55.838812+00:00", + "gas_price": 35843464006, + "gas_limit": 21000, + "nonce": 0, + "value": "0x1", + "from": "0xc834dcdc9a074dbbadcc71584789ae4b463db116", + "to": "0x5e87fD270D40C47266B7E3c822f4a9d21043012D", + "relayer_id": "sepolia-example" + }, + "error": null +} +``` + +### Stellar + + +Basic support for Stellar; it is currently under active development. The API interactions and specifics described below may evolve. + + +This section outlines how to interact with the Stellar network via the Relayer API. The relayer supports Soroban smart contract operations, including contract invocation, deployment, and WASM uploads. + +| Method Name | Required Parameters | Description | +| --- | --- | --- | +| Send Transaction | `network`, `operations` (or `transaction_xdr`) | Submit a transaction to the Stellar network. Supports payment and InvokeHostFunction operations, pre-built XDR transactions, and fee bump transactions. | +| Get Transaction Details | `transaction_id` | Retrieve a specific transaction by its ID. | +| List Transactions | (none) | List transactions for the relayer. | + +#### Supported Operation Types + +| Operation Type | Description | +| --- | --- | +| `payment` | Transfer native XLM or other assets between accounts | +| `invoke_contract` | Call a deployed Soroban smart contract function | +| `create_contract` | Deploy a new Soroban smart contract from WASM hash | +| `upload_wasm` | Upload WASM contract code to the Stellar ledger | + +#### Send Transaction +Submit a transaction to the Stellar network. + +Request: `POST http://localhost:8080/api/v1/relayers//transactions` + +##### Transaction Input Methods + +The relayer supports three ways to submit transactions: + +1. ***Operations-based***: Build a transaction by specifying the `operations` array (recommended for most use cases) +2. ***Transaction XDR (unsigned)***: Submit a pre-built unsigned transaction using `transaction_xdr` field (advanced use case) +3. ***Transaction XDR (signed) with fee bump***: Submit a signed transaction using `transaction_xdr` with `fee_bump: true` to wrap it in a fee bump transaction +===== Transaction Structure + +***Required fields:*** +- `network`: The Stellar network ("testnet", "mainnet", etc.) +- Either `operations` (array of operations) OR `transaction_xdr` (base64-encoded XDR) - but not both + +***Optional fields:*** +- `source_account`: The Stellar account that will be the source of the transaction (defaults to relayer’s address) +- `memo`: Transaction memo (see Memo Types below) +- `valid_until`: Transaction expiration time (ISO 8601 format) +- `transaction_xdr`: Pre-built transaction XDR (base64 encoded, signed or unsigned) - mutually exclusive with `operations` +- `fee_bump`: Boolean flag to request fee-bump wrapper (only valid with signed `transaction_xdr`) +- `max_fee`: Maximum fee for fee bump transactions in stroops (defaults to 1,000,000 = 0.1 XLM) + +##### Asset Types + +Assets in Stellar operations must be specified with a type field: + +***Native XLM:*** +```json +{"type": "native"} +``` + +***Credit Asset (4 characters or less):*** +```json +{ + "type": "credit_alphanum4", + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" +} +``` + +***Credit Asset (5-12 characters):*** +```json +{ + "type": "credit_alphanum12", + "code": "LONGASSET", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" +} +``` + +##### Memo Types + +Transactions can include optional memos: + +***No Memo:*** +```json +{"type": "none"} +``` + +***Text Memo (max 28 UTF-8 bytes):*** +```json +{"type": "text", "value": "Payment for services"} +``` + +***ID Memo:*** +```json +{"type": "id", "value": "12345"} +``` + +***Hash Memo (32 bytes hex):*** +```json +{"type": "hash", "value": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"} +``` + +***Return Memo (32 bytes hex):*** +```json +{"type": "return", "value": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"} +``` + +Example requests (cURL): + +***1. Payment Operation:*** +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "payment", + "destination": "GD77B6LYQ5XDCW6CND7CQMA23FSV7MZQGLBAU5OMEOXQM6XFTCMWQQCJ", + "asset": {"type": "native"}, + "amount": 1000000 + } + ], + "memo": {"type": "text", "value": "Payment for services"} +}' +``` + +***2. Invoke Contract:*** +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "invoke_contract", + "contract_address": "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA", + "function_name": "transfer", + "args": [ + {"address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY"}, + {"address": "GD77B6LYQ5XDCW6CND7CQMA23FSV7MZQGLBAU5OMEOXQM6XFTCMWQQCJ"}, + {"u64": "1000000"} + ], + "auth": {"type": "source_account"} + } + ] +}' +``` + +***3. Create Contract:*** +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "create_contract", + "source": { + "from": "address", + "address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY" + }, + "wasm_hash": "d3b2f6f8a1c5e9b4a7d8c2e1f5a9b3c6e8d4f7a2b5c8e1d4f7a0b3c6e9d2f5a8", + "salt": "0000000000000000000000000000000000000000000000000000000000000001" + } + ] +}' +``` + + +For create_contract, you can also include optional `constructor_args` array and use `"from": "contract"` for factory pattern deployments. + + +***4. Upload WASM:*** +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "upload_wasm", + "wasm": { + "type": "base64", + "base64": "AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAQcPAgVoZWxsbwAACG1lbW9yeTIDCgQAAAAL" + } + } + ] +}' +``` + + +WASM can be provided as either `"type": "base64"` or `"type": "hex"` encoding. + + +***5. Submit Pre-built Unsigned Transaction (XDR):*** + +For advanced use cases, you can submit a pre-built unsigned transaction as base64-encoded XDR: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "transaction_xdr": "AAAAAgAAAAC0V9YG9Ks6MEexw5yB+FDD8VJwmtv5OU2BwgPN6PpZcwAAAGQCeO4pAAAAAgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAACgAAAAAAAAAAAAAAAAAAAAA=", + "source_account": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY" +}' +``` + + +When submitting XDR transactions, the `operations` field is not required. The relayer will parse the transaction from the XDR + + +***6. Fee Bump Transaction:*** + +To submit a fee bump transaction for an existing signed transaction: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "transaction_xdr": "AAAAAgAAAAC0V9YG9Ks6MEexw5yB+FDD8VJwmtv5OU2BwgPN6PpZcwAAAGQCeO4pAAAAAgAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAACgAAAAAAAAAAAAAAAAAAAADN6PpZcwAAAEDtHqNhqC0V8mEZX9xXd4Gw3amGyg/aNTPKUQIH2nipnRps7H3HQTPhPiSzxwvzfXcGAz9H3hXXIkWegqJlEAoN", + "fee_bump": true, + "max_fee": 1000000 +}' +``` + + +When `fee_bump` is true, the `transaction_xdr` should contain a fully signed transaction (not a fee bump envelope). The relayer will create a fee bump transaction wrapper around it, paying the additional fees up to `max_fee` (in stroops). + + +Example response: +```json +{ + "success": true, + "data": { + "id": "5431b88c-183b-41c7-9bbb-841d38ddd866", + "hash": null, + "status": "pending", + "created_at": "2025-05-19T11:26:55.188781+00:00", + "sent_at": null, + "confirmed_at": null, + "source_account": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY", + "fee": 0, + "sequence_number": 0 + }, + "error": null +} +``` + +#### Get Transaction Details +Retrieve details for a specific Stellar transaction submitted via the relayer. + +Request: `GET http://localhost:8080/api/v1/relayers//transactions/` + +* ``: The ID of your Stellar relayer configuration. +* ``: The ID of the transaction (returned from Send Transaction or from List Transactions). + +Example request (cURL): +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers//transactions/' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": { + "id": "5431b88c-183b-41c7-9bbb-841d38ddd866", + "hash": "f22e5d9a36cbedee20de01d5bf89d2e80682c102e844d72f567da1acd1944cb0", + "status": "submitted", + "created_at": "2025-05-19T11:26:55.188781+00:00", + "sent_at": "2025-05-19T11:26:56.136646+00:00", + "confirmed_at": null, + "source_account": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY", + "fee": 0, + "sequence_number": 3700719915892739 + }, + "error": null +} +``` + +#### List Transactions +List transactions associated with a Stellar relayer, with support for pagination. + +Request: `GET http://localhost:8080/api/v1/relayers//transactions` + +Example request (cURL): +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' +``` + +Example response: +```json +{ + "success": true, + "data": [ + { + "id": "5431b88c-183b-41c7-9bbb-841d38ddd866", + "hash": "f22e5d9a36cbedee20de01d5bf89d2e80682c102e844d72f567da1acd1944cb0", + "status": "submitted", + "created_at": "2025-05-19T11:26:55.188781+00:00", + "sent_at": "2025-05-19T11:26:56.136646+00:00", + "confirmed_at": null, + "source_account": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY", + "fee": 0, + "sequence_number": 3700719915892739 + } + ], + "error": null, + "pagination": { + "current_page": 1, + "per_page": 10, + "total_items": 1 + } +} +``` + +#### ScVal Argument Format + +When invoking contract functions, arguments must be provided as ScVal values in JSON format. The relayer uses the stellar-xdr JSON serialization format. + +Here are the supported ScVal types and their formats: + +| Type | Format | Description | +| --- | --- | --- | +| U64 | `"u64": "1000000"` | Unsigned 64-bit integer | +| I64 | `"i64": "-500"` | Signed 64-bit integer | +| U32 | `"u32": 42` | Unsigned 32-bit integer | +| I32 | `"i32": -42` | Signed 32-bit integer | +| Boolean | `"bool": true` | Boolean value | +| String | `"string": "hello world"` | UTF-8 string | +| Symbol | `"symbol": "transfer"` | Symbol (used for function names and identifiers) | +| Address | `"address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY"` | Stellar account or contract address | +| Bytes | `"bytes": "deadbeef"` | Hex-encoded byte array | +| Vector | `"vec": ["u32": 1, "u32": 2, "u32": 3]` | Array of ScVal values | +| Map | `"map": ["key": "symbol": "name", "val": "string": "MyToken"]` | Key-value pairs of ScVal values | +| U128 | `"u128": "hi": "100", "lo": "200"` | Unsigned 128-bit integer (as high/low parts) | +| I128 | `"i128": "hi": "-100", "lo": "200"` | Signed 128-bit integer (as high/low parts) | +| U256 | `"u256": "hi_hi": "1", "hi_lo": "2", "lo_hi": "3", "lo_lo": "4"` | Unsigned 256-bit integer (as four 64-bit parts) | +| I256 | `"i256": "hi_hi": "-1", "hi_lo": "2", "lo_hi": "3", "lo_lo": "4"` | Signed 256-bit integer (as four 64-bit parts) | + +***Address Format Notes:*** +- Account addresses start with 'G' +- Contract addresses start with 'C' +- All addresses use SEP-23 Strkey encoding + +#### Authorization Modes + +Soroban operations support different authorization modes: + +| Type | Description | Example Usage | +| --- | --- | --- | +| `none` | No authorization required | `"auth": "type": "none"` | +| `source_account` | Use the transaction source account (default) | `"auth": "type": "source_account"` | +| `addresses` | Use specific addresses (future feature) | `"auth": "type": "addresses", "signers": ["GABC..."]` | +| `xdr` | Advanced: provide base64-encoded XDR entries | `"auth": "type": "xdr", "entries": [""]` | diff --git a/docs/content/relayer/1.0.x/changelog.mdx b/docs/content/relayer/1.0.x/changelog.mdx new file mode 100644 index 00000000..38c7b004 --- /dev/null +++ b/docs/content/relayer/1.0.x/changelog.mdx @@ -0,0 +1,505 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v1.1.0) - 2025-08-11 + +## [1.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v1.0.0...v1.1.0) (2025-08-11) + + +### 🚀 Features + +* Add Arbitrum support ([#373](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/373)) ([7b5372b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b5372bf54fe26756ca5db6cb393e0d9d79ae621)) +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* add evm intristic gas_limit validation ([dd1b2d6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dd1b2d6768d09f051791d0db68c912a38d273715)) +* Add get_status method for EVM and Stellar ([#229](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/229)) ([e84217e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e84217e0fa941fcd580ad6b84ab6bfac939dd5f4)) +* Add Launctube plugin example ([#414](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/414)) ([5bda763](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5bda7635f304923fcd4031f855009228eeefee4b)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add node support to environment ([#236](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/236)) ([3ab46f8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ab46f848e7e4c6dee2545d62dc646b33623d63d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* Add plugin invoker service ([#290](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/290)) ([489ce02](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/489ce0285cd88a18b1616af94bfc970a4a674228)) +* Add plugins call endpoint ([#279](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/279)) ([c278589](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c278589f4c6bf88be86788fdd9b68c2f166f5f33)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* Add status_reason field to transaction responses ([#369](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/369)) ([c489e5d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c489e5d39e3cec555caf92ac93266016c547b2bb)) +* Add stellar launchtube plugin ([#401](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/401)) ([801e2f7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/801e2f7efc8f0cb7eb54f545ce398e6ee24cf6b9)) +* Add support for feebumped tx ([#309](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/309)) ([b4efd2e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b4efd2e894fb6534b61a10c5f8872a73d923410c)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add support for stellar InvokeHostFunction transactions ([#284](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/284)) ([32ba63e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/32ba63e58e3dfc1359b7a5c9f61f9ff2a8b6c317)) +* Add support to plugin list endpoint ([#358](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/358)) ([6517af0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6517af0a753db41638b006fa2b896a3ccec0d4ef)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* Add transaction status handling for stellar ([#223](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/223)) ([9496eb6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9496eb63514afb0bd29c731bebe86ffdcf393362)) +* Add wait API for plugin transactions ([#345](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/345)) ([6069af2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6069af256e6cfe8470244731d4bb444b87bd175f)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Added resolve_plugin_path for script_path ([#340](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/340)) ([0b30739](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0b30739e51f5ef6c0b97c1da585d403496b2bbac)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* Create initial js plugins library ([#302](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/302)) ([98238e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/98238e9a6a30de8dba3bf8d308a82658e29de46f)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* Enhance Stellar tx handling with fee updates ([#368](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/368)) ([05617d7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/05617d7cb06ab378c2c2207f9d0a2e11a04cc472)) +* **evm:** Add AWS KMS signer support ([#287](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/287)) ([723a9a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/723a9a8d7e625dd3f52b2d678d0e1cd842053e06)) +* **evm:** Implement delete pending txs ([#289](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/289)) ([bc6f829](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc6f829e580d42359adebceeddaf38002390e10b)) +* **evm:** Implement json rpc endpoint ([#286](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/286)) ([91528aa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/91528aab82e3fa3cba08f63feb4ac9879aa8940e)) +* extract networks to json files ([#238](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/238)) ([5ac07b3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ac07b3c570485d7cdbc419a23f373867d7ebe81)) +* handle non-retriable errors and provider health errors ([#233](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/233)) ([7add348](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7add348da4d06af5ebebcce78d856485e9894ac3)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Implement get_balance method for StellarRelayer ([#228](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/228)) ([d92c75f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d92c75fe7da1b02ddb7a38df32f98082474e4cd9)) +* implement network config deserializing ([#235](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/235)) ([6d537f9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6d537f9298626fefc0d5a45c311a95208e1c8ef5)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* Pass arbitrary payloads to script exectution ([#312](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/312)) ([adecaf5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/adecaf5d73c3df9083c6a3fcf62ed669bc90b25c)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* plat-6215 add support for rpc failovers and retries ([#231](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/231)) ([ca6d24f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ca6d24f1bcdbb912795dcb1496519b49b5e81bf1)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6303 store solana submitted transactions to db and run status check logic ([#398](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/398)) ([e8420bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e8420bca02c20a53b02d9bedc8da1b7a784716dc)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* plat-6522 allow for the use of on chain defi to automatically swap spl ([#198](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/198)) ([dc9e2e2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dc9e2e2dd1d46830bc6479c1928a2e7ef7f91fb3)) +* plat-6571 add support for gcp signer ([#221](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/221)) ([0170fa1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0170fa12c3ecc64d1c48ed3a726358ed74d4596b)) +* Plat-6677 implement redis repositories for existing collections ([#350](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/350)) ([5fee731](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5fee731c5f19013f41a12a5b93af79d65bdf777e)) +* Plat-6679 implement startup logic to populate redis from config file ([#359](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/359)) ([5e1c0c8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5e1c0c825d3c1185a5c59360a2c857d79b46abba)) +* Plat-6681 expose crud api endpoints ([#365](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/365)) ([f3c3426](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f3c34266f3f035cd240105833ef4e67711cb0356)) +* Plat-6684 add support for transaction entries expiration ([#394](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/394)) ([6f6f765](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6f6f765556b2fc16764f8afe02ceedf268c26c13)) +* plat-6817 EVM add support for gas limit calculation ([#355](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/355)) ([dd1b2d6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dd1b2d6768d09f051791d0db68c912a38d273715)) +* plat-6873 add storage documentation ([#395](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/395)) ([ffd4ed5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ffd4ed58d322bad63be500a084a0b082ac7b59d9)) +* Plugins improvements ([#410](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/410)) ([648a0f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/648a0f121a6308e8bde0e09010d2e0c83de5c6ec)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Relayer plugins - add support to plugins in configs ([#253](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/253)) ([6a14239](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6a14239486900b2ef121b5de9e87410c412b65fe)) +* **replace_tx:** Implement replace tx for evm ([#272](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/272)) ([b48e71f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b48e71f55fda03bea83e90255b0d180db704cb52)) +* Set default network folder ([#313](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/313)) ([b28c99c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b28c99c43bedd921a55660622d845e63890e0d74)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* **signer:** Add GCP Signer to EVM ([#305](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/305)) ([a8817b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a8817b6c87c65731232d0a141338f3996aef2510)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Stellar RPC config ([#213](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/213)) ([6fd75ea](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd75ea65bf1a945ba891f99d83b0cdacdf30014)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) +* support for retries and failovers in EVM Provider ([#197](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/197)) ([542f21a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/542f21a9346def9b7fe47e0a29a2bbd5ab2af349)) +* Support plugin timeouts ([#348](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/348)) ([0a1c51e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0a1c51e9fe540ba570af25146538992a26b9a8a0)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Types introduced for plugin params and result ([#351](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/351)) ([dda83a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dda83a296fd5bd5bfca7f7902f4ca035e1bd8796)) +* Update Stellar network config and docs ([#380](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/380)) ([a4e1a0f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a4e1a0f38590f21c6d5e917a02fee4f6bef4f075)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Add memo validation for InvokeHostFunction ([#294](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/294)) ([6bb4ffa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6bb4ffaf9ceb4a8daef29ec5878595cca7041300)) +* change the ampersand to and, as as the shell interpret it ([#206](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/206)) ([d164d6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d164d6a4d63fbf0acdfe1330cf25147e86280af8)) +* Changing base image to wolfi, added node and npm ([#266](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/266)) ([1181996](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1181996dac6da52f96e164b1c937828a3940d5b8)) +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Config example file ([#285](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/285)) ([a020c6f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a020c6fcd6f9b638d955d5f2c99aa0e199d8bf6e)) +* Correct env var value in semgrep.yml ([#375](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/375)) ([2e98e21](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e98e2149135b97a62b90c302675379642fdf7b3)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* Docker readme file ([#339](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/339)) ([2db9933](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2db9933def061046cc3585a07249107a236ef98c)) +* docker-scan - chainguard issue ([#255](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/255)) ([c9ab94b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c9ab94bcee7b386a33b063504b3e6d2cf188d8b5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Implement stellar sequence sync and tx reset ([#367](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/367)) ([60b5deb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/60b5deb4915041d60a064cfac1a066406c339517)) +* Inheritance validation ([#374](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/374)) ([f8b921b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f8b921b4d6d85b8068428f1e34de121183a02179)) +* Make plugins entry in configs optional ([#300](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/300)) ([f299779](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f299779318429677fd672d4a2433828971a1b62e)) +* Minor fixes in Plugin docs ([#325](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/325)) ([33bb6a1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/33bb6a1841f2e84723e49cc81258a930241dc735)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) +* Plat-6815 resubmission bug ([#353](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/353)) ([72ac174](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/72ac17471e3a0a6ac35e9a9bb9ff8fe5e8b94bf2)) +* plat-6888 aws kms signer issue ([#411](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/411)) ([3c12c88](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3c12c88703c92526fe975eabba6ba0ffa9ca9c79)) +* Plugin result + adds tests for plugin ts lib ([#336](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/336)) ([b30246e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b30246e8922d3cb5bd3c5b92a7678f7591db5b97)) +* Relayer plugins format output ([#307](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/307)) ([8f25e5f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f25e5f55812e3d346c8bc0ff063cf07e2f0b753)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* remove the ci job dependant from the test job ([#222](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/222)) ([4056610](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40566108b66c701323145c2889ce0141b84714b8)) +* Replace automatic minor version bumps ([#315](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/315)) ([85784b4](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/85784b486a9508429ae94373a7f3db13d78b39d6)) +* Replace tx request body ([#326](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/326)) ([a20c916](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a20c916b592891b7a2afafd2e62b32723fc05dc2)) +* SBOM upload error ([#342](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/342)) ([1f9318e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1f9318e22cbe59ca03bc617b0986379574e5f770)) +* Semgrep CI integration ([#371](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/371)) ([6b9a6d2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6b9a6d24e22b78743f16c566026b34f9912669ad)) +* Semgrep send metrics value ([#381](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/381)) ([315ccbc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/315ccbca9a48816fc6e0c8133301aa3e3186ff93)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) +* Solve issues with new solana_sdk version ([#324](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/324)) ([ab97253](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab972533259506bb21e22ec7f899a45d2fc97db5)) +* Switch Redocly build to use standalone html file ([#291](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/291)) ([97a8698](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/97a86980bec6260920a469018fee0d3541d1a063)) +* syntax error in codeql.yml ([#385](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/385)) ([987fd33](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/987fd33566b66b2821490d0769a3c863a778c271)) +* Update configs and dockerfiles in examples ([#298](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/298)) ([2e505ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e505ad827ab7544f7c6a3fdf4018b1e9428f1d6)) +* Update semgrep.yml ([#347](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/347)) ([5ffb803](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ffb8036ca6d3fb5a8cdb34fa5484e7732c842a1)) +* Update Stellar API docs to match implementation ([#292](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/292)) ([96d95e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/96d95e35784c25f39afe626b56f11477fd213196)) +* Use unicode character for emoji ([#343](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/343)) ([784e89f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/784e89fae4ad2ddad037ddbbd0bec6df160e9a6a)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v1.0.0) - 2025-06-30 + +## [1.0.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.2.0...v1.0.0) (2025-06-30) + + +### 🚀 Features + +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* Add get_status method for EVM and Stellar ([#229](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/229)) ([e84217e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e84217e0fa941fcd580ad6b84ab6bfac939dd5f4)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add node support to environment ([#236](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/236)) ([3ab46f8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ab46f848e7e4c6dee2545d62dc646b33623d63d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* Add plugin invoker service ([#290](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/290)) ([489ce02](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/489ce0285cd88a18b1616af94bfc970a4a674228)) +* Add plugins call endpoint ([#279](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/279)) ([c278589](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c278589f4c6bf88be86788fdd9b68c2f166f5f33)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* Add support for feebumped tx ([#309](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/309)) ([b4efd2e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b4efd2e894fb6534b61a10c5f8872a73d923410c)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add support for stellar InvokeHostFunction transactions ([#284](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/284)) ([32ba63e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/32ba63e58e3dfc1359b7a5c9f61f9ff2a8b6c317)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* Add transaction status handling for stellar ([#223](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/223)) ([9496eb6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9496eb63514afb0bd29c731bebe86ffdcf393362)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* Create initial js plugins library ([#302](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/302)) ([98238e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/98238e9a6a30de8dba3bf8d308a82658e29de46f)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* **evm:** Add AWS KMS signer support ([#287](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/287)) ([723a9a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/723a9a8d7e625dd3f52b2d678d0e1cd842053e06)) +* **evm:** Implement delete pending txs ([#289](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/289)) ([bc6f829](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc6f829e580d42359adebceeddaf38002390e10b)) +* **evm:** Implement json rpc endpoint ([#286](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/286)) ([91528aa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/91528aab82e3fa3cba08f63feb4ac9879aa8940e)) +* extract networks to json files ([#238](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/238)) ([5ac07b3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ac07b3c570485d7cdbc419a23f373867d7ebe81)) +* handle non-retriable errors and provider health errors ([#233](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/233)) ([7add348](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7add348da4d06af5ebebcce78d856485e9894ac3)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Implement get_balance method for StellarRelayer ([#228](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/228)) ([d92c75f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d92c75fe7da1b02ddb7a38df32f98082474e4cd9)) +* implement network config deserializing ([#235](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/235)) ([6d537f9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6d537f9298626fefc0d5a45c311a95208e1c8ef5)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* Pass arbitrary payloads to script exectution ([#312](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/312)) ([adecaf5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/adecaf5d73c3df9083c6a3fcf62ed669bc90b25c)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* plat-6215 add support for rpc failovers and retries ([#231](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/231)) ([ca6d24f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ca6d24f1bcdbb912795dcb1496519b49b5e81bf1)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* plat-6522 allow for the use of on chain defi to automatically swap spl ([#198](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/198)) ([dc9e2e2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dc9e2e2dd1d46830bc6479c1928a2e7ef7f91fb3)) +* plat-6571 add support for gcp signer ([#221](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/221)) ([0170fa1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0170fa12c3ecc64d1c48ed3a726358ed74d4596b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Relayer plugins - add support to plugins in configs ([#253](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/253)) ([6a14239](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6a14239486900b2ef121b5de9e87410c412b65fe)) +* **replace_tx:** Implement replace tx for evm ([#272](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/272)) ([b48e71f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b48e71f55fda03bea83e90255b0d180db704cb52)) +* Set default network folder ([#313](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/313)) ([b28c99c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b28c99c43bedd921a55660622d845e63890e0d74)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* **signer:** Add GCP Signer to EVM ([#305](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/305)) ([a8817b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a8817b6c87c65731232d0a141338f3996aef2510)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Stellar RPC config ([#213](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/213)) ([6fd75ea](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd75ea65bf1a945ba891f99d83b0cdacdf30014)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) +* support for retries and failovers in EVM Provider ([#197](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/197)) ([542f21a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/542f21a9346def9b7fe47e0a29a2bbd5ab2af349)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Add memo validation for InvokeHostFunction ([#294](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/294)) ([6bb4ffa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6bb4ffaf9ceb4a8daef29ec5878595cca7041300)) +* change the ampersand to and, as as the shell interpret it ([#206](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/206)) ([d164d6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d164d6a4d63fbf0acdfe1330cf25147e86280af8)) +* Changing base image to wolfi, added node and npm ([#266](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/266)) ([1181996](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1181996dac6da52f96e164b1c937828a3940d5b8)) +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Config example file ([#285](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/285)) ([a020c6f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a020c6fcd6f9b638d955d5f2c99aa0e199d8bf6e)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* docker-scan - chainguard issue ([#255](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/255)) ([c9ab94b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c9ab94bcee7b386a33b063504b3e6d2cf188d8b5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Make plugins entry in configs optional ([#300](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/300)) ([f299779](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f299779318429677fd672d4a2433828971a1b62e)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) +* Relayer plugins format output ([#307](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/307)) ([8f25e5f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f25e5f55812e3d346c8bc0ff063cf07e2f0b753)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* remove the ci job dependant from the test job ([#222](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/222)) ([4056610](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40566108b66c701323145c2889ce0141b84714b8)) +* Replace automatic minor version bumps ([#315](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/315)) ([85784b4](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/85784b486a9508429ae94373a7f3db13d78b39d6)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) +* Update configs and dockerfiles in examples ([#298](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/298)) ([2e505ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e505ad827ab7544f7c6a3fdf4018b1e9428f1d6)) +* Update Stellar API docs to match implementation ([#292](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/292)) ([96d95e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/96d95e35784c25f39afe626b56f11477fd213196)) + +[Changes][v1.0.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.2.0) - 2025-05-14 + +## [0.2.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.1...v0.2.0) (2025-05-14) + + +### 🚀 Features + +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) + + +### 🐛 Bug Fixes + +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) + +[Changes][v0.2.0] + + + +# [v0.1.1](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.1.1) - 2025-04-08 + +## [0.1.1](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.0...v0.1.1) (2025-04-08) + + +### 🐛 Bug Fixes + +* Skip releases ([e79b2e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e79b2e963439721dd8e151fa0827654e4019df5f)) +* Skip releases with release please ([#158](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/158)) ([e79b2e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e79b2e963439721dd8e151fa0827654e4019df5f)) + + +### ⚙️ Miscellaneous Chores + +* Fix workflow and missing libs in docker file ([#157](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/157)) ([c7a681d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c7a681dea154b06b675a286e936606e2f9ce087b)) +* plat-7575 Docs fixes ([#153](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/153)) ([#154](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/154)) ([44257e8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44257e8ea3e658adbf40f69ad809e4e3503e9af4)) + +[Changes][v0.1.1] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.1.0) - 2025-04-07 + +## 0.1.0 (2025-04-07) + + +### 🚀 Features + +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) + + +### 📚 Documentation + +* add cargo docs ([#75](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/75)) ([c4dd8e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c4dd8e30525ccaeb563560bc2ef87cdcec5b1790)) +* Add testing instructions ([#107](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/107)) ([c7c2ed7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c7c2ed7772d99b4b68ced9fbf8835fa9e46da5e1)) +* Adding configuration documentation ([#48](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/48)) ([929cc1b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/929cc1bf1e0c6b3be872daf6654abe24eb79b907)) +* Fixing formatting and location of files ([#41](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/41)) ([4d4f153](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d4f1530f466a5bd597d0338559ccb33815286f0)) +* Move README to a similar format to Monitor ([#39](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/39)) ([5985339](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/59853396b3786a972ce7bbc793d4dbacc62fe6c0)) +* Readability improvements ([#133](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/133)) ([9220727](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9220727cc2b4349052c2d96a48c5d9c3012b38b9)) +* Small doc updates - policy field descriptions ([#51](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/51)) ([cc83c49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/cc83c496bbe2593018b03c414a864691c967ff41)) +* Small fixes and Antora docs improvements ([#40](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/40)) ([655d16d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/655d16dc658a74b7413ce785dee5b8e33cfb40f7)) +* TG link and other minor doc updates ([#116](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/116)) ([fc68b6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc68b6afa844d2c2638d031fce44fcc514d59a7d)) +* Update API docs. Fix Dockerfiles ([#77](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/77)) ([0bd6bfe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0bd6bfea69d60c1a7e9d6b8a690ba1a2d0e44b74)) + +[Changes][v0.1.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.2.0...v1.0.0 +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/tree/v0.1.0 diff --git a/docs/content/relayer/1.0.x/configuration/index.mdx b/docs/content/relayer/1.0.x/configuration/index.mdx new file mode 100644 index 00000000..3e40155a --- /dev/null +++ b/docs/content/relayer/1.0.x/configuration/index.mdx @@ -0,0 +1,469 @@ +--- +title: Configuration +--- + +## Overview + +Most configuration files should live under `./config`, including the signer configurations, under `./config/keys`. +Please ensure appropriate access permissions on all configuration files (for `./config/keys/*`, we recommend `0500`. + + + + +The configuration system consists of two main components: + +1. ***`config.json`***: Contains relayer definitions, signer configurations, and network policies +2. ***`.env`*** file: Contains environment variables like API keys and connection strings + +Both files must be properly configured before starting the application. Changes to either file require restarting the container to take effect. + +For quick setup examples with pre-configured files, see the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository. + + + +## Environment configuration (.env) + +This defines some base configurations for the Relayer application: + +Copy the example environment file and update values according to your needs + +```bash +cp .env.example .env +``` + +This table lists the environment variables and their default values. + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `RUST_LOG` | `info` | `info, debug, warn, error, trace` | Log level. | +| `CONFIG_DIR` | `./config` | `` | Relative path of directory where config files reside | +| `CONFIG_FILE_NAME` | `config.json` | `` | File Name of the configuration file. | +| `RATE_LIMIT_RPS` | `100` | `` | Rate limit for the API in requests per second. | +| `RATE_LIMIT_BURST_SIZE` | `300` | `` | Rate limit burst size. | +| `API_KEY` | `` | `string`, | API key to use for authentication to the relayer server. Minimum length 32 characters. | +| `WEBHOOK_SIGNING_KEY` | `` | `string` | Signing key to use for webhook notifications. Minimum length 32 characters. | +| `LOG_MODE` | `stdout` | `stdout, file` | Write logs either to console or to file. | +| `LOG_DATA_DIR` | `./logs` | `` | Directory to persist log files on host. | +| `LOG_MAX_SIZE (in bytes)` | `1073741824` | `` | Size after which logs needs to be rolled. | +| `METRICS_ENABLED` | `false` | `bool` | Enable metrics server for external tools to scrape metrics. | +| `METRICS_PORT` | `8081` | `` | Port to use for metrics server. | +| `REDIS_URL` | `redis://localhost:6379` | `` | Redis connection URL for the relayer. | +| `REDIS_CONNECTION_TIMEOUT_MS` | `10000` | `` | Connection timeout for Redis in milliseconds. | +| `RPC_TIMEOUT_MS` | `10000` | `` | Sets the maximum time to wait for RPC connections before timing out. | +| `PROVIDER_MAX_RETRIES` | `3` | `` | Maximum number of retry attempts for provider operations. | +| `PROVIDER_RETRY_BASE_DELAY_MS` | `100` | `` | Base delay between retry attempts in milliseconds. | +| `PROVIDER_RETRY_MAX_DELAY_MS` | `2000` | `` | Maximum delay between retry attempts in milliseconds. | +| `PROVIDER_MAX_FAILOVERS` | `3` | `` | Maximum number of failovers (switching to different providers). | +| `ENABLE_SWAGGER` | `false` | `true, false` | Enable or disable Swagger UI for API documentation. | +| `KEYSTORE_PASSPHRASE` | `` | `` | Passphrase for the keystore file used for signing transactions. | + +### Environment configuration example + +`.env` file config example: + +``` +RUST_LOG=DEBUG +CONFIG_DIR=./config +CONFIG_FILE_NAME=config.json +WEBHOOK_SIGNING_KEY=e1d42480-6f74-4d0b-85f4-b7f0bb690fae +API_KEY=5eefd216-0e44-4ca7-b421-2925f90d30d5 +RATE_LIMIT_RPS=100 +RATE_LIMIT_BURST_SIZE=300 +METRICS_ENABLED=true +METRICS_PORT=8081 +REDIS_URL=redis://localhost:6379 +REDIS_CONNECTION_TIMEOUT_MS=10000 +RPC_TIMEOUT_MS=10000 +PROVIDER_MAX_RETRIES=3 +PROVIDER_RETRY_BASE_DELAY_MS=100 +PROVIDER_RETRY_MAX_DELAY_MS=2000 +PROVIDER_MAX_FAILOVERS=3 +ENABLE_SWAGGER=false +KEYSTORE_PASSPHRASE=your_keystore_passphrase +``` + +## Main configuration file (config.json) + +This file can exist in any directory, but the default location is `./config/config.json`. + +Copy the example config file and update values according to your needs + +```bash +cp config/config.example.json config/config.json +``` + +Key sections in this file include: + +* Signers: Defines transaction signing methods. +* Notifications: Sets up status alerts +* Relayers: Configures networks, notifications channels, policies & singers. +* Networks: Defines blockchain network configurations. +* Plugins: Configures plugins. + +### 1. Signers + +Transaction signers are responsible for cryptographically signing transactions before they are submitted to blockchain networks. The `signers` array must contain at least one valid signer configuration. + +For comprehensive details on configuring all supported signer types including: + +* Local keystore file signers +* HashiCorp Vault (secret, cloud, and transit) +* Cloud KMS providers (Google Cloud, AWS) +* Turnkey signers +* Security best practices and troubleshooting + +See the dedicated [Signers Configuration](/relayer/1.0.x/configuration/signers) guide. + +### 2. Notifications + +* `notifications` array, which should contain, at least, one valid configuration: + +```json +"notifications": [ + { + "id": "notification-test", + "type": "webhook", + "url": "https://webhook.site/f95cf78d-742d-4b21-88b7-d683e6fd147b", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } +] +``` +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the notification | +| type | String | Type of notification (only `webhook` available, for now) | +| url | String | Notification URL | +| signing_key.type | String | Type of key used in signing the notification (`env` or `plain`) | +| signing_key.value | String | Signing key value, env variable name, ... | + +### 3. Relayers + +* `relayers` array, containing at least one valid relayer configuration: + +```json +"relayers": [ + { + "id": "solana-testnet", + "name": "Solana Testnet", + "paused": false, + "notification_id": "notification-test", + "signer_id": "local-signer", + "network_type": "solana", + "network": "testnet", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 // Higher weight routes more requests to this endpoint. The value must be an integer between 0 and 100 (inclusive). + }, + { + "url": "https://backup-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "allowed_programs": [ + "11111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "BPFLoaderUpgradeab1e11111111111111111111111" + ] + } + } +] +``` + +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the relayer | +| name | String | Human readable name for the relayer | +| paused | Boolean | Whether or not the relayer is paused (`true`, `false`) | +| notification_id | String | ID of a configured notification object | +| signer_id | String | ID of a configured signer | +| network_type | String | Type of network the relayer will connect to (`evm`, `solana`) | +| network | String | Network the relayer will connect to. Must match a network identifier defined in your network configuration files. See [Network Configuration](/relayer/1.0.x/network_configuration) for details on defining networks. | +| custom_rpc_urls | list | Optional custom RPC URLs for the network. If provided, this will be used instead of the public RPC URLs. This is useful for using your own RPC node or a paid service provider. The first url of the list is going to be used as the default | +| policies | list | Overrides default policies. Please refer to the [`Policies`](/relayer/1.0.x/configuration#network-policies) table | + +Policies +| Network type | Policy | Type | Description | +| --- | --- | --- | --- | +| solana, evm | min_balance | `unsigned 128` | Minimum balance (in lamports or wei) required for the relayer to operate. Optional. | +| solana | fee_payment_strategy | `enum(user,relayer)` | Specifies who pays the fee. "user" (default) means the sender pays; "relayer" means the relayer pays. For "user", RPC methods add an instruction to transfer SPL tokens (calculated from the current SOL price plus a configurable margin) from the user to the relayer, ensuring fees are sustainably covered in tokens rather than SOL. | +| solana | swap_config | `SwapConfig` | Optional object configuring automated token‐swaps on Solana. | +| solana | fee_margin_percentage | `f32` | Additional margin percentage added to estimated transaction fees to account for price fluctuations. For example, a value of 10 will add 10% to estimated fees. Optional. | +| solana | max_allowed_fee_lamports | `unsigned 64` | Maximum allowed fee (in lamports) for a transaction. Optional. | +| solana | allowed_tokens | `Vector` | List of allowed tokens. Only these tokens are supported if provided. Optional. | +| solana | allowed_programs | `Vector` | List of allowed programs by their identifiers. Only these programs are supported if provided. | +| solana | allowed_accounts | `Vector` | List of allowed accounts by their public keys. The relayer will only operate with these accounts if provided. | +| solana | disallowed_accounts | `Vector` | List of disallowed accounts by their public keys. These accounts will be explicitly blocked. | +| solana | max_tx_data_size | `unsigned 16` | Maximum transaction size. Optional. | +| solana | max_signatures | `unsigned 8` | Maximum supported signatures. Optional. | +| evm | gas_price_cap | `unsigned 128` | Specify a maximum gas price for every transaction sent with the Relayer. When enabled, any transaction exceeding the cap will have its gasPrice or maxFeePerGas overwritten. (Optional) | +| evm | whitelist_receivers | `Vector` | A list of authorized contracts for each transaction sent using the Relayer. Transactions will be rejected if the destination address is not on the list. (Optional) | + +#### RPC URL Configuration + +The relayer supports two ways to configure RPC URLs: + +1. ***Public RPC URLs***: These are the default RPC endpoints provided by the network. They are automatically selected based on the network configuration. +2. ***Custom RPC URLs***: You can specify custom RPC URLs using the `custom_rpc_urls` field in the relayer configuration. Each URL can be configured with an optional weight for high availability: + +```json +"custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 // Higher weight routes more requests to this endpoint. The value must be an integer between 0 and 100 (inclusive). + }, + { + "url": "https://secondary-rpc.example.com", + "weight": 100, // Max allowed weight + }, + { + "url": "https://backup-rpc.example.com" // No weight specified, defaults to 100 + }, + { + "url": "https://backup2-rpc.example.com", + "weight": 0, // A value of 0 disables the endpoint. + } +] +``` + +This is useful when you want to: + * Use your own RPC nodes with load balancing + * Use a paid service provider for better reliability and performance + * Override the default public RPC URLs + * Access custom network endpoints + * Configure primary and backup endpoints with different weights + +When both are available, the relayer will: +1. First attempt to use the `custom_rpc_urls` if configured. +2. Fall back to the public RPC URLs if no custom URL is configured. + +For backward compatibility, string arrays are still supported: + +```json +"custom_rpc_urls": ["https://your-rpc.example.com"] +``` + + + + +When using custom RPC URLs: + +* Ensure the URLs are secure (HTTPS) when accessing over public networks +* Keep your API keys and authentication tokens secure +* Test the RPC endpoints' reliability and performance before using it in production +* Configure weights to prioritize endpoints, assigning higher values to more reliable or performant ones. +* The weight must be an integer between 0 and 100 (inclusive). +* A weight of 0 disables the endpoint. +* If a weight is not specified for an endpoint, it defaults to 100. + + + +### 4. Plugins + +For more information on how to write a plugin, please refer to the [Plugins](/relayer/1.0.x/plugins) page. + +* `plugins` array, containing plugin configurations: + +```json +"plugins": [ + { + "id": "my-plugin", + "path": "my-plugin.ts" + } +] +``` + +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the plugin | +| path | String | Path to the plugin file | + +### 5. Networks + +You can configure networks either: + +* In separate JSON files (recommended for better organization) +* Directly in your main `config.json` + +For comprehensive network configuration details, including: + +* Network field reference +* Configuration examples for all network types +* Network inheritance +* Special tags and their behavior +* Best practices and troubleshooting + +See the dedicated [Network Configuration](/relayer/1.0.x/network_configuration) guide. + +## Configuration File Example + +Full `config/config.json` example with evm and solana relayers definitions using keystore signer: + +```json +{ + "relayers": [ + { + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "evm", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 + }, + { + "url": "https://backup-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "gas_price_cap": 30000000000000, + "eip1559_pricing": true + } + }, + { + "id": "solana-example", + "name": "Solana Example", + "network": "devnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": [ + { + "url": "https://primary-solana-rpc.example.com", + "weight": 2 + }, + { + "url": "https://backup-solana-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "allowed_tokens": [ + { + "mint": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "max_allowed_fee": 100000000 + }, + { + "mint": "So11111111111111111111111111111111111111112" + } + ] + } + }, + { + "id": "solana-mainnet-example", + "name": "Solana Mainnet Example", + "network": "mainnet-beta", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": ["https://your-private-solana-rpc.example.com"], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "swap_config": { + "cron_schedule": "0 0 * * * *", + "min_balance_threshold": 0, + "strategy": "jupiter-ultra" + }, + "allowed_tokens": [ + { + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "max_allowed_fee": 100000000, + "swap_config": { + "min_amount": 0, + "max_amount": 0, + "retain_min_amount": 0 + } + }, + { + "mint": "So11111111111111111111111111111111111111112" + } + ] + } + } + ], + "notifications": [ + { + "id": "notification-example", + "type": "webhook", + "url": "https://webhook.site/1384d4d9-21b1-40a0-bcd1-d3f3b66be955", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } + ], + "signers": [ + { + "id": "local-signer", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } + } + ], + "networks": [ + { + "average_blocktime_ms": 12000, + "chain_id": 11155111, + "explorer_urls": [ + "https://api-sepolia.etherscan.io/api", + "https://sepolia.etherscan.io" + ], + "features": [ + "eip1559" + ], + "is_testnet": true, + "network": "sepolia", + "required_confirmations": 6, + "rpc_urls": [ + "https://sepolia.drpc.org", + "https://1rpc.io/sepolia", + "https://ethereum-sepolia-rpc.publicnode.com", + "https://ethereum-sepolia-public.nodies.app" + ], + "symbol": "ETH", + "tags": [ + "deprecated" + ], + "type": "evm" + }, + { + "type": "solana", + "network": "devnet", + "rpc_urls": ["https://api.devnet.solana.com"], + "explorer_urls": ["https://explorer.solana.com?cluster=devnet"], + "average_blocktime_ms": 400, + "is_testnet": true + }, + { + "type": "solana", + "network": "mainnet-beta", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "average_blocktime_ms": 400, + "is_testnet": false + } + ] +} +``` diff --git a/docs/content/relayer/1.0.x/configuration/signers.mdx b/docs/content/relayer/1.0.x/configuration/signers.mdx new file mode 100644 index 00000000..efe9f74b --- /dev/null +++ b/docs/content/relayer/1.0.x/configuration/signers.mdx @@ -0,0 +1,369 @@ +--- +title: Signers Configuration +--- + +## Overview + +Signers are responsible for cryptographically signing transactions before they are submitted to blockchain networks. OpenZeppelin Relayer supports multiple signer types to accommodate different security requirements and infrastructure setups. + +The `signers` array in your configuration must contain at least one valid signer configuration. Each signer is referenced by its `id` in relayer configurations. + +## Configuration Structure + +Example signer configuration: +```json +"signers": [ + { + "id": "my_id", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } + } +] +``` + +## Supported Signer Types + +OpenZeppelin Relayer supports the following signer types: + +* `local`: Keystore file signer +* `vault`: HashiCorp Vault secret signer +* `vault_cloud`: Hosted HashiCorp Vault secret signer +* `vault_transit`: HashiCorp Vault Transit signer +* `turnkey`: Turnkey signer +* `google_cloud_kms`: Google Cloud KMS signer +* `aws_kms`: Amazon AWS KMS signer + +## Network Compatibility Matrix + +The following table shows which signer types are compatible with each network type: + +| Signer Type | EVM Networks | Solana Networks | Stellar Networks | +| --- | --- | --- | --- | +| `local` | ✅ Supported | ✅ Supported | ✅ Supported | +| `vault` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `vault_cloud` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `vault_transit` | ❌ Not supported | ✅ Supported | ❌ Not supported | +| `turnkey` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `google_cloud_kms` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `aws_kms` | ✅ Supported | ❌ Not supported | ❌ Not supported | + + + + +***Network-specific considerations:*** + +* ***EVM Networks***: Use secp256k1 cryptography. Most signers support EVM networks with proper key generation. +* ***Solana Networks***: Use ed25519 cryptography. Ensure your signer supports ed25519 key generation and signing. +* ***Stellar Networks***: Use ed25519 cryptography with specific Stellar requirements. Limited signer support due to network-specific implementation requirements. +* ***AWS KMS***: Currently optimized for EVM networks with secp256k1 support. +* ***Google Cloud KMS***: Supports both secp256k1 (EVM) and ed25519 (Solana) key types. +* ***Turnkey***: Supports EVM and Solana networks with appropriate key management. + + + +## Common Configuration Fields + +All signer types share these common configuration fields: + +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique identifier for the signer (used to reference this signer in relayer configurations) | +| type | String | Type of signer (see supported signer types above) | +| config | Map | Signer type-specific configuration object | + +## Local Signer Configuration + +The local signer uses encrypted keystore files stored on the filesystem. + +```json +{ + "id": "local-signer", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| path | String | Path to the signer JSON file. Should be under the `./config` directory | +| passphrase.type | String | Type of passphrase source (`env` or `plain`) | +| passphrase.value | String | Passphrase value or environment variable name | + +## HashiCorp Vault Signer Configuration + +### Vault Secret Signer + +Uses HashiCorp Vault’s secret engine to store private keys. + +```json +{ + "id": "vault-signer", + "type": "vault", + "config": { + "address": "https://vault.example.com", + "role_id": { + "type": "env", + "value": "VAULT_ROLE_ID" + }, + "secret_id": { + "type": "env", + "value": "VAULT_SECRET_ID" + }, + "key_name": "relayer-key", + "mount_point": "secret" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| address | String | Specifies the Vault API endpoint | +| role_id.type | String | Type of value source (`env` or `plain`) | +| role_id.value | String | The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored | +| secret_id.type | String | Type of value source (`env` or `plain`) | +| secret_id.value | String | The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored | +| key_name | String | The name of the cryptographic key within Vault’s Secret engine that is used for signing operations | +| mount_point | String | The mount point for the Secrets engine in Vault. Defaults to `secret` if not explicitly specified. Optional. | + +### Vault Cloud Signer + +Uses HashiCorp Vault Cloud (HCP Vault) for key management. + +```json +{ + "id": "vault-cloud-signer", + "type": "vault_cloud", + "config": { + "client_id": "your-client-id", + "client_secret": { + "type": "env", + "value": "VAULT_CLOUD_CLIENT_SECRET" + }, + "org_id": "your-org-id", + "project_id": "your-project-id", + "app_name": "relayer-app", + "key_name": "signing-key" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| client_id | String | The client identifier used to authenticate with Vault Cloud | +| client_secret.type | String | Type of value source (`env` or `plain`) | +| client_secret.value | String | The Vault secret value, or the environment variable name where the secret value is stored | +| org_id | String | The organization ID for your Vault Cloud account | +| project_id | String | The project ID that uniquely identifies your Vault Cloud project | +| app_name | String | The name of the application integrating with Vault Cloud | +| key_name | String | The name of the cryptographic key used for signing or encryption operations in Vault Cloud | + +### Vault Transit Signer + +Uses HashiCorp Vault’s Transit secrets engine for cryptographic operations. + +```json +{ + "id": "vault-transit-signer", + "type": "vault_transit", + "config": { + "address": "https://vault.example.com", + "role_id": { + "type": "env", + "value": "VAULT_ROLE_ID" + }, + "secret_id": { + "type": "env", + "value": "VAULT_SECRET_ID" + }, + "key_name": "relayer-transit-key", + "mount_point": "transit", + "namespace": "production", + "pubkey": "your-public-key-here" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| address | String | Specifies the Vault API endpoint | +| role_id.type | String | Type of value source (`env` or `plain`) | +| role_id.value | String | The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored | +| secret_id.type | String | Type of value source (`env` or `plain`) | +| secret_id.value | String | The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored | +| key_name | String | The name of the cryptographic key within Vault’s Transit engine that is used for signing operations | +| mount_point | String | The mount point for the Transit secrets engine in Vault. Defaults to `transit` if not explicitly specified. Optional. | +| namespace | String | The Vault namespace for API calls. This is used only in Vault Enterprise environments. Optional. | +| pubkey | String | Public key of the cryptographic key within Vault’s Transit engine that is used for signing operations | + +## Turnkey Signer Configuration + +Uses Turnkey’s secure key management infrastructure. + +```json +{ + "id": "turnkey-signer", + "type": "turnkey", + "config": { + "api_public_key": "your-api-public-key", + "api_private_key": { + "type": "env", + "value": "TURNKEY_API_PRIVATE_KEY" + }, + "organization_id": "your-org-id", + "private_key_id": "your-private-key-id", + "public_key": "your-public-key" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| api_public_key | String | The public key associated with your Turnkey API access credentials. Used for authentication to the Turnkey signing service | +| api_private_key.type | String | Type of value source (`env` or `plain`) | +| api_private_key.value | String | The Turnkey API private key or environment variable name containing it. Used with the public key to authenticate API requests | +| organization_id | String | Your unique Turnkey organization identifier. Required to access resources within your specific organization | +| private_key_id | String | The unique identifier of the private key in your Turnkey account that will be used for signing operations | +| public_key | String | The public key corresponding to the private key identified by private_key_id. Used for address derivation and signature verification | + +## Google Cloud KMS Signer Configuration + +Uses Google Cloud Key Management Service for secure key operations. + + + + +For EVM transaction signing, ensure your Google Cloud KMS key is created with: +- Protection level: HSM +- Purpose: Asymmetric sign +- Algorithm: "Elliptic Curve secp256k1 - SHA256 Digest" + +This provides secp256k1 compatibility required for Ethereum transactions. + + + +```json +{ + "id": "gcp-kms-signer", + "type": "google_cloud_kms", + "config": { + "service_account": { + "project_id": "your-gcp-project", + "private_key_id": { + "type": "env", + "value": "GCP_PRIVATE_KEY_ID" + }, + "private_key": { + "type": "env", + "value": "GCP_PRIVATE_KEY" + }, + "client_email": { + "type": "env", + "value": "GCP_CLIENT_EMAIL" + }, + "client_id": "your-client-id" + }, + "key": { + "location": "us-west2", + "key_ring_id": "relayer-keyring", + "key_id": "relayer-key", + "key_version": 1 + } + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| service_account.project_id | String | The Google Cloud project ID where your KMS resources are located | +| service_account.private_key_id.type | String | Type of value source for the private key ID (`env` or `plain`) | +| service_account.private_key_id.value | String | The private key ID value or the environment variable name containing it | +| service_account.private_key.type | String | Type of value source for the private key (`env` or `plain`) | +| service_account.private_key.value | String | The Google Cloud service account private key (PEM format) or the environment variable name containing it | +| service_account.client_email.type | String | Type of value source for the client email (`env` or `plain`) | +| service_account.client_email.value | String | The Google Cloud service account client email or the environment variable name containing it | +| service_account.client_id | String | The Google Cloud service account client ID | +| key.location | String | The Google Cloud location (region) where your KMS key ring is located (e.g., "us-west2", "global") | +| key.key_ring_id | String | The KMS key ring ID containing your cryptographic key | +| key.key_id | String | The KMS key ID used for signing operations | +| key.key_version | Integer | The version of the KMS key to use for signing operations. Defaults to 1 | + +## AWS KMS Signer Configuration + +Uses Amazon Web Services Key Management Service for cryptographic operations. + +```json +{ + "id": "aws-kms-signer", + "type": "aws_kms", + "config": { + "region": "us-west-2", + "key_id": "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| region | String | AWS region. If the key is non-replicated across regions, this must match the key’s original region. Optional. If not specified, the default region from shared credentials is used | +| key_id | String | ID of the key in AWS KMS (can be key ID, key ARN, alias name, or alias ARN) | + +## Security Best Practices + +### File Permissions +* Set restrictive permissions on keystore files: `chmod 0500 config/keys/*` +* Ensure configuration directories are properly secured +* Use environment variables for sensitive data like passphrases and API keys + +### Key Management +* Use HSM-backed keys for production environments when available +* Implement proper key rotation policies +* Never commit private keys or sensitive configuration to version control +* Use dedicated service accounts with minimal required permissions + +### Environment Separation +* Use different signers for different environments (development, staging, production) +* Implement proper secrets management in production deployments +* Consider using cloud-native key management services for enhanced security + +## Troubleshooting + +### Common Issues + +***Invalid keystore passphrase*** + +* Verify the passphrase environment variable is correctly set +* Check that the keystore file is not corrupted +* Ensure the keystore format is compatible + +***Cloud KMS authentication failures*** + +* Verify service account credentials are valid and properly formatted +* Check that the service account has necessary permissions for KMS operations +* Ensure the KMS key exists and is in the correct region/project + +***Vault connection issues*** + +* Verify Vault server address and network connectivity +* Check AppRole credentials and permissions +* Ensure the secret/transit engine is properly mounted and configured + +For additional troubleshooting help, check the application logs and refer to the specific cloud provider or service documentation. diff --git a/docs/content/relayer/1.0.x/index.mdx b/docs/content/relayer/1.0.x/index.mdx new file mode 100644 index 00000000..d3cb442a --- /dev/null +++ b/docs/content/relayer/1.0.x/index.mdx @@ -0,0 +1,336 @@ +--- +title: OpenZeppelin Relayer +--- + +## Overview + +OpenZeppelin Relayer is a service that provides infrastructure to relay transactions to the EVM & Non-EVM networks. It is designed to be used as a backend for dApps that need to interact with these networks. + +## Features + +* ***Multi-Chain Support***: Interact with multiple blockchain networks, including Solana and EVM-based chains. +* ***Transaction Relaying***: Submit transactions to supported blockchain networks efficiently. +* ***Transaction Signing***: Securely sign transactions using configurable key management. +* ***Transaction Fee Estimation***: Estimate transaction fees for better cost management. +* ***Solana Gasless Transactions***: Support for gasless transactions on Solana, enabling users to interact without transaction fees. +* ***Transaction Nonce Management***: Handle nonce management to ensure transaction order. +* ***Transaction Status Monitoring***: Track the status of submitted transactions. +* ***SDK Integration***: Easily interact with the relayer through our companion JavaScript/TypeScript SDK. +* ***Extensible Architecture***: Easily add support for new blockchain networks. +* ***Configurable Network Policies***: Define and enforce network-specific policies for transaction processing. +* ***Metrics and Observability***: Monitor application performance using Prometheus and Grafana. +* ***Docker Support***: Deploy the relayer using Docker for both development and production environments. +* ***Plugins***: Extend the functionality of the relayer with custom logic using TypeScript functions. + +## Supported Networks + +OpenZeppelin Relayer supports multiple blockchain networks through a flexible JSON-based configuration system. Networks are defined in configuration files, allowing you to configure: + +* ***Any EVM-compatible network*** (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* ***Solana networks*** (mainnet-beta, devnet, testnet, custom RPC endpoints) +* ***Stellar networks*** (Pubnet, Testnet, custom networks) +* ***Create custom network configurations*** with specific RPC endpoints, chain IDs, and network parameters +* ***Use inheritance*** to create network variants that inherit from base configurations + +### Network Types + +| Network Type | Description | +| --- | --- | +| `evm` | Ethereum Virtual Machine compatible networks. Supports any EVM chain by configuring chain ID, RPC URLs, and network-specific parameters. | +| `solana` | Solana blockchain networks. Supports all Solana clusters and custom RPC endpoints. | +| `stellar` | Stellar blockchain networks (Partial support). Supports Stellar Public Network and Testnet. | + +Networks can be loaded from: + +* ***JSON arrays***: Direct network definitions in configuration files +* ***Directory of files***: Multiple JSON files each containing network definitions + +For detailed network configuration options and examples, see the [Network Configuration](/relayer/1.0.x/network_configuration) page. + + + + +For information about our development plans and upcoming features, see [Project Roadmap](/relayer/1.0.x/roadmap). + + + + + + +To get started immediately, see [Quickstart](/relayer/1.0.x/quickstart). + + + +## Technical Overview + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +flowchart TB + subgraph "Clients" + client[API/SDK] + end + + subgraph "OpenZeppelin Relayer" + subgraph "API Layer" + api[API Routes & Controllers] + middleware[Middleware] + plugins[Plugins] + end + + subgraph "Domain Layer" + domain[Domain Logic] + relayer[Relayer Services] + policies[Policy Enforcement] + end + + subgraph "Infrastructure" + repositories[Repositories] + jobs[Job Queue System] + signer[Signer Services] + provider[Network Providers] + end + + subgraph "Services Layer" + transaction[Transaction Services] + vault[Vault Services] + webhook[Webhook Notifications] + monitoring[Monitoring & Metrics] + end + + subgraph "Configuration" + config_files[Config Files] + env_vars[Environment Variables] + end + end + + subgraph "External Systems" + blockchain[Blockchain Networks] + redis[Redis] + vault_ext[HashiCorp Vault] + metrics[Prometheus/Grafana] + notification[Notification Services] + end + + %% Client connections + client -- "HTTP Requests" --> api + + %% API Layer connections + api -- "Processes requests" --> middleware + middleware -- "Validates & routes" --> domain + middleware -- "Invokes" --> plugins + + %% Domain Layer connections + domain -- "Uses" --> relayer + domain -- "Enforces" --> policies + relayer -- "Processes" --> transaction + plugins -- "Interacts with" --> relayer + + %% Services Layer connections + transaction -- "Signs with" --> signer + transaction -- "Connects via" --> provider + transaction -- "Queues jobs" --> jobs + webhook -- "Notifies" --> notification + monitoring -- "Collects" --> metrics + signer -- "May use" --> vault + + %% Infrastructure connections + repositories -- "Stores data" --> redis + jobs -- "Processes async" --> redis + vault -- "Secrets management" --> vault_ext + provider -- "Interacts with" --> blockchain + + %% Configuration connections + config_files -- "Configures" --> domain + env_vars -- "Configures" --> domain + + %% Styling + classDef apiClass fill:#f9f,stroke:#333,stroke-width:2px + classDef domainClass fill:#bbf,stroke:#333,stroke-width:2px + classDef infraClass fill:#bfb,stroke:#333,stroke-width:2px + classDef serviceClass fill:#fbf,stroke:#333,stroke-width:2px + classDef configClass fill:#fbb,stroke:#333,stroke-width:2px + classDef externalClass fill:#ddd,stroke:#333,stroke-width:1px + + class api,middleware,plugins apiClass + class domain,relayer,policies domainClass + class repositories,jobs,signer,provider infraClass + class transaction,vault,webhook,monitoring serviceClass + class config_files,env_vars configClass + class blockchain,redis,vault_ext,metrics,notification externalClass +``` + +## Project Structure + +The project follows a standard Rust project layout: + +``` +openzeppelin-relayer/ +├── src/ +│ ├── api/ # Route and controllers logic +│ ├── bootstrap/ # Service initialization logic +│ ├── config/ # Configuration logic +│ ├── constants/ # Constant values used in the system +│ ├── domain/ # Domain logic +│ ├── jobs/ # Asynchronous processing logic (queueing) +│ ├── logging/ # Logs File rotation logic +│ ├── metrics/ # Metrics logic +│ ├── models/ # Data structures and types +│ ├── repositories/ # Configuration storage +│ ├── services/ # Services logic +│ └── utils/ # Helper functions +│ +├── config/ # Configuration files +├── tests/ # Integration tests +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── examples/ # Configuration examples +├── helpers/ # Rust helper scripts +├── plugins/ # Plugins directory +└── ... other root files (Cargo.toml, README.md, etc.) +``` + +For detailed information about each directory and its contents, see [Project Structure Details](/relayer/1.0.x/structure). + +## Getting Started + +### Prerequisites + +* Rust 2021 edition, version `1.85` or later +* Docker (optional, for containerized deployment) +* Node.js, typescript and ts-node (optional, for plugins) + + + +**Ready-to-Use Example Configurations** + +For quick setup with various configurations, check the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository: + +* `basic-example`: Simple setup with Redis +* `basic-example-logging`: Configuration with file-based logging +* `basic-example-metrics`: Setup with Prometheus and Grafana metrics +* `vault-secret-signer`: Using HashiCorp Vault for key management +* `vault-transit-signer`: Using Vault Transit for secure signing +* `evm-gcp-kms-signer`: Using Google Cloud KMS for EVM secure signing +* `evm-turnkey-signer`: Using Turnkey for EVM secure signing +* `solana-turnkey-signer`: Using Turnkey for Solana secure signing + +Each example includes a README with step-by-step instructions and Docker Compose configuration. + + +### Install Locally + +1. Clone the repository: + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-relayer + cd openzeppelin-relayer + ``` +2. Verify you have sodium libs installed. If not, follow these instructions: + + * Install a stable libsodium version from [here](https://download.libsodium.org/libsodium/releases/). + * Follow the steps in the [libsodium installation guide](https://doc.libsodium.org/installation). +3. Install dependencies: + + ```bash + cargo build + ``` + +## Running the Relayer + +### Option 1: Run Locally + +```bash +cargo run +``` + + +Before executing the command, ensure that the `.env` and `config.json` files are configured as detailed in the [Configuration References](#configuration_references) section. + + +### Option 2: Run with Docker + +The Relayer can be run as either a development or production container using the corresponding Dockerfile (`Dockerfile.development` or `Dockerfile.production`). + +#### Step 1: Configure Environment + +* Edit `.env` at the root of the repository to adjust environment variables +* The appropriate .env file will be included during image build + +#### Step 2: Build the Image + +You can build using Docker Compose (v2). + +```bash +# Default build +docker compose build + +# Or, for a leaner image (and using Dockerfile.production) +DOCKERFILE=Dockerfile.production docker compose build +``` + +#### Step 3: Run the Container + +Use Docker Compose to run the container: + +```bash +docker compose up -d +``` + +For production runs, you can use: + +```bash +DOCKERFILE=Dockerfile.production docker compose up -d +``` + +## Configuration + +OpenZeppelin Relayer requires proper configuration before starting. The configuration system uses two main files: + +* ***`config.json`***: Contains relayer definitions, signer configurations, and network policies +* ***`.env`***: Contains environment variables like API keys and connection strings + + + + +Both configuration files must be properly set up before starting the application. Changes to either file require restarting the container to take effect. + +For quick setup examples with pre-configured files, see the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository. + + + +For comprehensive configuration details, including: + +* Environment variables and their settings +* Main configuration file structure +* Signer configurations (local, vault, cloud KMS, etc.) +* Notification setup +* Relayer policies and network settings +* Plugin configuration +* Complete configuration examples + +See the dedicated [Configuration Guide](/relayer/1.0.x/configuration). + +## Important Considerations + +## Deployment Considerations + + +The OpenZeppelin Relayer is designed to function as a backend service and is not meant to be directly exposed to the public internet. To protect the service from unauthorized access, deploy it behind your own secure backend infrastructure—such as a reverse proxy or firewall—and restrict access to trusted internal components only. Direct exposure can increase the risk of exploitation and security breaches. + + +## Support + +For support or inquiries, contact us on [Telegram](https://t.me/openzeppelin_tg/2). + +## License +This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details. + +## Security +For security concerns, please refer to our [Security Policy](https://github.com/OpenZeppelin/openzeppelin-relayer/blob/main/SECURITY.md). diff --git a/docs/content/relayer/1.0.x/network_configuration.mdx b/docs/content/relayer/1.0.x/network_configuration.mdx new file mode 100644 index 00000000..76857d0a --- /dev/null +++ b/docs/content/relayer/1.0.x/network_configuration.mdx @@ -0,0 +1,381 @@ +--- +title: Network Configuration +--- + +The OpenZeppelin Relayer supports multiple blockchain networks through a flexible JSON-based configuration system. This guide covers everything you need to know about configuring networks for your relayer instances. + +## Overview + +Networks are defined in JSON configuration files, allowing you to: + +* Configure ***any EVM-compatible network*** (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* Set up ***Solana networks*** (mainnet-beta, devnet, testnet, custom RPC endpoints) +* Configure ***Stellar networks*** (Pubnet, Testnet, custom networks) +* Create ***custom network configurations*** with specific RPC endpoints, chain IDs, and network parameters +* Use ***inheritance*** to create network variants without duplicating configuration + +## Network Types + +| Network Type | Description | +| --- | --- | +| `evm` | Ethereum Virtual Machine compatible networks. Supports any EVM chain by configuring chain ID, RPC URLs, and network-specific parameters. | +| `solana` | Solana blockchain networks. Supports all Solana clusters and custom RPC endpoints. | +| `stellar` | Stellar blockchain networks. Supports Stellar Public Network and Testnet. | + +## Configuration Methods + +### Default Network Configuration + +If no `networks` field is specified in your `config.json`, the relayer will automatically load network configurations from the `./config/networks` directory. This is the default behavior. + +```json +{ + "relayers": [...], + "notifications": [...], + "signers": [...] + // No "networks" field - defaults to "./config/networks" +} +``` + + +Once you specify a `networks` field in your configuration, the default `./config/networks` directory will ***not*** be loaded automatically. If you want to use files from that directory, you must explicitly specify the path `"./config/networks"`. + + +You can configure networks in two ways: + +### Method 1: Separate JSON Files + +Specify the path to network configuration files in your main `config.json`: + +```json +{ + "relayers": [...], + "notifications": [...], + "signers": [...], + "networks": "./config/networks" // Path to directory or file +} +``` + + +This is the same as the default behavior, but explicitly specified. You can also point to a different directory or file path. + + +Each JSON file ***must*** contain a top-level `networks` array: + +```json +{ + "networks": [ + // ... network definitions ... + ] +} +``` + +When using a directory structure: +``` +networks/ +├── evm.json # {"networks": [...]} +├── solana.json # {"networks": [...]} +└── stellar.json # {"networks": [...]} +``` + +### Method 2: Direct Configuration + +Define networks directly in your main `config.json` instead of using separate files: + +```json +{ + "relayers": [...], + "notifications": [...], + "signers": [...], + "networks": [ + { + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, + // ... other fields + } + ] +} +``` + +When using this method, the default `./config/networks` directory is ignored, and only the networks defined in this array will be available. + +## Network Field Reference + +### Common Fields + +All network types support these configuration fields: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | Yes | Network type: `"evm"`, `"solana"`, or `"stellar"` | +| `network` | string | Yes | Unique network identifier (e.g., "ethereum-mainnet", "polygon-mumbai") | +| `from` | string | No | Name of parent network to inherit from (same type only) | +| `rpc_urls` | array[string] | Yes* | List of RPC endpoint URLs (*Required for base networks, optional for inherited) | +| `explorer_urls` | array[string] | No | List of blockchain explorer URLs | +| `average_blocktime_ms` | number | No | Estimated average time between blocks in milliseconds | +| `is_testnet` | boolean | No | Whether this is a testnet (affects behavior and validation) | +| `tags` | array[string] | No | Arbitrary tags for categorization and filtering | + +### Special Network Tags + +Some tags have special meaning and affect relayer behavior: + +| Tag | Description and Behavior | +| --- | --- | +| `rollup` | Identifies Layer 2 rollup networks (e.g., Arbitrum, Optimism, Base) | +| `optimism` | Identifies Optimism-based networks using the OP Stack (e.g., Optimism, Base, World Chain) | +| `no-mempool` | Indicates networks that lack a traditional mempool (e.g., Arbitrum) | +| `deprecated` | Marks networks that are deprecated and may be removed in future versions | + +#### Example: Using Special Tags + +Here’s an example showing how special tags are used in practice: + +```json +{ + "type": "evm", + "network": "arbitrum-one", + "chain_id": 42161, + "required_confirmations": 1, + "symbol": "ETH", + "rpc_urls": ["https://arb1.arbitrum.io/rpc"], + "tags": ["rollup", "no-mempool"], // Arbitrum is a rollup without mempool + "is_testnet": false +} +``` + +These tags help the relayer: + +* Apply specific transaction handling for rollups +* Use optimized fee calculation for OP Stack chains +* Skip mempool-related operations for networks without mempools +* Warn users about deprecated networks + +### EVM-Specific Fields + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `chain_id` | number | Yes* | Unique chain identifier (e.g., 1 for Ethereum mainnet, 137 for Polygon) (*Required for base networks, optional for inherited) | +| `required_confirmations` | number | Yes* | Number of block confirmations before considering a transaction final (*Required for base networks, optional for inherited) | +| `symbol` | string | Yes* | Native currency symbol (e.g., "ETH", "MATIC", "BNB") (*Required for base networks, optional for inherited) | +| `features` | array[string] | No | Supported features (e.g., ["eip1559", "london"]) | + +#### Example: EVM Network Configuration + +Here’s an example showing an EVM network configuration: + +```json +{ + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, // Ethereum mainnet chain ID + "required_confirmations": 12, // High security: 12 confirmations + "symbol": "ETH", // Native currency symbol + "features": ["eip1559"], // Supports EIP-1559 fee market + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"], + "is_testnet": false +} +``` + +### Solana-Specific Fields + +Currently, Solana networks use only the common fields. Additional Solana-specific configuration options may be added in future versions. + +### Stellar-Specific Fields + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `passphrase` | string | No | Network passphrase for transaction signing and network identification (optional for all networks, including base networks) | + +#### Example: Stellar Network Configuration + +Here’s an example showing a Stellar network configuration with passphrase: + +```json +{ + "type": "stellar", + "network": "pubnet", + "rpc_urls": ["https://horizon.stellar.org"], + "explorer_urls": ["https://stellar.expert/explorer/public"], + "passphrase": "Public Global Stellar Network ; September 2015", // Official mainnet passphrase + "average_blocktime_ms": 5000, + "is_testnet": false +} +``` + +## Configuration Examples + +### Basic EVM Network + +```json +{ + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, + "required_confirmations": 12, + "symbol": "ETH", + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"], + "explorer_urls": ["https://etherscan.io"], + "average_blocktime_ms": 12000, + "is_testnet": false, + "tags": ["mainnet", "ethereum"] +} +``` + +### Layer 2 EVM Network with Tags + +```json +{ + "type": "evm", + "network": "optimism", + "chain_id": 10, + "required_confirmations": 1, + "symbol": "ETH", + "rpc_urls": [ + "https://mainnet.optimism.io", + "https://optimism.drpc.org" + ], + "features": ["eip1559"], + "tags": ["rollup", "optimism"], + "average_blocktime_ms": 2000, + "is_testnet": false +} +``` + +### Solana Network + +```json +{ + "type": "solana", + "network": "mainnet-beta", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "average_blocktime_ms": 400, + "is_testnet": false, + "tags": ["mainnet", "solana"] +} +``` + +### Stellar Network + +```json +{ + "type": "stellar", + "network": "pubnet", + "rpc_urls": ["https://horizon.stellar.org"], + "passphrase": "Public Global Stellar Network ; September 2015", + "explorer_urls": ["https://stellar.expert/explorer/public"], + "average_blocktime_ms": 5000, + "is_testnet": false, + "tags": ["mainnet", "stellar"] +} +``` + +## Network Inheritance + +Networks can inherit from other networks of the same type, allowing you to create variants without duplicating configuration: + +```json +{ + "networks": [ + { + "type": "evm", + "network": "ethereum-base", + "chain_id": 1, + "required_confirmations": 12, + "symbol": "ETH", + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"] + }, + { + "from": "ethereum-base", + "type": "evm", + "network": "ethereum-sepolia", + "chain_id": 11155111, + "required_confirmations": 3, + "rpc_urls": ["https://sepolia.infura.io/v3/YOUR_KEY"], + "is_testnet": true + } + ] +} +``` + +When using inheritance: + +* The child network inherits all fields from the parent +* Fields specified in the child override parent values +* The `from` field must reference a network of the same type + +## Using Networks in Relayer Configuration + +Once networks are defined, reference them in your relayer configurations: + +```json +{ + "relayers": [ + { + "id": "my-evm-relayer", + "name": "My EVM Relayer", + "network": "ethereum-mainnet", // References network ID + "network_type": "evm", + "signer_id": "my-signer" + } + ] +} +``` + +## Best Practices + +### 1. Network Organization +* Group related networks in separate files (e.g., `ethereum.json`, `polygon.json`) +* Use consistent naming conventions for network identifiers +* Include both mainnet and testnet configurations + +### 2. RPC URLs +* Always configure multiple RPC URLs for redundancy +* Use private/dedicated RPC endpoints for production +* Ensure URLs are secure (HTTPS) when accessing over public networks + +### 3. Confirmation Requirements +* Set appropriate `required_confirmations` based on network security +* Higher values for mainnet, lower for testnets +* Consider network-specific finality characteristics + +### 4. Tags and Features +* Use tags to categorize networks (e.g., "mainnet", "testnet", "rollup") +* Enable appropriate features (e.g., "eip1559" for supported networks) +* Document custom tags used in your organization + +### 5. Inheritance +* Create base configurations for common settings +* Use inheritance to reduce duplication +* Override only necessary fields in child networks + +## Troubleshooting + +### Common Issues + +***Network not found:*** + +* Ensure the network identifier in relayer config matches exactly +* Check that network configuration files are in the correct location +* Verify JSON syntax is valid + +***RPC connection failures:*** + +* Test RPC URLs independently before configuring +* Ensure firewall/network allows outbound HTTPS connections +* Check API keys are included in RPC URLs where required + +***Invalid configuration:*** + +* Validate required fields are present for network type +* Ensure numeric fields (chain_id, confirmations) are numbers, not strings +* Check that inherited networks reference existing parent networks + +## See Also + +* [Relayer Configuration](/relayer/1.0.x#relayer_configuration) +* [Quickstart Guide](/relayer/1.0.x/quickstart) +* [Solana Integration](/relayer/1.0.x/solana) +* [API Reference](https://openzeppelin-relayer.netlify.app/api_docs.html) diff --git a/docs/content/relayer/1.0.x/plugins.mdx b/docs/content/relayer/1.0.x/plugins.mdx new file mode 100644 index 00000000..b38323a5 --- /dev/null +++ b/docs/content/relayer/1.0.x/plugins.mdx @@ -0,0 +1,180 @@ +--- +title: Plugins +--- + +## Overview + +OpenZeppelin Relayer supports plugins to extend the functionality of the relayer. + +Plugins are `TypeScript` functions running in the Relayer server that can include any arbitrary logic defined by the Relayer operator. + +It also includes a simple `Plugin` library to interact with the Relayer, allowing to send transactions, and +is extensible to support new features in the future. + +## Configuration + +### Writing a Plugin + +Plugins are declared under `plugins` directory, and are expected to be TypeScript files (`.ts` extension). + +```bash +openzeppelin-relayer/ +├── plugins/ +│ └── my-plugin.ts # Plugin code +└── config/ + └── config.json # Plugins in configuration file +``` + +The plugin code must include the following structure: + +```typescript +/// Required imports. +import { runPlugin, Plugin } from "./lib/plugin"; + +/// Here you can define custom params that will be included as part +/// of the request when the plugin is invoked. +type MyCustomParams = { + foo: string, + bar: number +} + +/// The plugin function body. +async function myPlugin(api: Plugin, params: MyCustomParams) { + // You can use the `params` to access the custom params passed to the plugin. + console.log(params.foo); + console.log(params.bar); + + // Api usage to send a transaction: + let relayer = api.relayer('my-relayer'); + const tx = await relayer.sendTransaction({ + to: "0x1234567890123456789012345678901234567890", + value: ethers.parseEther("1"), + }); +} + +/// `runPlugin` is the entry point for the plugin. +runPlugin(myPlugin); +``` + +### Declaring in config file + +Plugins are configured in the `./config/config.json` file, under the `plugins` key. + +The file contains a list of plugins, each with an id and path. + + +The plugin path is relative to the `/plugins` directory + + +Example: + +```json + +"plugins": [ + { + "id": "my-plugin", + "path": "my-plugin.ts" + } +] +``` + +## Invocation + +Plugins are invoked by hitting the `api/v1/plugins/plugin-id/call` endpoint. + +The endpoint accepts a `POST` request with the following body: + +```json +{ + "params": { + "foo": "bar", + "bar": 1 + } +} +``` + +Then the plugin will be invoked passing the `params` as the second argument of the plugin function. + +## Debugging + +When invoking a plugin, the response will include: + +* `logs`: The logs from the plugin execution. +* `return_value`: The returned value of the plugin execution. +* `error`: An error message if the plugin execution failed. +* `traces`: A list of messages sent between the plugin and the Relayer instance. This includes all the payloads passed through the `api` object. + +### Example + +1. Example Plugin code: + +```typescript +import { runPlugin, Plugin } from "./lib/plugin"; +import { ethers } from "ethers"; + +type MyCustomParams = { + foo: string, +} + +async function myPlugin(api: Plugin, params: MyCustomParams) { + console.log("Hello, world!"); + + // For example, to send a transaction: + let relayer = api.relayer('my-relayer'); + const tx = await relayer.sendTransaction({ + to: "0x1234567890123456789012345678901234567890", + value: ethers.parseEther("1"), + }); + + console.log("Foo ", params.foo); + + return `Successfully sent transaction with id: ${tx.id}`; +} + +runPlugin(myPlugin); +``` + +1. Example Invocation: + +```bash +curl -X POST http://localhost:3000/api/v1/plugins/my-plugin/call \ +-H "Content-Type: application/json" \ +-d '{"params": {"foo": "bar"}}' +``` + +1. Example Response: + +```json +{ + "success": true, + "message": "Plugin called successfully", + "logs": [ + { + "level": "log", + "message": "Hello, world!" + }, + { + "level": "log", + "message": "Foo bar" + } + ], + "return_value": "Successfully sent transaction with id: 1234567890", + "error": "", + "traces": [ + { + "relayer_id": "my-relayer", + "method": "sendTransaction", + "payload": { + "to": "0x1234567890123456789012345678901234567890", + "value": "1000000000000000000" + } + } + ] +} +``` + +Where: +- `logs` indicates the terminal logs (console.log, console.error, etc.) of the plugin. +- `traces` are the messages sent between the plugin and the Relayer instance. +- `error` will include the error message if the plugin fails. +- `return_value` will include the returned value of the plugin execution. diff --git a/docs/content/relayer/1.0.x/quickstart.mdx b/docs/content/relayer/1.0.x/quickstart.mdx new file mode 100644 index 00000000..d2496bae --- /dev/null +++ b/docs/content/relayer/1.0.x/quickstart.mdx @@ -0,0 +1,167 @@ +--- +title: Quick Start Guide +--- + +This guide provides step-by-step instructions for setting up OpenZeppelin Relayer. It includes prerequisites, installation, and configuration examples. + +## Prerequisites + +* Rust 2021, version `1.85` or later. +* Redis +* Docker (optional, for containerized deployment) +* Node.js, typescript and ts-node (optional, for plugins) + +## Configuration + +### Step 1: Clone the Repository + +Clone the repository and navigate to the project directory: + +```bash +git clone https://github.com/OpenZeppelin/openzeppelin-relayer +cd openzeppelin-relayer +``` + +### Step 2: Create Configuration Files + +Create environment configuration: + +```bash +cp .env.example .env +``` + +These files are already partially configured. We will add missing data in next steps. + +Ready-to-Use Example Configurations + +For quick setup with various configurations, check the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository: + +### Step 3: Create a Signer + +Generate a new signer keystore for the basic example: + +```bash +cargo run --example create_key -- \ + --password \ + --output-dir examples/basic-example/config/keys \ + --filename local-signer.json +``` +Replace `` with a strong password. + + + + +Your password must contain at least: + +* 12 characters +* One uppercase letter +* One lowercase letter +* One number +* One special character + + + +Next, update the `KEYSTORE_PASSPHRASE` in `.env` with the password you used above. + +### Step 4: Configure Notifications + +#### Configure Webhook URL + +Edit the file `config/config.json` and update the `notifications[0].url` field with your webhook URL. For a quick test, you can use a temporary URL from [Webhook.site](https://webhook.site). + +#### Configure Webhook Signing Key + +Generate a webhook signing key: + +```bash +cargo run --example generate_uuid +``` + + + + +Alternatively, you can use any online UUID generator tool if you don’t want to run the included command. + + + +Copy the generated UUID and update the `WEBHOOK_SIGNING_KEY` entry in `.env`. + +### Step 5: Configure API Key + +Generate an API key signing key for development: + +```bash +cargo run --example generate_uuid +``` + + + + +You can also use UUID generator with a simple command on your terminal. + +```bash +uuidgen +``` + +Alternatively, you can use any online UUID generator tool. + + + +Copy the generated UUID and update the `API_KEY` entry in `.env`. + +### Step 6: Run the Service + +#### Local + +Run Redis container: + +```sh +docker run --name openzeppelin-redis \ + -p 6379:6379 \ + -d redis:latest +``` + +Run Relayer service: + +```bash +cargo run +``` + +#### Docker + +Building and Running the docker image: + +```bash +docker compose up -d +``` + +By default docker compose command uses `Dockerfile.development` to build the image. If you want to use `Dockerfile.production`, you can use the following command: + +```bash +DOCKERFILE=Dockerfile.production docker compose up -d +``` + +### Step 7: Test the Relayer + +Verify the service by sending a GET request: + +```bash +curl -X GET http://localhost:8080/api/v1/relayers \ + -H "Content-Type: application/json" \ + -H "AUTHORIZATION: Bearer YOUR_API_KEY" +``` +Replace `YOUR_API_KEY` with the API key you configured in your `.env` file. + +Expected Result: A successful request should return an HTTP 200 status code along with the list of relayers. + +## Using the relayer through the API + +For detailed API usage, refer to the [API guide](/relayer/1.0.x/api_reference). The guide provides endpoint descriptions, usage examples, and best practices for integrating with the relayer service. + +## Using the relayer through the SDK + +For documentation and examples on how to consume Relayer service via SDK check [SDK documentation](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk). + +## Additional Resources and Troubleshooting + +Troubleshooting: If you encounter issues during setup or deployment, verify your environment variables, check container logs, and review your configuration files for syntax errors. diff --git a/docs/content/relayer/1.0.x/roadmap.mdx b/docs/content/relayer/1.0.x/roadmap.mdx new file mode 100644 index 00000000..0fbfbfbf --- /dev/null +++ b/docs/content/relayer/1.0.x/roadmap.mdx @@ -0,0 +1,113 @@ +--- +title: OpenZeppelin Relayer Roadmap +--- + +This document outlines the planned development roadmap for the OpenZeppelin Relayer project. Please note that priorities and timelines may shift based on community feedback, security considerations, and emerging blockchain ecosystem needs. + + + + +This roadmap represents our current plans and is subject to change. We will update this document regularly to reflect our progress and any changes in direction. + + + +## General Roadmap + +* **Stability Improvements** + * Enhanced error handling and recovery mechanisms: Implement robust exception management and failover processes to minimize downtime. + * Improved background job processing: Optimize task scheduling and queuing systems to ensure smooth and reliable asynchronous operations. + * Comprehensive test coverage: Extend unit, integration, and regression tests across all components. + * End-to-End (E2E) testing: Simulate real-world scenarios to verify complete system functionality. + * Performance optimizations: Enhance throughput and reduce latency for high-demand scenarios. + * Stress and load testing: Identify and address performance bottlenecks under extreme conditions. +* **Security Enhancements** + * External security audit: Engage third-party experts to identify and remediate vulnerabilities. + * Continuous security monitoring: Implement ongoing surveillance and incident response protocols to swiftly address threats. +* **Developer Experience** + * SDK improvements: Expand SDK capabilities, add multi-language support, and simplify integration processes. + * Enhanced documentation: Develop interactive guides, detailed API references, and comprehensive troubleshooting tips. + * Additional examples and best practices: Provide real-world usage scenarios and community-contributed tutorials to ease onboarding. +* **Features** + * Redis storage integration: Leverage Redis for fast, scalable data storage across all system components. + * Enhanced relayer balance management: Implement real-time monitoring and alerts to maintain optimal balance status. + * Dynamic gas price updates: Regularly fetch and update gas prices from multiple reliable sources to ensure accurate fee estimations. +* **Scaling Improvements** + * Horizontal scaling capabilities: Design the system architecture to seamlessly distribute workloads across multiple instances. + +## Network-Specific Roadmap + +### EVM Networks (🏗️ In Progress) + +#### Current Status +* Basic Transaction Submission +* Fee Estimation +* Transaction Status Tracking +* Flexible Network Configuration System (any EVM-compatible network via JSON configuration) +* Hosted signers support (AWS KMS, GCP, Turnkey) +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* L2 improvements +* SDK client improvements +* Full CRUD API support + +### Solana (🏗️ In Progress) + +#### Current Status +* Solana Paymaster Specification Support +* Fee estimation +* Gasless transactions +* Hosted Signer Integrations (Vault, GCP, Turnkey) +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* Extended RPC Methods +* Improved Transaction Status Checks +* Full CRUD API support + +### Stellar (🏗️ In Progress) + +#### Current Status +* Supports payment and InvokeHostFunction operations, pre-built XDR transactions, and fee bump transactions, +* Advanced transaction status logic +* Stellar-specific endpoints +* Expanded signer support +* Transaction lifecycle management logic +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* Relayer security policies: Transaction amount limits, destination whitelisting, time bound and limit operations +* Hosted signers +* Full CRUD API support + +## Community and Documentation + +### Continuous +* **Documentation** + * Comprehensive API reference + * Tutorials and guides + * Integration examples +* **Community Engagement** + * Contributing guidelines + * Support for community-driven improvements + +## Notes on Prioritization + + + + +Our development priorities are influenced by several factors: + +1. **Security**: Security enhancements always take precedence +2. **Stability**: Ensuring reliable operation across all supported networks +3. **Community Feedback**: Features requested by the community +4. **Ecosystem Developments**: Adapting to changes in blockchain protocols + + + +This roadmap is a living document and will be updated regularly to reflect changing priorities and completed milestones. We welcome community input on our direction and priorities. + +To contribute to discussions about the roadmap, please join our community channels or open an issue on our GitHub repository with your suggestions. diff --git a/docs/content/relayer/1.0.x/solana.mdx b/docs/content/relayer/1.0.x/solana.mdx new file mode 100644 index 00000000..dc84d013 --- /dev/null +++ b/docs/content/relayer/1.0.x/solana.mdx @@ -0,0 +1,214 @@ +--- +title: Solana Integration +--- + +## Overview + +OpenZeppelin Relayer provides robust support for Solana networks, enabling secure transaction relaying, automated token swaps, gasless transactions, and advanced fee management. This page covers everything you need to get started and make the most of Solana-specific features. + +## Features + +* Automated token swaps via Jupiter DEX (mainnet-beta only) +* Gasless transactions (user or relayer pays fees) +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and nonce management +* Custom RPC endpoints and network policies +* Metrics and observability + +## Supported Networks + +Solana networks are defined via JSON configuration files, providing flexibility to: + +* Configure standard Solana clusters: `mainnet-beta`, `devnet`, `testnet` +* Set up custom Solana-compatible networks with specific RPC endpoints +* Create network variants using inheritance from base configurations + +Example Solana network configurations: + +```json +{ + "networks": [ + { + "type": "solana", + "network": "solana-mainnet", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "is_testnet": false, + "tags": ["mainnet", "solana"] + }, + { + "type": "solana", + "network": "solana-devnet", + "rpc_urls": ["https://api.devnet.solana.com"], + "explorer_urls": ["https://explorer.solana.com?cluster=devnet"], + "is_testnet": true, + "tags": ["devnet", "solana"] + }, + { + "type": "solana", + "network": "solana-custom", + "rpc_urls": ["https://your-custom-solana-rpc.example.com"], + "tags": ["custom", "solana"] + } + ] +} +``` + +For detailed network configuration options, see the [Network Configuration](/relayer/1.0.x/network_configuration) guide. + +## Supported Signers + +* `vault_transit` (hosted) +* `turnkey` (hosted) +* `google_cloud_kms` (hosted) +* `local` (local) +* `vault` (local) +* `vault_cloud` (local) + + + + +In production systems, hosted signers are recommended for the best security model. + + + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/1.0.x/quickstart). +Key prerequisites: + +* Rust 2021, version `1.85` or later +* Redis +* Docker (optional) + +Example configuration for a Solana relayer: +```json +{ + "id": "solana-example", + "name": "Solana Example", + "network": "devnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": [ + { "url": "https://primary-solana-rpc.example.com", "weight": 100 }, + { "url": "https://backup-solana-rpc.example.com", "weight": 100 } + ], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "allowed_tokens": [ + { "mint": "So111...", "max_allowed_fee": 100000000 } + ], + "swap_config": { + "strategy": "jupiter-swap", + "cron_schedule": "0 0 * * * *", + "min_balance_threshold": 1000000, + "jupiter_swap_options": { + "dynamic_compute_unit_limit": true, + "priority_level": "high", + "priority_fee_max_lamports": 1000000000 + } + } + } +} +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository, window=_blank](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +In addition to standard relayer configuration and policies, Solana relayers support additional options: + +* `fee_payment_strategy`: `"user"` or `"relayer"` (who pays transaction fees) +* `allowed_tokens`: List of SPL tokens supported for swaps and fee payments +* `allowed_programs`, `allowed_accounts`, `disallowed_accounts`: Restrict relayer operations to specific programs/accounts +* `swap_config`: Automated token swap settings (see below) + +You can check all options in [User Documentation - Relayers](/relayer/1.0.x#3_relayers). + +### Automated token swap configuration options: + +* `strategy`: The swap engine to use. Supported values: `"jupiter-swap"` (Jupiter Swap API), `"jupiter-ultra"` (Jupiter Ultra API). +* `cron_schedule`: Cron expression defining how often scheduled swaps should run (e.g., `"0 0 * * * *"` for every hour). +* `min_balance_threshold`: Minimum token balance (in lamports) that triggers a swap. If the relayer’s balance drops below this, a swap is attempted. +* `jupiter_swap_options`: Advanced options for Jupiter swaps, such as: + * `dynamic_compute_unit_limit`: If `true`, dynamically adjusts compute units for swap transactions. + * `priority_level`: Priority for the swap transaction. Supported values: `"medium"`, `"high"`, `"veryHigh"`. + * `priority_fee_max_lamports`: Maximum priority fee (in lamports) to pay for a swap transaction. +* Per-token swap limits: + * `min_amount`: Minimum amount of a token to swap in a single operation. + * `max_amount`: Maximum amount of a token to swap in a single operation. + * `retain_min_amount`: Minimum amount of a token to retain in the relayer account after a swap (prevents swapping the entire balance). + +## Automated Token Swaps + +The relayer can perform automated token swaps on Solana when user fee_payment_strategy is used for relayer using: + +* ***jupiter-swap*** – via the Jupiter Swap API +* ***jupiter-ultra*** – via the Jupiter Ultra API + +Swaps can be set to work as: + +* ***Scheduled Swaps***: Background jobs run swaps based on your cron schedule. +* ***On-Demand Swaps***: If a transaction fails due to insufficient funds, the relayer attempts a swap before returning an error. + +## API Reference + +The Solana API conforms to the [Paymaster spec, window=_blank](https://docs.google.com/document/d/1lweO5WH12QJaSAu5RG_wUistyk_nFeT6gy1CdvyCEHg/edit?tab=t.0#heading=h.4yldgprkuvav). + +Common endpoints: +- `POST /api/v1/relayers//rpc` + Methods: + +* `feeEstimate`, +* `prepareTransaction`, +* `transferTransaction`, +* `signTransaction`, +* `signAndSendTransaction`, +* `getSupportedTokens` +* `getSupportedFeatures` + +Example: Estimate fee for a transaction +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "feeEstimate", + "params": { + "transaction": "", + "fee_token": "" + }, + "id": 1 +}' +``` + +See [API Reference](/relayer/1.0.x/api_reference) and [SDK examples, window=_blank](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/tree/main/examples/solana) for full details and examples. + +## Security + +* Do not expose the relayer directly to the public internet. +* Deploy behind a secure backend (reverse proxy, firewall). +* Use hosted signers in production systems. + +## Troubleshooting + +* Check environment variables and configuration files for errors +* Review container logs for issues + +## Roadmap + +* See [Project Roadmap](/relayer/1.0.x/roadmap) for upcoming features + +## Support + +For help, join our [Telegram](https://t.me/openzeppelin_tg/2) or open an issue on GitHub. + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/1.0.x/structure.mdx b/docs/content/relayer/1.0.x/structure.mdx new file mode 100644 index 00000000..0704bb4a --- /dev/null +++ b/docs/content/relayer/1.0.x/structure.mdx @@ -0,0 +1,106 @@ +--- +title: Project Structure +--- + +This document provides detailed information about each directory in the OpenZeppelin Relayer project. + +## Source Code Organization + +### `src/` Directory +The main source code directory contains the core implementation files organized into several modules: + +* `api/`: Route and controllers logic + * Manages HTTP routing and delegates incoming requests to controllers +* `bootstrap/`: Service initialization + * Bootstraps and initializes application services +* `config/`: Configuration management + * Handles system configuration and environment settings +* `constants/`: Global constants + * Provides static values used across the application +* `domain/`: Business domain logic + * Encapsulates core business rules and domain-specific functionality +* `jobs/`: Asynchronous job processing + * Manages background task queueing and execution +* `logging/`: Logging and file rotation + * Implements logging functionalities and log file management +* `metrics/`: Metrics collection + * Collects and reports application performance and usage metrics +* `models/`: Core data models and types + * Defines data structures and type definitions for the system +* `repositories/`: Configuration storage + * Provides interfaces for storing and retrieving configuration data +* `services/`: Business service logic + * Implements core business functionalities and service operations +* `utils/`: Utility functions + * Offers helper functions and common utilities for the application + +## Documentation + +### `docs/` Directory +Project documentation: + +* User guides +* API documentation +* Configuration examples +* Architecture diagrams + +## Configuration + +### `config/` Directory + +Houses system configuration file and keys: + +* `config.json` configuration file +* keystore files referenced from config.json file + +## Tests + +### `test/` Directory + +Includes comprehensive testing suites to ensure system reliability: + +* End-to-end tests that simulate real-world user scenarios + +## Scripts + +### `scripts/` Directory + +Utility scripts. + +## Examples + +### `examples/` Directory + +Provides practical examples and sample configurations to help users get started: + +* Demonstrates typical service configurations for various environments +* Acts as a quick-start guide for customizing and deploying the relayer +* Serves as a reference for best practices in configuration and deployment + +## Development Tools + +### Pre-commit Hooks +Located in the project root: + +* Code formatting checks +* Linting rules +* Commit message validation + +### Build Configuration +Core build files: + +* `Cargo.toml`: Project dependencies and metadata +* `rustfmt.toml`: Code formatting rules +* `rust-toolchain.toml`: Rust version and components + +## Docker Support + +The project includes Docker configurations for different environments: + +* `Dockerfile.development`: Development container setup +* `Dockerfile.production`: Production-ready container + + + +For detailed information about running the relayers in containers, see the Docker deployment section in the main documentation. + diff --git a/docs/content/relayer/1.1.x/api/callPlugin.mdx b/docs/content/relayer/1.1.x/api/callPlugin.mdx new file mode 100644 index 00000000..e80f5752 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/callPlugin.mdx @@ -0,0 +1,15 @@ +--- +title: Calls a plugin method. +full: true +_openapi: + method: POST + route: /api/v1/plugins/{plugin_id}/call + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/cancelTransaction.mdx b/docs/content/relayer/1.1.x/api/cancelTransaction.mdx new file mode 100644 index 00000000..342de9b2 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/cancelTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Cancels a specific transaction by its ID. +full: true +_openapi: + method: DELETE + route: /api/v1/relayers/{relayer_id}/transactions/{transaction_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/createNotification.mdx b/docs/content/relayer/1.1.x/api/createNotification.mdx new file mode 100644 index 00000000..d7d916be --- /dev/null +++ b/docs/content/relayer/1.1.x/api/createNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Creates a new notification. +full: true +_openapi: + method: POST + route: /api/v1/notifications + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/createRelayer.mdx b/docs/content/relayer/1.1.x/api/createRelayer.mdx new file mode 100644 index 00000000..6004316f --- /dev/null +++ b/docs/content/relayer/1.1.x/api/createRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Creates a new relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/createSigner.mdx b/docs/content/relayer/1.1.x/api/createSigner.mdx new file mode 100644 index 00000000..5cf9ec01 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/createSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Creates a new signer. +full: true +_openapi: + method: POST + route: /api/v1/signers + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/deleteNotification.mdx b/docs/content/relayer/1.1.x/api/deleteNotification.mdx new file mode 100644 index 00000000..0f81bb7e --- /dev/null +++ b/docs/content/relayer/1.1.x/api/deleteNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes a notification by ID. +full: true +_openapi: + method: DELETE + route: /api/v1/notifications/{notification_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/deletePendingTransactions.mdx b/docs/content/relayer/1.1.x/api/deletePendingTransactions.mdx new file mode 100644 index 00000000..52fccbfd --- /dev/null +++ b/docs/content/relayer/1.1.x/api/deletePendingTransactions.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes all pending transactions for a specific relayer. +full: true +_openapi: + method: DELETE + route: /api/v1/relayers/{relayer_id}/transactions/pending + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/deleteRelayer.mdx b/docs/content/relayer/1.1.x/api/deleteRelayer.mdx new file mode 100644 index 00000000..19dc6cde --- /dev/null +++ b/docs/content/relayer/1.1.x/api/deleteRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes a relayer by ID. +full: true +_openapi: + method: DELETE + route: /api/v1/relayers/{relayer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/deleteSigner.mdx b/docs/content/relayer/1.1.x/api/deleteSigner.mdx new file mode 100644 index 00000000..8ff8d171 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/deleteSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes a signer by ID. +full: true +_openapi: + method: DELETE + route: /api/v1/signers/{signer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getNotification.mdx b/docs/content/relayer/1.1.x/api/getNotification.mdx new file mode 100644 index 00000000..683be6fa --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves details of a specific notification by ID. +full: true +_openapi: + method: GET + route: /api/v1/notifications/{notification_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getRelayer.mdx b/docs/content/relayer/1.1.x/api/getRelayer.mdx new file mode 100644 index 00000000..ab762fed --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves details of a specific relayer by ID. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getRelayerBalance.mdx b/docs/content/relayer/1.1.x/api/getRelayerBalance.mdx new file mode 100644 index 00000000..ac75d13c --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getRelayerBalance.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves the balance of a specific relayer. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/balance + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getRelayerStatus.mdx b/docs/content/relayer/1.1.x/api/getRelayerStatus.mdx new file mode 100644 index 00000000..a2cb963b --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getRelayerStatus.mdx @@ -0,0 +1,15 @@ +--- +title: Fetches the current status of a specific relayer. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/status + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getSigner.mdx b/docs/content/relayer/1.1.x/api/getSigner.mdx new file mode 100644 index 00000000..eadf133a --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves details of a specific signer by ID. +full: true +_openapi: + method: GET + route: /api/v1/signers/{signer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getTransactionById.mdx b/docs/content/relayer/1.1.x/api/getTransactionById.mdx new file mode 100644 index 00000000..77c008b2 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getTransactionById.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves a specific transaction by its ID. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/transactions/{transaction_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/getTransactionByNonce.mdx b/docs/content/relayer/1.1.x/api/getTransactionByNonce.mdx new file mode 100644 index 00000000..810eb101 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/getTransactionByNonce.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves a transaction by its nonce value. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/transactions/by-nonce/{nonce} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/health.mdx b/docs/content/relayer/1.1.x/api/health.mdx new file mode 100644 index 00000000..bf6af450 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/health.mdx @@ -0,0 +1,29 @@ +--- +title: Health routes implementation +full: true +_openapi: + method: GET + route: /v1/health + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + Handles the `/health` endpoint. + + + Returns an `HttpResponse` with a status of `200 OK` and a body of + `"OK"`. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file +Handles the `/health` endpoint. + +Returns an `HttpResponse` with a status of `200 OK` and a body of `"OK"`. + + diff --git a/docs/content/relayer/1.1.x/api/index.mdx b/docs/content/relayer/1.1.x/api/index.mdx new file mode 100644 index 00000000..89580fb1 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/index.mdx @@ -0,0 +1,114 @@ +--- +title: Relayer API Reference +--- + +## Relayers + +### [List Relayers](./api/listRelayers) +Lists all relayers with pagination support + +### [Create Relayer](./api/createRelayer) +Creates a new relayer + +### [Get Relayer](./api/getRelayer) +Retrieves details of a specific relayer by ID + +### [Delete Relayer](./api/deleteRelayer) +Deletes a relayer by ID + +### [Update Relayer](./api/updateRelayer) +Updates a relayer's information + +### [Get Relayer Balance](./api/getRelayerBalance) +Retrieves the balance of a specific relayer + +### [RPC](./api/rpc) +Performs a JSON-RPC call using the specified relayer + +### [Sign](./api/sign) +Signs data using the specified relayer + +### [Sign Transaction](./api/signTransaction) +Signs a transaction using the specified relayer (Stellar only) + +### [Sign Typed Data](./api/signTypedData) +Signs typed data using the specified relayer + +### [Get Relayer Status](./api/getRelayerStatus) +Fetches the current status of a specific relayer + +### [Send Transaction](./api/sendTransaction) +Sends a transaction through the specified relayer + +### [List Transactions](./api/listTransactions) +Lists all transactions for a specific relayer with pagination + +### [Get Transaction by Nonce](./api/getTransactionByNonce) +Retrieves a transaction by its nonce value + +### [Delete Pending Transactions](./api/deletePendingTransactions) +Deletes all pending transactions for a specific relayer + +### [Get Transaction by ID](./api/getTransactionById) +Retrieves a specific transaction by its ID + +### [Replace Transaction](./api/replaceTransaction) +Replaces a specific transaction with a new one + +### [Cancel Transaction](./api/cancelTransaction) +Cancels a specific transaction by its ID + +## Plugins + +### [Call Plugin](./api/callPlugin) +Calls a plugin method + +## Notifications + +### [List Notifications](./api/listNotifications) +Lists all notifications with pagination support + +### [Create Notification](./api/createNotification) +Creates a new notification + +### [Get Notification](./api/getNotification) +Retrieves details of a specific notification by ID + +### [Delete Notification](./api/deleteNotification) +Deletes a notification by ID + +### [Update Notification](./api/updateNotification) +Updates an existing notification + +## Signers + +### [List Signers](./api/listSigners) +Lists all signers with pagination support + +### [Create Signer](./api/createSigner) +Creates a new signer + +### [Get Signer](./api/getSigner) +Retrieves details of a specific signer by ID + +### [Delete Signer](./api/deleteSigner) +Deletes a signer by ID + +### [Update Signer](./api/updateSigner) +Updates an existing signer + +## Metrics + +### [Scrape Metrics](./api/scrape_metrics) +Triggers an update of system metrics and returns the result in plain text format + +### [List Metrics](./api/list_metrics) +Returns a list of all available metric names in JSON format + +### [Metric Detail](./api/metric_detail) +Returns the details of a specific metric in plain text format + +## Health + +### [Health](./api/health) +Health routes implementation \ No newline at end of file diff --git a/docs/content/relayer/1.1.x/api/listNotifications.mdx b/docs/content/relayer/1.1.x/api/listNotifications.mdx new file mode 100644 index 00000000..e736cba8 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/listNotifications.mdx @@ -0,0 +1,25 @@ +--- +title: Notification routes implementation +full: true +_openapi: + method: GET + route: /api/v1/notifications + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + + Lists all notifications with pagination support. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file + +Lists all notifications with pagination support. + + diff --git a/docs/content/relayer/1.1.x/api/listRelayers.mdx b/docs/content/relayer/1.1.x/api/listRelayers.mdx new file mode 100644 index 00000000..073c35b4 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/listRelayers.mdx @@ -0,0 +1,25 @@ +--- +title: Relayer routes implementation +full: true +_openapi: + method: GET + route: /api/v1/relayers + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + + Lists all relayers with pagination support. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file + +Lists all relayers with pagination support. + + diff --git a/docs/content/relayer/1.1.x/api/listSigners.mdx b/docs/content/relayer/1.1.x/api/listSigners.mdx new file mode 100644 index 00000000..78a67216 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/listSigners.mdx @@ -0,0 +1,25 @@ +--- +title: Signer routes implementation +full: true +_openapi: + method: GET + route: /api/v1/signers + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + + Lists all signers with pagination support. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file + +Lists all signers with pagination support. + + diff --git a/docs/content/relayer/1.1.x/api/listTransactions.mdx b/docs/content/relayer/1.1.x/api/listTransactions.mdx new file mode 100644 index 00000000..2a90441d --- /dev/null +++ b/docs/content/relayer/1.1.x/api/listTransactions.mdx @@ -0,0 +1,15 @@ +--- +title: Lists all transactions for a specific relayer with pagination. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/transactions/ + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/list_metrics.mdx b/docs/content/relayer/1.1.x/api/list_metrics.mdx new file mode 100644 index 00000000..8ea7a892 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/list_metrics.mdx @@ -0,0 +1,33 @@ +--- +title: Metrics routes implementation +full: true +_openapi: + method: GET + route: /metrics + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + Returns a list of all available metric names in JSON format. + + + # Returns + + + An `HttpResponse` containing a JSON array of metric names. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file +Returns a list of all available metric names in JSON format. + +# Returns + +An `HttpResponse` containing a JSON array of metric names. + + diff --git a/docs/content/relayer/1.1.x/api/metric_detail.mdx b/docs/content/relayer/1.1.x/api/metric_detail.mdx new file mode 100644 index 00000000..e252ce6f --- /dev/null +++ b/docs/content/relayer/1.1.x/api/metric_detail.mdx @@ -0,0 +1,38 @@ +--- +title: Returns the details of a specific metric in plain text format. +full: true +_openapi: + method: GET + route: /metrics/{metric_name} + toc: [] + structuredData: + headings: [] + contents: + - content: >- + # Parameters + + + - `path`: The name of the metric to retrieve details for. + + + # Returns + + + An `HttpResponse` containing the metric details in plain text, or a + 404 error if the metric is + + not found. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +# Parameters + +- `path`: The name of the metric to retrieve details for. + +# Returns + +An `HttpResponse` containing the metric details in plain text, or a 404 error if the metric is +not found. + + diff --git a/docs/content/relayer/1.1.x/api/replaceTransaction.mdx b/docs/content/relayer/1.1.x/api/replaceTransaction.mdx new file mode 100644 index 00000000..6ff72edd --- /dev/null +++ b/docs/content/relayer/1.1.x/api/replaceTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Replaces a specific transaction with a new one. +full: true +_openapi: + method: PUT + route: /api/v1/relayers/{relayer_id}/transactions/{transaction_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/rpc.mdx b/docs/content/relayer/1.1.x/api/rpc.mdx new file mode 100644 index 00000000..1def5de6 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/rpc.mdx @@ -0,0 +1,15 @@ +--- +title: Performs a JSON-RPC call using the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/rpc + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/scrape_metrics.mdx b/docs/content/relayer/1.1.x/api/scrape_metrics.mdx new file mode 100644 index 00000000..0c8509b4 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/scrape_metrics.mdx @@ -0,0 +1,30 @@ +--- +title: >- + Triggers an update of system metrics and returns the result in plain text + format. +full: true +_openapi: + method: GET + route: /debug/metrics/scrape + toc: [] + structuredData: + headings: [] + contents: + - content: >- + # Returns + + + An `HttpResponse` containing the updated metrics in plain text, or an + error message if the + + update fails. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +# Returns + +An `HttpResponse` containing the updated metrics in plain text, or an error message if the +update fails. + + diff --git a/docs/content/relayer/1.1.x/api/sendTransaction.mdx b/docs/content/relayer/1.1.x/api/sendTransaction.mdx new file mode 100644 index 00000000..e70a0dba --- /dev/null +++ b/docs/content/relayer/1.1.x/api/sendTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Sends a transaction through the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/transactions + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/sign.mdx b/docs/content/relayer/1.1.x/api/sign.mdx new file mode 100644 index 00000000..28cf96de --- /dev/null +++ b/docs/content/relayer/1.1.x/api/sign.mdx @@ -0,0 +1,15 @@ +--- +title: Signs data using the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/sign + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/signTransaction.mdx b/docs/content/relayer/1.1.x/api/signTransaction.mdx new file mode 100644 index 00000000..a53aa136 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/signTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Signs a transaction using the specified relayer (Stellar only). +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/sign-transaction + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/signTypedData.mdx b/docs/content/relayer/1.1.x/api/signTypedData.mdx new file mode 100644 index 00000000..63a7d4d2 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/signTypedData.mdx @@ -0,0 +1,15 @@ +--- +title: Signs typed data using the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/sign-typed-data + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/updateNotification.mdx b/docs/content/relayer/1.1.x/api/updateNotification.mdx new file mode 100644 index 00000000..711b8b8d --- /dev/null +++ b/docs/content/relayer/1.1.x/api/updateNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Updates an existing notification. +full: true +_openapi: + method: PATCH + route: /api/v1/notifications/{notification_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/updateRelayer.mdx b/docs/content/relayer/1.1.x/api/updateRelayer.mdx new file mode 100644 index 00000000..0af41d58 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/updateRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Updates a relayer's information based on the provided update request. +full: true +_openapi: + method: PATCH + route: /api/v1/relayers/{relayer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/api/updateSigner.mdx b/docs/content/relayer/1.1.x/api/updateSigner.mdx new file mode 100644 index 00000000..000843a6 --- /dev/null +++ b/docs/content/relayer/1.1.x/api/updateSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Updates an existing signer. +full: true +_openapi: + method: PATCH + route: /api/v1/signers/{signer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/1.1.x/changelog.mdx b/docs/content/relayer/1.1.x/changelog.mdx new file mode 100644 index 00000000..38c7b004 --- /dev/null +++ b/docs/content/relayer/1.1.x/changelog.mdx @@ -0,0 +1,505 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v1.1.0) - 2025-08-11 + +## [1.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v1.0.0...v1.1.0) (2025-08-11) + + +### 🚀 Features + +* Add Arbitrum support ([#373](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/373)) ([7b5372b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b5372bf54fe26756ca5db6cb393e0d9d79ae621)) +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* add evm intristic gas_limit validation ([dd1b2d6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dd1b2d6768d09f051791d0db68c912a38d273715)) +* Add get_status method for EVM and Stellar ([#229](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/229)) ([e84217e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e84217e0fa941fcd580ad6b84ab6bfac939dd5f4)) +* Add Launctube plugin example ([#414](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/414)) ([5bda763](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5bda7635f304923fcd4031f855009228eeefee4b)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add node support to environment ([#236](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/236)) ([3ab46f8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ab46f848e7e4c6dee2545d62dc646b33623d63d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* Add plugin invoker service ([#290](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/290)) ([489ce02](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/489ce0285cd88a18b1616af94bfc970a4a674228)) +* Add plugins call endpoint ([#279](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/279)) ([c278589](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c278589f4c6bf88be86788fdd9b68c2f166f5f33)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* Add status_reason field to transaction responses ([#369](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/369)) ([c489e5d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c489e5d39e3cec555caf92ac93266016c547b2bb)) +* Add stellar launchtube plugin ([#401](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/401)) ([801e2f7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/801e2f7efc8f0cb7eb54f545ce398e6ee24cf6b9)) +* Add support for feebumped tx ([#309](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/309)) ([b4efd2e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b4efd2e894fb6534b61a10c5f8872a73d923410c)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add support for stellar InvokeHostFunction transactions ([#284](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/284)) ([32ba63e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/32ba63e58e3dfc1359b7a5c9f61f9ff2a8b6c317)) +* Add support to plugin list endpoint ([#358](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/358)) ([6517af0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6517af0a753db41638b006fa2b896a3ccec0d4ef)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* Add transaction status handling for stellar ([#223](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/223)) ([9496eb6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9496eb63514afb0bd29c731bebe86ffdcf393362)) +* Add wait API for plugin transactions ([#345](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/345)) ([6069af2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6069af256e6cfe8470244731d4bb444b87bd175f)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Added resolve_plugin_path for script_path ([#340](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/340)) ([0b30739](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0b30739e51f5ef6c0b97c1da585d403496b2bbac)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* Create initial js plugins library ([#302](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/302)) ([98238e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/98238e9a6a30de8dba3bf8d308a82658e29de46f)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* Enhance Stellar tx handling with fee updates ([#368](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/368)) ([05617d7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/05617d7cb06ab378c2c2207f9d0a2e11a04cc472)) +* **evm:** Add AWS KMS signer support ([#287](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/287)) ([723a9a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/723a9a8d7e625dd3f52b2d678d0e1cd842053e06)) +* **evm:** Implement delete pending txs ([#289](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/289)) ([bc6f829](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc6f829e580d42359adebceeddaf38002390e10b)) +* **evm:** Implement json rpc endpoint ([#286](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/286)) ([91528aa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/91528aab82e3fa3cba08f63feb4ac9879aa8940e)) +* extract networks to json files ([#238](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/238)) ([5ac07b3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ac07b3c570485d7cdbc419a23f373867d7ebe81)) +* handle non-retriable errors and provider health errors ([#233](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/233)) ([7add348](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7add348da4d06af5ebebcce78d856485e9894ac3)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Implement get_balance method for StellarRelayer ([#228](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/228)) ([d92c75f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d92c75fe7da1b02ddb7a38df32f98082474e4cd9)) +* implement network config deserializing ([#235](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/235)) ([6d537f9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6d537f9298626fefc0d5a45c311a95208e1c8ef5)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* Pass arbitrary payloads to script exectution ([#312](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/312)) ([adecaf5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/adecaf5d73c3df9083c6a3fcf62ed669bc90b25c)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* plat-6215 add support for rpc failovers and retries ([#231](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/231)) ([ca6d24f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ca6d24f1bcdbb912795dcb1496519b49b5e81bf1)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6303 store solana submitted transactions to db and run status check logic ([#398](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/398)) ([e8420bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e8420bca02c20a53b02d9bedc8da1b7a784716dc)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* plat-6522 allow for the use of on chain defi to automatically swap spl ([#198](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/198)) ([dc9e2e2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dc9e2e2dd1d46830bc6479c1928a2e7ef7f91fb3)) +* plat-6571 add support for gcp signer ([#221](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/221)) ([0170fa1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0170fa12c3ecc64d1c48ed3a726358ed74d4596b)) +* Plat-6677 implement redis repositories for existing collections ([#350](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/350)) ([5fee731](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5fee731c5f19013f41a12a5b93af79d65bdf777e)) +* Plat-6679 implement startup logic to populate redis from config file ([#359](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/359)) ([5e1c0c8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5e1c0c825d3c1185a5c59360a2c857d79b46abba)) +* Plat-6681 expose crud api endpoints ([#365](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/365)) ([f3c3426](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f3c34266f3f035cd240105833ef4e67711cb0356)) +* Plat-6684 add support for transaction entries expiration ([#394](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/394)) ([6f6f765](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6f6f765556b2fc16764f8afe02ceedf268c26c13)) +* plat-6817 EVM add support for gas limit calculation ([#355](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/355)) ([dd1b2d6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dd1b2d6768d09f051791d0db68c912a38d273715)) +* plat-6873 add storage documentation ([#395](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/395)) ([ffd4ed5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ffd4ed58d322bad63be500a084a0b082ac7b59d9)) +* Plugins improvements ([#410](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/410)) ([648a0f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/648a0f121a6308e8bde0e09010d2e0c83de5c6ec)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Relayer plugins - add support to plugins in configs ([#253](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/253)) ([6a14239](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6a14239486900b2ef121b5de9e87410c412b65fe)) +* **replace_tx:** Implement replace tx for evm ([#272](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/272)) ([b48e71f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b48e71f55fda03bea83e90255b0d180db704cb52)) +* Set default network folder ([#313](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/313)) ([b28c99c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b28c99c43bedd921a55660622d845e63890e0d74)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* **signer:** Add GCP Signer to EVM ([#305](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/305)) ([a8817b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a8817b6c87c65731232d0a141338f3996aef2510)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Stellar RPC config ([#213](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/213)) ([6fd75ea](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd75ea65bf1a945ba891f99d83b0cdacdf30014)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) +* support for retries and failovers in EVM Provider ([#197](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/197)) ([542f21a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/542f21a9346def9b7fe47e0a29a2bbd5ab2af349)) +* Support plugin timeouts ([#348](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/348)) ([0a1c51e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0a1c51e9fe540ba570af25146538992a26b9a8a0)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Types introduced for plugin params and result ([#351](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/351)) ([dda83a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dda83a296fd5bd5bfca7f7902f4ca035e1bd8796)) +* Update Stellar network config and docs ([#380](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/380)) ([a4e1a0f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a4e1a0f38590f21c6d5e917a02fee4f6bef4f075)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Add memo validation for InvokeHostFunction ([#294](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/294)) ([6bb4ffa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6bb4ffaf9ceb4a8daef29ec5878595cca7041300)) +* change the ampersand to and, as as the shell interpret it ([#206](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/206)) ([d164d6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d164d6a4d63fbf0acdfe1330cf25147e86280af8)) +* Changing base image to wolfi, added node and npm ([#266](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/266)) ([1181996](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1181996dac6da52f96e164b1c937828a3940d5b8)) +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Config example file ([#285](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/285)) ([a020c6f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a020c6fcd6f9b638d955d5f2c99aa0e199d8bf6e)) +* Correct env var value in semgrep.yml ([#375](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/375)) ([2e98e21](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e98e2149135b97a62b90c302675379642fdf7b3)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* Docker readme file ([#339](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/339)) ([2db9933](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2db9933def061046cc3585a07249107a236ef98c)) +* docker-scan - chainguard issue ([#255](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/255)) ([c9ab94b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c9ab94bcee7b386a33b063504b3e6d2cf188d8b5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Implement stellar sequence sync and tx reset ([#367](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/367)) ([60b5deb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/60b5deb4915041d60a064cfac1a066406c339517)) +* Inheritance validation ([#374](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/374)) ([f8b921b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f8b921b4d6d85b8068428f1e34de121183a02179)) +* Make plugins entry in configs optional ([#300](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/300)) ([f299779](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f299779318429677fd672d4a2433828971a1b62e)) +* Minor fixes in Plugin docs ([#325](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/325)) ([33bb6a1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/33bb6a1841f2e84723e49cc81258a930241dc735)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) +* Plat-6815 resubmission bug ([#353](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/353)) ([72ac174](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/72ac17471e3a0a6ac35e9a9bb9ff8fe5e8b94bf2)) +* plat-6888 aws kms signer issue ([#411](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/411)) ([3c12c88](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3c12c88703c92526fe975eabba6ba0ffa9ca9c79)) +* Plugin result + adds tests for plugin ts lib ([#336](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/336)) ([b30246e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b30246e8922d3cb5bd3c5b92a7678f7591db5b97)) +* Relayer plugins format output ([#307](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/307)) ([8f25e5f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f25e5f55812e3d346c8bc0ff063cf07e2f0b753)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* remove the ci job dependant from the test job ([#222](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/222)) ([4056610](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40566108b66c701323145c2889ce0141b84714b8)) +* Replace automatic minor version bumps ([#315](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/315)) ([85784b4](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/85784b486a9508429ae94373a7f3db13d78b39d6)) +* Replace tx request body ([#326](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/326)) ([a20c916](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a20c916b592891b7a2afafd2e62b32723fc05dc2)) +* SBOM upload error ([#342](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/342)) ([1f9318e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1f9318e22cbe59ca03bc617b0986379574e5f770)) +* Semgrep CI integration ([#371](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/371)) ([6b9a6d2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6b9a6d24e22b78743f16c566026b34f9912669ad)) +* Semgrep send metrics value ([#381](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/381)) ([315ccbc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/315ccbca9a48816fc6e0c8133301aa3e3186ff93)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) +* Solve issues with new solana_sdk version ([#324](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/324)) ([ab97253](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab972533259506bb21e22ec7f899a45d2fc97db5)) +* Switch Redocly build to use standalone html file ([#291](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/291)) ([97a8698](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/97a86980bec6260920a469018fee0d3541d1a063)) +* syntax error in codeql.yml ([#385](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/385)) ([987fd33](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/987fd33566b66b2821490d0769a3c863a778c271)) +* Update configs and dockerfiles in examples ([#298](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/298)) ([2e505ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e505ad827ab7544f7c6a3fdf4018b1e9428f1d6)) +* Update semgrep.yml ([#347](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/347)) ([5ffb803](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ffb8036ca6d3fb5a8cdb34fa5484e7732c842a1)) +* Update Stellar API docs to match implementation ([#292](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/292)) ([96d95e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/96d95e35784c25f39afe626b56f11477fd213196)) +* Use unicode character for emoji ([#343](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/343)) ([784e89f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/784e89fae4ad2ddad037ddbbd0bec6df160e9a6a)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v1.0.0) - 2025-06-30 + +## [1.0.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.2.0...v1.0.0) (2025-06-30) + + +### 🚀 Features + +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* Add get_status method for EVM and Stellar ([#229](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/229)) ([e84217e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e84217e0fa941fcd580ad6b84ab6bfac939dd5f4)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add node support to environment ([#236](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/236)) ([3ab46f8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ab46f848e7e4c6dee2545d62dc646b33623d63d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* Add plugin invoker service ([#290](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/290)) ([489ce02](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/489ce0285cd88a18b1616af94bfc970a4a674228)) +* Add plugins call endpoint ([#279](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/279)) ([c278589](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c278589f4c6bf88be86788fdd9b68c2f166f5f33)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* Add support for feebumped tx ([#309](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/309)) ([b4efd2e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b4efd2e894fb6534b61a10c5f8872a73d923410c)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add support for stellar InvokeHostFunction transactions ([#284](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/284)) ([32ba63e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/32ba63e58e3dfc1359b7a5c9f61f9ff2a8b6c317)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* Add transaction status handling for stellar ([#223](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/223)) ([9496eb6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9496eb63514afb0bd29c731bebe86ffdcf393362)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* Create initial js plugins library ([#302](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/302)) ([98238e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/98238e9a6a30de8dba3bf8d308a82658e29de46f)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* **evm:** Add AWS KMS signer support ([#287](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/287)) ([723a9a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/723a9a8d7e625dd3f52b2d678d0e1cd842053e06)) +* **evm:** Implement delete pending txs ([#289](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/289)) ([bc6f829](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc6f829e580d42359adebceeddaf38002390e10b)) +* **evm:** Implement json rpc endpoint ([#286](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/286)) ([91528aa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/91528aab82e3fa3cba08f63feb4ac9879aa8940e)) +* extract networks to json files ([#238](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/238)) ([5ac07b3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ac07b3c570485d7cdbc419a23f373867d7ebe81)) +* handle non-retriable errors and provider health errors ([#233](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/233)) ([7add348](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7add348da4d06af5ebebcce78d856485e9894ac3)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Implement get_balance method for StellarRelayer ([#228](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/228)) ([d92c75f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d92c75fe7da1b02ddb7a38df32f98082474e4cd9)) +* implement network config deserializing ([#235](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/235)) ([6d537f9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6d537f9298626fefc0d5a45c311a95208e1c8ef5)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* Pass arbitrary payloads to script exectution ([#312](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/312)) ([adecaf5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/adecaf5d73c3df9083c6a3fcf62ed669bc90b25c)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* plat-6215 add support for rpc failovers and retries ([#231](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/231)) ([ca6d24f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ca6d24f1bcdbb912795dcb1496519b49b5e81bf1)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* plat-6522 allow for the use of on chain defi to automatically swap spl ([#198](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/198)) ([dc9e2e2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dc9e2e2dd1d46830bc6479c1928a2e7ef7f91fb3)) +* plat-6571 add support for gcp signer ([#221](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/221)) ([0170fa1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0170fa12c3ecc64d1c48ed3a726358ed74d4596b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Relayer plugins - add support to plugins in configs ([#253](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/253)) ([6a14239](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6a14239486900b2ef121b5de9e87410c412b65fe)) +* **replace_tx:** Implement replace tx for evm ([#272](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/272)) ([b48e71f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b48e71f55fda03bea83e90255b0d180db704cb52)) +* Set default network folder ([#313](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/313)) ([b28c99c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b28c99c43bedd921a55660622d845e63890e0d74)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* **signer:** Add GCP Signer to EVM ([#305](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/305)) ([a8817b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a8817b6c87c65731232d0a141338f3996aef2510)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Stellar RPC config ([#213](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/213)) ([6fd75ea](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd75ea65bf1a945ba891f99d83b0cdacdf30014)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) +* support for retries and failovers in EVM Provider ([#197](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/197)) ([542f21a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/542f21a9346def9b7fe47e0a29a2bbd5ab2af349)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Add memo validation for InvokeHostFunction ([#294](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/294)) ([6bb4ffa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6bb4ffaf9ceb4a8daef29ec5878595cca7041300)) +* change the ampersand to and, as as the shell interpret it ([#206](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/206)) ([d164d6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d164d6a4d63fbf0acdfe1330cf25147e86280af8)) +* Changing base image to wolfi, added node and npm ([#266](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/266)) ([1181996](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1181996dac6da52f96e164b1c937828a3940d5b8)) +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Config example file ([#285](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/285)) ([a020c6f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a020c6fcd6f9b638d955d5f2c99aa0e199d8bf6e)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* docker-scan - chainguard issue ([#255](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/255)) ([c9ab94b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c9ab94bcee7b386a33b063504b3e6d2cf188d8b5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Make plugins entry in configs optional ([#300](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/300)) ([f299779](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f299779318429677fd672d4a2433828971a1b62e)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) +* Relayer plugins format output ([#307](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/307)) ([8f25e5f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f25e5f55812e3d346c8bc0ff063cf07e2f0b753)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* remove the ci job dependant from the test job ([#222](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/222)) ([4056610](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40566108b66c701323145c2889ce0141b84714b8)) +* Replace automatic minor version bumps ([#315](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/315)) ([85784b4](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/85784b486a9508429ae94373a7f3db13d78b39d6)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) +* Update configs and dockerfiles in examples ([#298](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/298)) ([2e505ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e505ad827ab7544f7c6a3fdf4018b1e9428f1d6)) +* Update Stellar API docs to match implementation ([#292](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/292)) ([96d95e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/96d95e35784c25f39afe626b56f11477fd213196)) + +[Changes][v1.0.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.2.0) - 2025-05-14 + +## [0.2.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.1...v0.2.0) (2025-05-14) + + +### 🚀 Features + +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) + + +### 🐛 Bug Fixes + +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) + +[Changes][v0.2.0] + + + +# [v0.1.1](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.1.1) - 2025-04-08 + +## [0.1.1](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.0...v0.1.1) (2025-04-08) + + +### 🐛 Bug Fixes + +* Skip releases ([e79b2e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e79b2e963439721dd8e151fa0827654e4019df5f)) +* Skip releases with release please ([#158](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/158)) ([e79b2e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e79b2e963439721dd8e151fa0827654e4019df5f)) + + +### ⚙️ Miscellaneous Chores + +* Fix workflow and missing libs in docker file ([#157](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/157)) ([c7a681d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c7a681dea154b06b675a286e936606e2f9ce087b)) +* plat-7575 Docs fixes ([#153](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/153)) ([#154](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/154)) ([44257e8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44257e8ea3e658adbf40f69ad809e4e3503e9af4)) + +[Changes][v0.1.1] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.1.0) - 2025-04-07 + +## 0.1.0 (2025-04-07) + + +### 🚀 Features + +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) + + +### 📚 Documentation + +* add cargo docs ([#75](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/75)) ([c4dd8e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c4dd8e30525ccaeb563560bc2ef87cdcec5b1790)) +* Add testing instructions ([#107](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/107)) ([c7c2ed7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c7c2ed7772d99b4b68ced9fbf8835fa9e46da5e1)) +* Adding configuration documentation ([#48](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/48)) ([929cc1b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/929cc1bf1e0c6b3be872daf6654abe24eb79b907)) +* Fixing formatting and location of files ([#41](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/41)) ([4d4f153](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d4f1530f466a5bd597d0338559ccb33815286f0)) +* Move README to a similar format to Monitor ([#39](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/39)) ([5985339](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/59853396b3786a972ce7bbc793d4dbacc62fe6c0)) +* Readability improvements ([#133](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/133)) ([9220727](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9220727cc2b4349052c2d96a48c5d9c3012b38b9)) +* Small doc updates - policy field descriptions ([#51](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/51)) ([cc83c49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/cc83c496bbe2593018b03c414a864691c967ff41)) +* Small fixes and Antora docs improvements ([#40](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/40)) ([655d16d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/655d16dc658a74b7413ce785dee5b8e33cfb40f7)) +* TG link and other minor doc updates ([#116](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/116)) ([fc68b6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc68b6afa844d2c2638d031fce44fcc514d59a7d)) +* Update API docs. Fix Dockerfiles ([#77](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/77)) ([0bd6bfe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0bd6bfea69d60c1a7e9d6b8a690ba1a2d0e44b74)) + +[Changes][v0.1.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.2.0...v1.0.0 +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/tree/v0.1.0 diff --git a/docs/content/relayer/1.1.x/configuration/index.mdx b/docs/content/relayer/1.1.x/configuration/index.mdx new file mode 100644 index 00000000..081bc613 --- /dev/null +++ b/docs/content/relayer/1.1.x/configuration/index.mdx @@ -0,0 +1,527 @@ +--- +title: Configuration +--- + +## Overview + +Most configuration files should live under `./config`, including the signer configurations, under `./config/keys`. +Please ensure appropriate access permissions on all configuration files (for `./config/keys/*`, we recommend `0500`. + + + + +The OpenZeppelin Relayer supports two configuration approaches: + +***File-based Configuration:*** +1. ***`config.json`***: Contains relayer definitions, signer configurations, and network policies +2. ***`.env`*** file: Contains environment variables like API keys and connection strings + +***API-based Configuration:*** +- Full CRUD operations for relayers, signers, and notifications via REST API +- Changes take effect immediately (no container restart required) +- See the ***API Reference*** page for detailed endpoints documentation + +See [Storage Configuration](/relayer/1.1.x/configuration/storage) for detailed information about how file-based and API-based configurations work together, storage behavior, and best practices. + + +For quick setup examples with pre-configured files, see the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository. + + + +## Environment configuration (.env) + +This defines some base configurations for the Relayer application: + +Copy the example environment file and update values according to your needs + +```bash +cp .env.example .env +``` + +This table lists the environment variables and their default values. + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `RUST_LOG` | `info` | `info, debug, warn, error, trace` | Log level. | +| `REPOSITORY_STORAGE_TYPE` | `in-memory` | `in-memory, redis` | Type of storage used for storing repository config and resources. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for detailed information. | +| `RESET_STORAGE_ON_START` | `false` | `bool` | Clears all resources from storage on startup and reloads entries from the config file. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for usage details. | +| `TRANSACTION_EXPIRATION_HOURS` | `4` | `number` | Number of hours after which transactions in a final state are removed from storage. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for more information. | +| `CONFIG_DIR` | `./config` | `` | Relative path of directory where config files reside | +| `CONFIG_FILE_NAME` | `config.json` | `` | File Name of the configuration file. | +| `RATE_LIMIT_RPS` | `100` | `` | Rate limit for the API in requests per second. | +| `RATE_LIMIT_BURST_SIZE` | `300` | `` | Rate limit burst size. | +| `API_KEY` | `` | `string`, | API key to use for authentication to the relayer server. Minimum length 32 characters. | +| `WEBHOOK_SIGNING_KEY` | `` | `string` | Signing key to use for webhook notifications. Minimum length 32 characters. | +| `LOG_MODE` | `stdout` | `stdout, file` | Write logs either to console or to file. | +| `LOG_DATA_DIR` | `./logs` | `` | Directory to persist log files on host. | +| `LOG_MAX_SIZE (in bytes)` | `1073741824` | `` | Size after which logs needs to be rolled. | +| `METRICS_ENABLED` | `false` | `bool` | Enable metrics server for external tools to scrape metrics. | +| `METRICS_PORT` | `8081` | `` | Port to use for metrics server. | +| `REDIS_URL` | `redis://localhost:6379` | `` | Redis connection URL for the relayer. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for Redis setup details. | +| `REDIS_CONNECTION_TIMEOUT_MS` | `10000` | `` | Connection timeout for Redis in milliseconds. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for Redis configuration. | +| `REDIS_KEY_PREFIX` | `oz-relayer` | `string` | Redis key prefix for namespacing. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for more information. | +| `STORAGE_ENCRYPTION_KEY` | `` | `string` | Encryption key used to encrypt data at rest in Redis storage. See [Storage Configuration](/relayer/1.1.x/configuration/storage) for security details. | +| `RPC_TIMEOUT_MS` | `10000` | `` | Sets the maximum time to wait for RPC connections before timing out. | +| `PROVIDER_MAX_RETRIES` | `3` | `` | Maximum number of retry attempts for provider operations. | +| `PROVIDER_RETRY_BASE_DELAY_MS` | `100` | `` | Base delay between retry attempts in milliseconds. | +| `PROVIDER_RETRY_MAX_DELAY_MS` | `2000` | `` | Maximum delay between retry attempts in milliseconds. | +| `PROVIDER_MAX_FAILOVERS` | `3` | `` | Maximum number of failovers (switching to different providers). | +| `ENABLE_SWAGGER` | `false` | `true, false` | Enable or disable Swagger UI for API documentation. | +| `KEYSTORE_PASSPHRASE` | `` | `` | Passphrase for the keystore file used for signing transactions. | + +### Environment configuration example + +`.env` file config example: + +``` +RUST_LOG=DEBUG +CONFIG_DIR=./config +CONFIG_FILE_NAME=config.json +WEBHOOK_SIGNING_KEY=e1d42480-6f74-4d0b-85f4-b7f0bb690fae +API_KEY=5eefd216-0e44-4ca7-b421-2925f90d30d5 +RATE_LIMIT_RPS=100 +RATE_LIMIT_BURST_SIZE=300 +METRICS_ENABLED=true +METRICS_PORT=8081 +REDIS_URL=redis://localhost:6379 +REDIS_CONNECTION_TIMEOUT_MS=10000 +REDIS_KEY_PREFIX=oz-relayer +RPC_TIMEOUT_MS=10000 +PROVIDER_MAX_RETRIES=3 +PROVIDER_RETRY_BASE_DELAY_MS=100 +PROVIDER_RETRY_MAX_DELAY_MS=2000 +PROVIDER_MAX_FAILOVERS=3 +ENABLE_SWAGGER=false +KEYSTORE_PASSPHRASE=your_keystore_passphrase +STORAGE_ENCRYPTION_KEY=X67aXacJB+krEldv9i2w7NCSFwwOzVV/1ELM2KJJjQw= +REPOSITORY_STORAGE_TYPE=redis +RESET_STORAGE_ON_START=false +TRANSACTION_EXPIRATION_HOURS=8 +``` + +## Main configuration file (config.json) + +This file can exist in any directory, but the default location is `./config/config.json`. + + + +All components defined in `config.json` can also be managed via REST API endpoints. This provides runtime flexibility for adding, updating, or removing relayers, signers, and notifications without restarting the service. See the ***API Reference*** page for detailed endpoints documentation. + + +Key sections in this file include: + +* Signers: Defines transaction signing methods. +* Notifications: Sets up status alerts +* Relayers: Configures networks, notifications channels, policies & singers. +* Networks: Defines blockchain network configurations. +* Plugins: Configures plugins. + +### 1. Signers + +Transaction signers are responsible for cryptographically signing transactions before they are submitted to blockchain networks. + +For comprehensive details on configuring all supported signer types including: + +* Local keystore file signers +* HashiCorp Vault (secret and transit) +* Cloud KMS providers (Google Cloud, AWS) +* Turnkey signers +* Security best practices and troubleshooting + +See the dedicated [Signers Configuration](/relayer/1.1.x/configuration/signers) guide. + + + +Signers can also be managed via API endpoints. + +See the ***API Reference*** page for detailed endpoints documentation. + + +### 2. Notifications + +* `notifications` array containing notification entries: + +```json +"notifications": [ + { + "id": "notification-test", + "type": "webhook", + "url": "https://webhook.site/f95cf78d-742d-4b21-88b7-d683e6fd147b", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } +] +``` +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the notification | +| type | String | Type of notification (only `webhook` available, for now) | +| url | String | Notification URL | +| signing_key.type | String | Type of key used in signing the notification (`env` or `plain`) | +| signing_key.value | String | Signing key value, env variable name, ... | + + + +Notifications can also be managed via API endpoints. + +See the ***API Reference*** page for detailed endpoints documentation. + + +### 3. Relayers + +* `relayers` array, containing relayer entries: + +```json +"relayers": [ + + "id": "solana-testnet", + "name": "Solana Testnet", + "paused": false, + "notification_id": "notification-test", + "signer_id": "local-signer", + "network_type": "solana", + "network": "testnet", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 // Higher weight routes more requests to this endpoint. The value must be an integer between 0 and 100 (inclusive). + , + + "url": "https://backup-rpc.example.com", + "weight": 1 + + ], + "policies": + "allowed_programs": [ + "11111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "BPFLoaderUpgradeab1e11111111111111111111111" + ] + + } +] +``` + +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the relayer | +| name | String | Human readable name for the relayer | +| paused | Boolean | Whether or not the relayer is paused (`true`, `false`) | +| notification_id | String | ID of a configured notification object | +| signer_id | String | ID of a configured signer | +| network_type | String | Type of network the relayer will connect to (`evm`, `solana`) | +| network | String | Network the relayer will connect to. Must match a network identifier defined in your network configuration files. See [Network Configuration](/relayer/1.1.x/network_configuration) for details on defining networks. | +| custom_rpc_urls | list | Optional custom RPC URLs for the network. If provided, this will be used instead of the public RPC URLs. This is useful for using your own RPC node or a paid service provider. The first url of the list is going to be used as the default | +| policies | list | Overrides default policies. Please refer to the [`Policies`](/relayer/1.1.x/configuration#network-policies) table | + +Policies +| Network type | Policy | Type | Description | +| --- | --- | --- | --- | +| solana, evm | min_balance | unsigned 128 | Minimum balance (in lamports or wei) required for the relayer to operate. Optional. | +| solana | fee_payment_strategy | enum(user,relayer) | Specifies who pays the fee. "user" (default) means the sender pays; "relayer" means the relayer pays. For "user", RPC methods add an instruction to transfer SPL tokens (calculated from the current SOL price plus a configurable margin) from the user to the relayer, ensuring fees are sustainably covered in tokens rather than SOL. | +| solana | swap_config | SwapConfig | Optional object configuring automated token‐swaps on Solana. | +| solana | fee_margin_percentage | f32 | Additional margin percentage added to estimated transaction fees to account for price fluctuations. For example, a value of 10 will add 10% to estimated fees. Optional. | +| solana | max_allowed_fee_lamports | unsigned 64 | Maximum allowed fee (in lamports) for a transaction. Optional. | +| solana | allowed_tokens | `Vector` | List of allowed tokens. Only these tokens are supported if provided. Optional. | +| solana | allowed_programs | `Vector` | List of allowed programs by their identifiers. Only these programs are supported if provided. Optional. | +| solana | allowed_accounts | `Vector` | List of allowed accounts by their public keys. The relayer will only operate with these accounts if provided. | +| solana | disallowed_accounts | `Vector` | List of disallowed accounts by their public keys. These accounts will be explicitly blocked. | +| solana | max_tx_data_size | unsigned 16 | Maximum transaction size. Optional. | +| solana | max_signatures | unsigned 8 | Maximum supported signatures. Optional. | +| evm | gas_price_cap | unsigned 128 | Specify a maximum gas price for every transaction sent with the Relayer. When enabled, any transaction exceeding the cap will have its gasPrice or maxFeePerGas overwritten. (Optional) | +| evm | gas_limit_estimation | bool | Automatic gas_limit calculation. Enabled by default. (Optional) | +| evm | whitelist_receivers | `Vector` | A list of authorized contracts for each transaction sent using the Relayer. Transactions will be rejected if the destination address is not on the list. (Optional) | + +#### RPC URL Configuration + +The relayer supports two ways to configure RPC URLs: + +1. ***Public RPC URLs***: These are the default RPC endpoints provided by the network. They are automatically selected based on the network configuration. +2. ***Custom RPC URLs***: You can specify custom RPC URLs using the `custom_rpc_urls` field in the relayer configuration. Each URL can be configured with an optional weight for high availability: + +```json +"custom_rpc_urls": [ + + "url": "https://primary-rpc.example.com", + "weight": 2 // Higher weight routes more requests to this endpoint. The value must be an integer between 0 and 100 (inclusive). + , + + "url": "https://secondary-rpc.example.com", + "weight": 100, // Max allowed weight + , + + "url": "https://backup-rpc.example.com" // No weight specified, defaults to 100 + , + + "url": "https://backup2-rpc.example.com", + "weight": 0, // A value of 0 disables the endpoint. + +] +``` + +This is useful when you want to: + * Use your own RPC nodes with load balancing + * Use a paid service provider for better reliability and performance + * Override the default public RPC URLs + * Access custom network endpoints + * Configure primary and backup endpoints with different weights + +When both are available, the relayer will: +1. First attempt to use the `custom_rpc_urls` if configured. +2. Fall back to the public RPC URLs if no custom URL is configured. + +For backward compatibility, string arrays are still supported: + +```json +"custom_rpc_urls": ["https://your-rpc.example.com"] +``` + + + + +When using custom RPC URLs: + +* Ensure the URLs are secure (HTTPS) when accessing over public networks +* Keep your API keys and authentication tokens secure +* Test the RPC endpoints' reliability and performance before using it in production +* Configure weights to prioritize endpoints, assigning higher values to more reliable or performant ones. +* The weight must be an integer between 0 and 100 (inclusive). +* A weight of 0 disables the endpoint. +* If a weight is not specified for an endpoint, it defaults to 100. + + + + + +Relayers could also be managed via API endpoints. + +See the ***API Reference*** page for detailed endpoints documentation. + + +### 4. Plugins + +For more information on how to write a plugin, please refer to the [Plugins](/relayer/1.1.x/plugins) page. + +* `plugins` array, containing plugin configurations: + +```json +"plugins": [ + { + "id": "my-plugin", + "path": "my-plugin.ts" + } +] +``` + +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the plugin | +| path | String | Path to the plugin file | + +### 5. Networks + +You can configure networks either: + +* In separate JSON files (recommended for better organization) +* Directly in your main `config.json` + +For comprehensive network configuration details, including: + +* Network field reference +* Configuration examples for all network types +* Network inheritance +* Special tags and their behavior +* Best practices and troubleshooting + +See the dedicated [Network Configuration](/relayer/1.1.x/network_configuration) guide. + +## Configuration File Example + +Full `config/config.json` example with evm and solana relayers definitions using keystore signer: + +```json +{ + "relayers": [ + { + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "evm", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 + }, + { + "url": "https://backup-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "gas_price_cap": 30000000000000, + "eip1559_pricing": true + } + }, + { + "id": "solana-example", + "name": "Solana Example", + "network": "devnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": [ + { + "url": "https://primary-solana-rpc.example.com", + "weight": 2 + }, + { + "url": "https://backup-solana-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "allowed_tokens": [ + { + "mint": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "max_allowed_fee": 100000000 + }, + { + "mint": "So11111111111111111111111111111111111111112" + } + ] + } + }, + { + "id": "solana-mainnet-example", + "name": "Solana Mainnet Example", + "network": "mainnet-beta", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": ["https://your-private-solana-rpc.example.com"], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "swap_config": { + "cron_schedule": "0 0 * * * *", + "min_balance_threshold": 0, + "strategy": "jupiter-ultra" + }, + "allowed_tokens": [ + { + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "max_allowed_fee": 100000000, + "swap_config": { + "min_amount": 0, + "max_amount": 0, + "retain_min_amount": 0 + } + }, + { + "mint": "So11111111111111111111111111111111111111112" + } + ] + } + } + ], + "notifications": [ + { + "id": "notification-example", + "type": "webhook", + "url": "https://webhook.site/1384d4d9-21b1-40a0-bcd1-d3f3b66be955", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } + ], + "signers": [ + { + "id": "local-signer", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } + } + ], + "networks": [ + { + "average_blocktime_ms": 12000, + "chain_id": 11155111, + "explorer_urls": [ + "https://api-sepolia.etherscan.io/api", + "https://sepolia.etherscan.io" + ], + "features": [ + "eip1559" + ], + "is_testnet": true, + "network": "sepolia", + "required_confirmations": 6, + "rpc_urls": [ + "https://sepolia.drpc.org", + "https://1rpc.io/sepolia", + "https://ethereum-sepolia-rpc.publicnode.com", + "https://ethereum-sepolia-public.nodies.app" + ], + "symbol": "ETH", + "tags": [ + "deprecated" + ], + "type": "evm" + }, + { + "type": "solana", + "network": "devnet", + "rpc_urls": ["https://api.devnet.solana.com"], + "explorer_urls": ["https://explorer.solana.com?cluster=devnet"], + "average_blocktime_ms": 400, + "is_testnet": true + }, + { + "type": "solana", + "network": "mainnet-beta", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "average_blocktime_ms": 400, + "is_testnet": false + } + ] +} +``` + +## Configuration Management Approaches + +The OpenZeppelin Relayer supports two complementary approaches for configuration management: + +### File-based Configuration +* Ideal for initial setup and deployment +* Configuration persists across restarts +* Requires container restart for changes to take effect +* Suitable for infrastructure-as-code workflows + +### API-based Configuration +* Enables runtime configuration changes +* No service restarts required +* Perfect for dynamic environments +* Supports automated configuration management + + + +See [Storage Configuration](/relayer/1.1.x/configuration/storage) for detailed information about how file-based and API-based configurations work together, storage behavior, and best practices. diff --git a/docs/content/relayer/1.1.x/configuration/signers.mdx b/docs/content/relayer/1.1.x/configuration/signers.mdx new file mode 100644 index 00000000..56f118e1 --- /dev/null +++ b/docs/content/relayer/1.1.x/configuration/signers.mdx @@ -0,0 +1,328 @@ +--- +title: Signers Configuration +--- + +## Overview + +Signers are responsible for cryptographically signing transactions before they are submitted to blockchain networks. OpenZeppelin Relayer supports multiple signer types to accommodate different security requirements and infrastructure setups. + +Each signer is referenced by its `id` in relayer configurations. + +## Configuration Structure + +Example signer configuration: +```json +"signers": [ + { + "id": "my_id", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } + } +] +``` + +## Supported Signer Types + +OpenZeppelin Relayer supports the following signer types: + +* `local`: Keystore file signer +* `vault`: HashiCorp Vault secret signer +* `vault_transit`: HashiCorp Vault Transit signer +* `turnkey`: Turnkey signer +* `google_cloud_kms`: Google Cloud KMS signer +* `aws_kms`: Amazon AWS KMS signer + +## Network Compatibility Matrix + +The following table shows which signer types are compatible with each network type: + +| Signer Type | EVM Networks | Solana Networks | Stellar Networks | +| --- | --- | --- | --- | +| `local` | ✅ Supported | ✅ Supported | ✅ Supported | +| `vault` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `vault_transit` | ❌ Not supported | ✅ Supported | ❌ Not supported | +| `turnkey` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `google_cloud_kms` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `aws_kms` | ✅ Supported | ❌ Not supported | ❌ Not supported | + + + +***Network-specific considerations:*** + +* ***EVM Networks***: Use secp256k1 cryptography. Most signers support EVM networks with proper key generation. +* ***Solana Networks***: Use ed25519 cryptography. Ensure your signer supports ed25519 key generation and signing. +* ***Stellar Networks***: Use ed25519 cryptography with specific Stellar requirements. Limited signer support due to network-specific implementation requirements. +* ***AWS KMS***: Currently optimized for EVM networks with secp256k1 support. +* ***Google Cloud KMS***: Supports both secp256k1 (EVM) and ed25519 (Solana) key types. +* ***Turnkey***: Supports EVM and Solana networks with appropriate key management. + + +## Common Configuration Fields + +All signer types share these common configuration fields: + +| Field | Type | Description | +| --- | --- | --- | +| `id` | String | Unique identifier for the signer (used to reference this signer in relayer configurations) | +| `type` | String | Type of signer (see supported signer types above) | +| `config` | Map | Signer type-specific configuration object | + +## Local Signer Configuration + +The local signer uses encrypted keystore files stored on the filesystem. + +```json +{ + "id": "local-signer", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| `path` | String | Path to the signer JSON file. Should be under the `./config` directory | +| `passphrase.type` | String | Type of passphrase source (`env` or `plain`) | +| `passphrase.value` | String | Passphrase value or environment variable name | + +## HashiCorp Vault Signer Configuration + +### Vault Secret Signer + +Uses HashiCorp Vault’s secret engine to store private keys. + +```json +{ + "id": "vault-signer", + "type": "vault", + "config": { + "address": "https://vault.example.com", + "role_id": { + "type": "env", + "value": "VAULT_ROLE_ID" + }, + "secret_id": { + "type": "env", + "value": "VAULT_SECRET_ID" + }, + "key_name": "relayer-key", + "mount_point": "secret" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| `address` | String | Specifies the Vault API endpoint | +| `role_id.type` | String | Type of value source (`env` or `plain`) | +| `role_id.value` | String | The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored | +| `secret_id.type` | String | Type of value source (`env` or `plain`) | +| `secret_id.value` | String | The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored | +| `key_name` | String | The name of the cryptographic key within Vault's Secret engine that is used for signing operations | +| `mount_point` | String | The mount point for the Secrets engine in Vault. Defaults to `secret` if not explicitly specified. Optional. | + +### Vault Transit Signer + +Uses HashiCorp Vault’s Transit secrets engine for cryptographic operations. + +```json +{ + "id": "vault-transit-signer", + "type": "vault_transit", + "config": { + "address": "https://vault.example.com", + "role_id": { + "type": "env", + "value": "VAULT_ROLE_ID" + }, + "secret_id": { + "type": "env", + "value": "VAULT_SECRET_ID" + }, + "key_name": "relayer-transit-key", + "mount_point": "transit", + "namespace": "production", + "pubkey": "your-public-key-here" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| `address` | String | Specifies the Vault API endpoint | +| `role_id.type` | String | Type of value source (`env` or `plain`) | +| `role_id.value` | String | The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored | +| `secret_id.type` | String | Type of value source (`env` or `plain`) | +| `secret_id.value` | String | The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored | +| `key_name` | String | The name of the cryptographic key within Vault's Transit engine that is used for signing operations | +| `mount_point` | String | The mount point for the Transit secrets engine in Vault. Defaults to `transit` if not explicitly specified. Optional. | +| `namespace` | String | The Vault namespace for API calls. This is used only in Vault Enterprise environments. Optional. | +| `pubkey` | String | Public key of the cryptographic key within Vault's Transit engine that is used for signing operations | + +## Turnkey Signer Configuration + +Uses Turnkey’s secure key management infrastructure. + +```json +{ + "id": "turnkey-signer", + "type": "turnkey", + "config": { + "api_public_key": "your-api-public-key", + "api_private_key": { + "type": "env", + "value": "TURNKEY_API_PRIVATE_KEY" + }, + "organization_id": "your-org-id", + "private_key_id": "your-private-key-id", + "public_key": "your-public-key" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| `api_public_key` | String | The public key associated with your Turnkey API access credentials. Used for authentication to the Turnkey signing service | +| `api_private_key.type` | String | Type of value source (`env` or `plain`) | +| `api_private_key.value` | String | The Turnkey API private key or environment variable name containing it. Used with the public key to authenticate API requests | +| `organization_id` | String | Your unique Turnkey organization identifier. Required to access resources within your specific organization | +| `private_key_id` | String | The unique identifier of the private key in your Turnkey account that will be used for signing operations | +| `public_key` | String | The public key corresponding to the private key identified by private_key_id. Used for address derivation and signature verification | + +## Google Cloud KMS Signer Configuration + +Uses Google Cloud Key Management Service for secure key operations. + +For EVM transaction signing, ensure your Google Cloud KMS key is created with: +- Protection level: HSM +- Purpose: Asymmetric sign +- Algorithm: "Elliptic Curve secp256k1 - SHA256 Digest" + +This provides secp256k1 compatibility required for Ethereum transactions. + + +```json +{ + "id": "gcp-kms-signer", + "type": "google_cloud_kms", + "config": { + "service_account": { + "project_id": "your-gcp-project", + "private_key_id": { + "type": "env", + "value": "GCP_PRIVATE_KEY_ID" + }, + "private_key": { + "type": "env", + "value": "GCP_PRIVATE_KEY" + }, + "client_email": { + "type": "env", + "value": "GCP_CLIENT_EMAIL" + }, + "client_id": "your-client-id" + }, + "key": { + "location": "us-west2", + "key_ring_id": "relayer-keyring", + "key_id": "relayer-key", + "key_version": 1 + } + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| service_account.project_id | String | The Google Cloud project ID where your KMS resources are located | +| service_account.private_key_id.type | String | Type of value source for the private key ID (`env` or `plain`) | +| service_account.private_key_id.value | String | The private key ID value or the environment variable name containing it | +| service_account.private_key.type | String | Type of value source for the private key (`env` or `plain`) | +| service_account.private_key.value | String | The Google Cloud service account private key (PEM format) or the environment variable name containing it | +| service_account.client_email.type | String | Type of value source for the client email (`env` or `plain`) | +| service_account.client_email.value | String | The Google Cloud service account client email or the environment variable name containing it | +| service_account.client_id | String | The Google Cloud service account client ID | +| key.location | String | The Google Cloud location (region) where your KMS key ring is located (e.g., "us-west2", "global") | +| key.key_ring_id | String | The KMS key ring ID containing your cryptographic key | +| key.key_id | String | The KMS key ID used for signing operations | +| key.key_version | Integer | The version of the KMS key to use for signing operations. Defaults to 1 | + +## AWS KMS Signer Configuration + +Uses Amazon Web Services Key Management Service for cryptographic operations. + +```json +{ + "id": "aws-kms-signer", + "type": "aws_kms", + "config": { + "region": "us-west-2", + "key_id": "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| region | String | AWS region. If the key is non-replicated across regions, this must match the key’s original region. Optional. If not specified, the default region from shared credentials is used | +| key_id | String | ID of the key in AWS KMS (can be key ID, key ARN, alias name, or alias ARN) | + +## Security Best Practices + +### File Permissions +* Set restrictive permissions on keystore files: `chmod 0500 config/keys/*` +* Ensure configuration directories are properly secured +* Use environment variables for sensitive data like passphrases and API keys + +### Key Management +* Use HSM-backed keys for production environments when available +* Implement proper key rotation policies +* Never commit private keys or sensitive configuration to version control +* Use dedicated service accounts with minimal required permissions + +### Environment Separation +* Use different signers for different environments (development, staging, production) +* Implement proper secrets management in production deployments +* Consider using cloud-native key management services for enhanced security + +## Troubleshooting + +### Common Issues + +***Invalid keystore passphrase*** + +* Verify the passphrase environment variable is correctly set +* Check that the keystore file is not corrupted +* Ensure the keystore format is compatible + +***Cloud KMS authentication failures*** + +* Verify service account credentials are valid and properly formatted +* Check that the service account has necessary permissions for KMS operations +* Ensure the KMS key exists and is in the correct region/project + +***Vault connection issues*** + +* Verify Vault server address and network connectivity +* Check AppRole credentials and permissions +* Ensure the secret/transit engine is properly mounted and configured + +For additional troubleshooting help, check the application logs and refer to the specific cloud provider or service documentation. diff --git a/docs/content/relayer/1.1.x/configuration/storage.mdx b/docs/content/relayer/1.1.x/configuration/storage.mdx new file mode 100644 index 00000000..6a471e8e --- /dev/null +++ b/docs/content/relayer/1.1.x/configuration/storage.mdx @@ -0,0 +1,150 @@ +--- +title: Storage Configuration +--- + +## Overview + +OpenZeppelin Relayer supports two storage backends for persisting configuration data and transaction state. The choice of storage backend affects how configuration is managed, data persistence, and performance characteristics. + + + + +Storage type determines how your configuration changes persist and how file-based and API-based configuration interact. Choose the right storage type for your deployment needs. + + + + + +***Community Contributions Welcome***: Additional storage backends (such as PostgreSQL, MongoDB, or other databases) are welcomed as contributions from the open source community. The storage system is designed to be extensible, making it straightforward to add new storage implementations. + + +## Storage Types + +### In-Memory Storage + +In-memory storage keeps all configuration and transaction data in the application’s memory. + +#### Use Cases +* ***Development and testing environments*** +* ***Temporary deployments*** +* ***Single-instance deployments*** +* ***When data persistence across restarts is not required*** + +#### Characteristics +* ***Fast Performance***: No network overhead for data access +* ***No External Dependencies***: Does not require Redis or other external services +* ***No Persistence***: All data is lost when the container restarts +* ***Single Instance***: Cannot be shared across multiple relayer instances + +#### Configuration Sync Behavior +* Configuration from `config.json` is loaded on every startup +* API changes are ***not*** synchronized back to `config.json` file +* All API-based configuration changes are lost on restart +* File-based configuration always takes precedence on startup + +```bash +# Enable in-memory storage +REPOSITORY_STORAGE_TYPE=in-memory +``` + +### Redis Storage + +Redis storage persists all configuration and transaction data in a Redis database. + +#### Use Cases +* ***Production deployments*** +* ***Multi-instance deployments*** +* ***When data persistence is required*** +* ***Scalable environments*** +* ***When API-based configuration changes should persist*** + +#### Characteristics +* ***Persistent***: Data survives container restarts +* ***Network Dependency***: Requires Redis connection +* ***Encryption***: Supports encryption at rest for sensitive data + +#### Configuration Sync Behavior +* Configuration from `config.json` is loaded into Redis ***only once*** during the first startup +* Subsequent startups use the configuration stored in Redis +* API changes are persisted and survive restarts +* File-based configuration can override Redis by setting `RESET_STORAGE_ON_START=true` + +```bash +# Enable Redis storage +REPOSITORY_STORAGE_TYPE=redis +REDIS_URL=redis://localhost:6379 +STORAGE_ENCRYPTION_KEY=your-encryption-key-here +``` + +## Configuration Reference + +### Core Storage Settings + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `REPOSITORY_STORAGE_TYPE` | `in-memory` | `in-memory, redis` | Type of storage backend used for storing configuration and transaction data. | +| `RESET_STORAGE_ON_START` | `false` | `true, false` | When `true`, clears all data from storage on startup and reloads from config files. Useful for forcing file-based configuration to override stored data. | +| `TRANSACTION_EXPIRATION_HOURS` | `4` | `number` | Number of hours after which transactions in a final state are automatically removed from storage to prevent storage bloat. | + +### Redis-Specific Settings + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `REDIS_URL` | `redis://localhost:6379` | Redis connection string | Full connection URL for the Redis instance. Supports Redis, Redis Sentinel, and Redis Cluster configurations. | +| `REDIS_CONNECTION_TIMEOUT_MS` | `10000` | `number` (milliseconds) | Maximum time to wait when connecting to Redis before timing out. | +| `REDIS_KEY_PREFIX` | `oz-relayer` | `string` | Prefix added to all Redis keys. Useful for namespacing when sharing Redis with other applications. | +| `STORAGE_ENCRYPTION_KEY` | `` | `string` (base64) | Encryption key used to encrypt sensitive data at rest in Redis. Generate using `cargo run --example generate_encryption_key`. | + +## Security Considerations + +### Redis Security + + + + +When using Redis storage in production: + +* ***Use encryption at rest***: Always set `STORAGE_ENCRYPTION_KEY` +* ***Secure Redis access***: Use Redis AUTH, TLS, and network security +* ***Network isolation***: Deploy Redis in a private network +* ***Regular backups***: Implement Redis backup strategy +* ***Monitor access***: Log and monitor Redis access patterns + + + +### Encryption at Rest + +Sensitive configuration data is encrypted before being stored in Redis when `STORAGE_ENCRYPTION_KEY` is provided. + +***Encrypted Data Includes:*** +- Signer private keys and passphrases +- Webhook signing keys +- API keys (when stored in configuration) +- Other sensitive configuration values + +***Generate Encryption Key:*** +```bash +# Generate a secure encryption key +cargo run --example generate_encryption_key + +# Alternative using OpenSSL +openssl rand -base64 32 +``` + +## Transaction Storage Management + +### Automatic Cleanup + +Transactions are automatically removed from storage after reaching their final state to prevent storage bloat: + +```bash +# Configure transaction retention (default: 4 hours) +TRANSACTION_EXPIRATION_HOURS=8 +``` + +***Final Transaction States:*** + +* `confirmed` - Transaction confirmed on blockchain +* `failed` - Transaction failed and will not be retried +* `cancelled` - Transaction was cancelled by user +* `expired`: - Transaction was expired diff --git a/docs/content/relayer/1.1.x/evm.mdx b/docs/content/relayer/1.1.x/evm.mdx new file mode 100644 index 00000000..36b326a7 --- /dev/null +++ b/docs/content/relayer/1.1.x/evm.mdx @@ -0,0 +1,306 @@ +--- +title: EVM Integration +--- + +## Overview + +OpenZeppelin Relayer provides comprehensive support for EVM (Ethereum Virtual Machine) networks, enabling secure transaction relaying, advanced gas management, EIP-1559 support, and robust fee estimation. This page covers everything you need to get started and make the most of EVM-specific features. + +## Features + +* Advanced gas price management with EIP-1559 support +* Dynamic gas limit estimation with fallback mechanisms +* Transaction replacement and acceleration +* Multi-network support (Ethereum, Arbitrum, Optimism, BSC, Polygon, etc.) +* Custom RPC endpoints with load balancing and failover +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and confirmation tracking +* Whitelist-based security policies +* Metrics and observability + +## Supported Networks + +EVM networks are defined via JSON configuration files, providing flexibility to: + +* Configure any EVM-compatible network (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* Set up custom EVM-compatible networks with specific RPC endpoints +* Create network variants using inheritance from base configurations +* Support both Layer 1 and Layer 2 networks + +For detailed network configuration options, see the [Network Configuration](/relayer/1.1.x/network_configuration) guide. + +## Supported Signers + +* `local` (local keystore files) +* `vault` (HashiCorp Vault secret storage) +* `vault_cloud` (hosted HashiCorp Vault) +* `turnkey` (hosted Turnkey signer) +* `google_cloud_kms` (Google Cloud KMS) +* `aws_kms` (Amazon AWS KMS) + +For detailed signer configuration options, see the [Signers](/relayer/1.1.x/configuration/signers) guide. + + + +In production systems, hosted signers (AWS KMS, Google Cloud KMS, Turnkey) are recommended for the best security model. + + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/1.1.x/quickstart). +Key prerequisites: + +* Rust 2021, version `1.86` or later +* Redis +* Docker (optional) + +Example configuration for an EVM relayer: +```json +{ + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "evm", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 100 + }, + { + "url": "https://backup-rpc.example.com", + "weight": 100 + } + ], + "policies": { + "gas_price_cap": 100000000000, + "eip1559_pricing": true, + "gas_limit_estimation": true, + "whitelist_receivers": [ + "0x1234567890123456789012345678901234567890", + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + ], + "min_balance": 1000000000000000000 + } +} +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +In addition to standard relayer configuration and policies, EVM relayers support additional options: + +* `gas_price_cap`: Maximum gas price limit (in wei) for transactions +* `gas_limit_estimation`: Enable/disable automatic gas limit estimation +* `whitelist_receivers`: List of authorized contract addresses for transactions +* `min_balance`: Minimum balance required for the relayer to operate (in wei) +* `eip1559_pricing`: Enable/disable EIP-1559 pricing methodology for transaction fees + +You can check all options in [User Documentation - Relayers](/relayer/1.1.x#3-relayers). + +### Gas Management Configuration + +#### Gas Price Cap +Set a maximum gas price to protect against extreme network congestion: + +```json +{ + "policies": { + "gas_price_cap": 100000000000 + } +} +``` + +#### Gas Limit Estimation +Enable or disable automatic gas limit estimation: + +```json +{ + "policies": { + "gas_limit_estimation": true + } +} +``` + +When disabled, gas limits must be provided explicitly in transaction requests. + +The relayer uses a two-tier approach for gas limit estimation: + +1. ***Primary Method***: Uses the RPC `estimate_gas` method to calculate gas requirements + * The estimated value is increased by 10% as a safety buffer + * Provides accurate estimates for most transaction types +2. ***Fallback Method***: When RPC estimation fails, default gas limits are applied based on transaction type: + * ***Simple ETH transfer*** (no data): 21,000 gas + * ***ERC20 transfer*** (`0xa9059cbb`): 65,000 gas + * ***ERC721/ERC20 transferFrom*** (`0x23b872dd`): 80,000 gas + * ***Complex contracts*** (all other function calls): 200,000 gas + + + +For advanced users working with complex transactions or custom contracts, it is recommended to include an explicit `gas_limit` parameter in the transaction request to ensure optimal gas usage and avoid estimation errors. + + +#### Whitelist Receivers +Restrict transactions to specific contract addresses: + +```json +{ + "policies": { + "whitelist_receivers": [ + "0x1234567890123456789012345678901234567890", + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + ] + } +} +``` + +## API Reference + +The EVM API provides comprehensive transaction management capabilities. + +Common endpoints: + +* `POST /api/v1/relayers//transactions` send transaction +* `GET /api/v1/relayers//transactions` list transactions +* `GET /api/v1/relayers//transactions/` get transaction by id + +### Send Transaction - Speed params + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "speed": "average" +}' +``` + +### Send Transaction - Speed params with gas limit included + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "speed": "average", + "gas_limit": 21000 +}' +``` + +### Transaction with EIP-1559 Pricing + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "max_fee_per_gas": 30000000000, + "max_priority_fee_per_gas": 20000000000 +}' +``` + +### Transaction with Legacy Pricing - gas estimation included + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "gas_price": "12312313123" +}' +``` + +### Get Transaction Status + +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/solana-example/transactions/' \ +--header 'Authorization: Bearer ' +``` + +See [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) for full details and examples. + +## Transaction Lifecycle + +### 1. Transaction Submission +* Validate transaction parameters +* Check whitelist policies (if enabled) +* Estimate gas limit (if not provided) +* Calculate gas price based on network conditions + +### 2. Transaction Signing +* Sign transaction using configured signer +* Generate appropriate signature format + +### 3. Transaction Broadcasting +* Submit to network via RPC endpoints +* Handle RPC failures with automatic retries +* Switch to backup RPC endpoints if needed + +### 4. Transaction Monitoring +* Track transaction status and confirmations +* Handle transaction replacements if needed +* Send notifications on status changes + +### 5. Transaction Confirmation +* Wait for required number of confirmations +* Mark transaction as confirmed or failed +* Clean up resources + +## Security Best Practices + +### Network Security +* Use private RPC endpoints in production +* Configure appropriate `gas_price_cap` to prevent excessive fees +* Enable `whitelist_receivers` for controlled environments +* Monitor relayer balance and set appropriate `min_balance` + +### Signer Security +* Use hosted signers (AWS KMS, Google Cloud KMS, Turnkey) in production +* Rotate signer keys regularly +* Implement proper access controls and audit logging +* Never store private keys in plain text + +### Operational Security +* Deploy behind a secure reverse proxy +* Use HTTPS for all communications +* Implement proper rate limiting +* Monitor for unusual transaction patterns + +### Monitoring and Observability + +Enable metrics and monitor: + +* Transaction success rates +* Gas price trends +* RPC endpoint performance +* Relayer balance levels +* Failed transaction patterns + +## Support + +For help with EVM integration: + +* Join our [Telegram](https://t.me/openzeppelin_tg/2) community +* Open an issue on our [GitHub repository](https://github.com/OpenZeppelin/openzeppelin-relayer) +* Check our [comprehensive documentation](https://docs.openzeppelin.com/relayer) + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/1.1.x/guides/index.mdx b/docs/content/relayer/1.1.x/guides/index.mdx new file mode 100644 index 00000000..7fca7f9c --- /dev/null +++ b/docs/content/relayer/1.1.x/guides/index.mdx @@ -0,0 +1,15 @@ +--- +title: Guides +--- + +## Overview + +Step-by-step guides for integrating with OpenZeppelin Relayer services and implementing common patterns for blockchain applications. + +## Available Guides + +### Stellar Channels Guide + +A comprehensive guide to using the OpenZeppelin Stellar Channels Service - a managed infrastructure for submitting Stellar Soroban transactions with automatic parallel processing and fee management. + +[Read the Stellar Channels Guide →](./stellar-channels-guide.mdx) diff --git a/docs/content/relayer/1.1.x/guides/stellar-channels-guide.mdx b/docs/content/relayer/1.1.x/guides/stellar-channels-guide.mdx new file mode 100644 index 00000000..1ba655ba --- /dev/null +++ b/docs/content/relayer/1.1.x/guides/stellar-channels-guide.mdx @@ -0,0 +1,219 @@ +--- +title: Stellar Channels Guide +--- + +## Overview + +OpenZeppelin Stellar Channels Service is a managed infrastructure for submitting Stellar Soroban transactions with automatic parallel processing and fee management. The service handles all the complexity of transaction submission, allowing you to focus on building your application. + +**Key Benefits:** + +- **_Zero Infrastructure Management_**: No servers, relayers, or channel accounts to configure +- **_Automatic Fee Payment_**: Gas fees paid by the service on your behalf +- **_Parallel Processing_**: High throughput via managed pool of channel accounts +- **_Simple Integration_**: Type-safe SDK with minimal setup +- **_Free to Use_**: No credits, subscriptions, or payment systems + +## Service Endpoints + +- **Mainnet**: `https://channels.openzeppelin.com` +- **Testnet**: `https://channels.openzeppelin.com/testnet` + +## Getting Started + +### 1. Get Your API Key + +Visit the service endpoint to generate an API key: + +- **Mainnet**: https://channels.openzeppelin.com/gen +- **Testnet**: https://channels.openzeppelin.com/testnet/gen + +Save your API key securely - you'll need it for all requests. + +### 2. Install the Client + +```bash +npm install @openzeppelin/relayer-plugin-channels +# or +pnpm add @openzeppelin/relayer-plugin-channels +# or +yarn add @openzeppelin/relayer-plugin-channels +``` + +### 3. Initialize the Client + +```typescript +import { ChannelsClient } from '@openzeppelin/relayer-plugin-channels'; + +const client = new ChannelsClient({ + baseUrl: 'https://channels.openzeppelin.com/testnet', + apiKey: process.env.CHANNELS_API_KEY, +}); +``` + +## Submitting Transactions + +The Channels service supports two transaction submission methods depending on your use case. + +### Method 1: Soroban Function + Auth (Recommended) + +This method is ideal when you want the service to handle transaction building and simulation. Submit the Soroban function and authorization entries, and the service builds the complete transaction using a channel account from the pool, enabling high-throughput parallel processing. + +```typescript +import { Contract, Networks, SorobanRpc } from '@stellar/stellar-sdk'; + +// Initialize your contract +const contract = new Contract('CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA'); + +// Build the transaction (don't sign yet) +const rpc = new SorobanRpc.Server('https://soroban-testnet.stellar.org'); +const source = await rpc.getAccount(sourceAddress); + +const tx = new TransactionBuilder(source, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(contract.call('transfer' /* args */)) + .setTimeout(30) + .build(); + +// Simulate to get auth entries +const simulation = await rpc.simulateTransaction(tx); +const assembled = SorobanRpc.assembleTransaction(tx, simulation).build(); + +// Extract function and auth XDRs +const op = assembled.operations[0]; +const func = op.func.toXDR('base64'); +const auth = (op.auth ?? []).map((a) => a.toXDR('base64')); + +// Submit to Channels +const result = await client.submitSorobanTransaction({ + func: func, + auth: auth, +}); + +console.log('Transaction submitted:', result.hash); +console.log('Status:', result.status); +``` + +**When to use this method:** + +- You want the service to handle transaction assembly +- You're working with standard Soroban contract calls +- You need automatic simulation and resource calculation +- You need high-throughput parallel transaction processing + +### Method 2: Pre-Signed Transaction XDR + +This method gives you full control over the transaction structure. You build, sign, and submit a complete transaction envelope. + +```typescript +import { Keypair, Networks, TransactionBuilder } from '@stellar/stellar-sdk'; + +// Build and sign your transaction +const sourceKeypair = Keypair.fromSecret('S...'); +const tx = new TransactionBuilder(source, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(/* your operation */) + .setTimeout(30) + .build(); + +// Sign the transaction +tx.sign(sourceKeypair); + +// Submit to Channels +const result = await client.submitTransaction({ + xdr: tx.toXDR(), // base64 envelope XDR +}); + +console.log('Transaction submitted:', result.hash); +console.log('Status:', result.status); +``` + +**When to use this method:** + +- You need precise control over transaction structure +- You're using advanced Stellar features +- Your transaction is already signed by another system + +## Response Format + +All successful submissions return: + +```typescript +{ + transactionId: string; // Internal tracking ID + hash: string; // Stellar transaction hash + status: string; // Transaction status (e.g., "confirmed") +} +``` + +## Error Handling + +The SDK provides structured error handling with three error types: + +```typescript +import { + PluginTransportError, + PluginExecutionError, + PluginUnexpectedError, +} from '@openzeppelin/relayer-plugin-channels'; + +try { + const result = await client.submitSorobanTransaction({ func, auth }); + console.log('Success:', result.hash); +} catch (error) { + if (error instanceof PluginTransportError) { + // Network failures (connection, timeout, 5xx errors) + console.error('Service unavailable:', error.message); + console.error('Status:', error.statusCode); + } else if (error instanceof PluginExecutionError) { + // Transaction rejected (validation, simulation failure, on-chain failure) + console.error('Transaction failed:', error.message); + console.error('Error code:', error.errorDetails?.code); + console.error('Details:', error.errorDetails?.details); + } else if (error instanceof PluginUnexpectedError) { + // Client-side errors (parsing, validation) + console.error('Client error:', error.message); + } +} +``` + +### Common Error Codes + +| Code | Description | Resolution | +| --------------------- | ------------------------------------- | --------------------------------------------------------- | +| `INVALID_PARAMS` | Invalid request parameters | Check that you're providing either `xdr` OR `func`+`auth` | +| `INVALID_XDR` | Failed to parse XDR | Verify XDR is valid base64 and properly encoded | +| `POOL_CAPACITY` | All channel accounts in use | Retry after a short delay | +| `SIMULATION_FAILED` | Transaction simulation failed | Check contract address and function arguments | +| `ONCHAIN_FAILED` | Transaction failed on-chain | Review transaction logic and on-chain state | +| `INVALID_TIME_BOUNDS` | Transaction timeout too far in future | Set timeout to ≤30 seconds | + +## TypeScript Support + +The SDK is fully typed + +```typescript +import type { + ChannelsClient, + ChannelsFuncAuthRequest, + ChannelsXdrRequest, + ChannelsTransactionResponse, +} from '@openzeppelin/relayer-plugin-channels'; + +// All parameters and responses are fully typed +const request: ChannelsFuncAuthRequest = { + func: 'AAAABAAAAAEAAAAGc3ltYm9s...', + auth: ['AAAACAAAAAEAAAA...'], +}; + +const response: ChannelsTransactionResponse = await client.submitSorobanTransaction(request); +``` + +## Support & Resources + +- **Stellar SDK Documentation**: https://stellar.github.io/js-stellar-sdk/ +- **Channels Plugin**: https://github.com/OpenZeppelin/relayer-plugin-channels diff --git a/docs/content/relayer/1.1.x/index.mdx b/docs/content/relayer/1.1.x/index.mdx new file mode 100644 index 00000000..50ebabe4 --- /dev/null +++ b/docs/content/relayer/1.1.x/index.mdx @@ -0,0 +1,335 @@ +--- +title: OpenZeppelin Relayer +--- + +## Overview + +OpenZeppelin Relayer is a service that provides infrastructure to relay transactions to the EVM & Non-EVM networks. It is designed to be used as a backend for dApps that need to interact with these networks. + +## Features + +* ***Multi-Chain Support***: Interact with multiple blockchain networks, including Solana and EVM-based chains. +* ***Transaction Relaying***: Submit transactions to supported blockchain networks efficiently. +* ***Transaction Signing***: Securely sign transactions using configurable key management. +* ***Transaction Fee Estimation***: Estimate transaction fees for better cost management. +* ***Solana Gasless Transactions***: Support for gasless transactions on Solana, enabling users to interact without transaction fees. +* ***Transaction Nonce Management***: Handle nonce management to ensure transaction order. +* ***Transaction Status Monitoring***: Track the status of submitted transactions. +* ***SDK Integration***: Easily interact with the relayer through our companion JavaScript/TypeScript SDK. +* ***Extensible Architecture***: Easily add support for new blockchain networks. +* ***Configurable Network Policies***: Define and enforce network-specific policies for transaction processing. +* ***Metrics and Observability***: Monitor application performance using Prometheus and Grafana. +* ***Docker Support***: Deploy the relayer using Docker for both development and production environments. +* ***Plugins***: Extend the functionality of the relayer with custom logic using TypeScript functions. + +## Supported Networks + +OpenZeppelin Relayer supports multiple blockchain networks through a flexible JSON-based configuration system. Networks are defined in configuration files, allowing you to configure: + +* ***Any EVM-compatible network*** (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* ***Solana networks*** (mainnet-beta, devnet, testnet, custom RPC endpoints) +* ***Stellar networks*** (Pubnet, Testnet, custom networks) +* ***Create custom network configurations*** with specific RPC endpoints, chain IDs, and network parameters +* ***Use inheritance*** to create network variants that inherit from base configurations + +### Network Types + +| Network Type | Description | +| --- | --- | +| `evm` | Ethereum Virtual Machine compatible networks. Supports any EVM chain by configuring chain ID, RPC URLs, and network-specific parameters. | +| `solana` | Solana blockchain networks. Supports all Solana clusters and custom RPC endpoints. | +| `stellar` | Stellar blockchain networks (Partial support). Supports Stellar Public Network and Testnet. | + +Networks can be loaded from: + +* ***JSON arrays***: Direct network definitions in configuration files +* ***Directory of files***: Multiple JSON files each containing network definitions + +For detailed network configuration options and examples, see the [Network Configuration](/relayer/1.1.x/network_configuration) page. + + + +For information about our development plans and upcoming features, see [Project Roadmap](/relayer/1.1.x/roadmap). + + + + +To get started immediately, see [Quickstart](/relayer/1.1.x/quickstart). + + +## Technical Overview + +```mermaid +flowchart TB + subgraph "Clients" + client[API/SDK] + end + + subgraph "OpenZeppelin Relayer" + subgraph "API Layer" + api[API Routes & Controllers] + middleware[Middleware] + plugins[Plugins] + end + + subgraph "Domain Layer" + domain[Domain Logic] + relayer[Relayer Services] + policies[Policy Enforcement] + end + + subgraph "Infrastructure" + repositories[Repositories] + jobs[Job Queue System] + signer[Signer Services] + provider[Network Providers] + end + + subgraph "Services Layer" + transaction[Transaction Services] + vault[Vault Services] + webhook[Webhook Notifications] + monitoring[Monitoring & Metrics] + end + + subgraph "Configuration" + config_files[Config Files] + env_vars[Environment Variables] + end + end + + subgraph "External Systems" + blockchain[Blockchain Networks] + redis[Redis] + vault_ext[HashiCorp Vault] + metrics[Prometheus/Grafana] + notification[Notification Services] + end + + %% Client connections + client -- "HTTP Requests" --> api + + %% API Layer connections + api -- "Processes requests" --> middleware + middleware -- "Validates & routes" --> domain + middleware -- "Invokes" --> plugins + + %% Domain Layer connections + domain -- "Uses" --> relayer + domain -- "Enforces" --> policies + relayer -- "Processes" --> transaction + plugins -- "Interacts with" --> relayer + + %% Services Layer connections + transaction -- "Signs with" --> signer + transaction -- "Connects via" --> provider + transaction -- "Queues jobs" --> jobs + webhook -- "Notifies" --> notification + monitoring -- "Collects" --> metrics + signer -- "May use" --> vault + + %% Infrastructure connections + repositories -- "Stores data" --> redis + jobs -- "Processes async" --> redis + vault -- "Secrets management" --> vault_ext + provider -- "Interacts with" --> blockchain + + %% Configuration connections + config_files -- "Configures" --> domain + env_vars -- "Configures" --> domain + + %% Styling + classDef apiClass fill:#f9f,stroke:#333,stroke-width:2px + classDef domainClass fill:#bbf,stroke:#333,stroke-width:2px + classDef infraClass fill:#bfb,stroke:#333,stroke-width:2px + classDef serviceClass fill:#fbf,stroke:#333,stroke-width:2px + classDef configClass fill:#fbb,stroke:#333,stroke-width:2px + classDef externalClass fill:#ddd,stroke:#333,stroke-width:1px + + class api,middleware,plugins apiClass + class domain,relayer,policies domainClass + class repositories,jobs,signer,provider infraClass + class transaction,vault,webhook,monitoring serviceClass + class config_files,env_vars configClass + class blockchain,redis,vault_ext,metrics,notification externalClass +``` + +## Project Structure + +The project follows a standard Rust project layout: + +``` +openzeppelin-relayer/ +├── src/ +│ ├── api/ # Route and controllers logic +│ ├── bootstrap/ # Service initialization logic +│ ├── config/ # Configuration logic +│ ├── constants/ # Constant values used in the system +│ ├── domain/ # Domain logic +│ ├── jobs/ # Asynchronous processing logic (queueing) +│ ├── logging/ # Logs File rotation logic +│ ├── metrics/ # Metrics logic +│ ├── models/ # Data structures and types +│ ├── repositories/ # Configuration storage +│ ├── services/ # Services logic +│ └── utils/ # Helper functions +│ +├── config/ # Configuration files +├── tests/ # Integration tests +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── examples/ # Configuration examples +├── helpers/ # Rust helper scripts +├── plugins/ # Plugins directory +└── ... other root files (Cargo.toml, README.md, etc.) +``` + +For detailed information about each directory and its contents, see [Project Structure Details](/relayer/1.1.x/structure). + +## Getting Started + +### Prerequisites + +* Rust 2021 edition, version `1.86` or later +* Docker (optional, for containerized deployment) +* Node.js, typescript and ts-node (optional, for plugins) + + + +**Ready-to-Use Example Configurations** + +For quick setup with various configurations, check the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository: + +* `basic-example`: Simple setup with Redis +* `basic-example-logging`: Configuration with file-based logging +* `basic-example-metrics`: Setup with Prometheus and Grafana metrics +* `vault-secret-signer`: Using HashiCorp Vault for key management +* `vault-transit-signer`: Using Vault Transit for secure signing +* `evm-gcp-kms-signer`: Using Google Cloud KMS for EVM secure signing +* `evm-turnkey-signer`: Using Turnkey for EVM secure signing +* `solana-turnkey-signer`: Using Turnkey for Solana secure signing +* `redis-storage`: Using Redis for Storage +* `network-configuration-config-file`: Using Custom network configuration via config file +* `network-configuration-json-file`: Using Custom network configuration via JSON file + +Each example includes a README with step-by-step instructions and Docker Compose configuration. + + +### Install Locally + +1. Clone the repository: + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-relayer + cd openzeppelin-relayer + ``` +2. Verify you have sodium libs installed. If not, follow these instructions: + + * Install a stable libsodium version from [here](https://download.libsodium.org/libsodium/releases/). + * Follow the steps in the [libsodium installation guide](https://doc.libsodium.org/installation). +3. Install dependencies: + + ```bash + cargo build + ``` + +## Running the Relayer + +### Option 1: Run Locally + +```bash +cargo run +``` + + +Before executing the command, ensure that the `.env` and `config.json` files are configured as detailed in the [Configuration References](/relayer/1.1.x/configuration) section. + + +### Option 2: Run with Docker + +The Relayer can be run as either a development or production container using the corresponding Dockerfile (`Dockerfile.development` or `Dockerfile.production`). + +#### Step 1: Configure Environment + +* Edit `.env` at the root of the repository to adjust environment variables +* The appropriate .env file will be included during image build + +#### Step 2: Build the Image + +You can build using Docker Compose (v2). + +```bash +# Default build +docker compose build + +# Or, for a leaner image (and using Dockerfile.production) +DOCKERFILE=Dockerfile.production docker compose build +``` + +#### Step 3: Run the Container + +Use Docker Compose to run the container: + +```bash +docker compose up -d +``` + +For production runs, you can use: + +```bash +DOCKERFILE=Dockerfile.production docker compose up -d +``` + +## Configuration + +OpenZeppelin Relayer supports two configuration approaches: + +***File-based Configuration:*** +- ***`config.json`***: Contains relayer definitions, signer configurations, and network policies +- ***`.env`***: Contains environment variables like API keys and connection strings + +***API-based Configuration:*** +- Runtime configuration management via REST API +- No service restarts required for configuration changes +- Full CRUD operations for relayers, signers, and notifications + + + + +Both approaches can be used together. File-based configuration is loaded on startup, while API changes provide runtime flexibility. Changes to environment variables (`.env`) always require restarting the container. + +When used together, API changes are not synced to file-based configuration. File-based configuration is loaded only once when using persistent storage mode. + +For quick setup examples with pre-configured files, see the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository. + + + +For comprehensive configuration details, including: + +* Environment variables and their settings +* Main configuration file structure +* Signer configurations (local, vault, cloud KMS, etc.) +* Notification setup +* Relayer policies and network settings +* Plugin configuration +* Complete configuration examples + +See the dedicated [Configuration Guide](/relayer/1.1.x/configuration). + +## Important Considerations + +## Deployment Considerations + + +The OpenZeppelin Relayer is designed to function as a backend service and is not meant to be directly exposed to the public internet. To protect the service from unauthorized access, deploy it behind your own secure backend infrastructure—such as a reverse proxy or firewall—and restrict access to trusted internal components only. Direct exposure can increase the risk of exploitation and security breaches. + + +## Support + +For support or inquiries, contact us on [Telegram](https://t.me/openzeppelin_tg/2). + +## License +This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details. + +## Security +For security concerns, please refer to our [Security Policy](https://github.com/OpenZeppelin/openzeppelin-relayer/blob/main/SECURITY.md). diff --git a/docs/content/relayer/1.1.x/network_configuration.mdx b/docs/content/relayer/1.1.x/network_configuration.mdx new file mode 100644 index 00000000..725f5e99 --- /dev/null +++ b/docs/content/relayer/1.1.x/network_configuration.mdx @@ -0,0 +1,387 @@ +--- +title: Network Configuration +--- + +The OpenZeppelin Relayer supports multiple blockchain networks through a flexible JSON-based configuration system. This guide covers everything you need to know about configuring networks for your relayer instances. + +## Overview + +Networks are defined in JSON configuration files, allowing you to: + +* Configure ***any EVM-compatible network*** (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* Set up ***Solana networks*** (mainnet-beta, devnet, testnet, custom RPC endpoints) +* Configure ***Stellar networks*** (Pubnet, Testnet, custom networks) +* Create ***custom network configurations*** with specific RPC endpoints, chain IDs, and network parameters +* Use ***inheritance*** to create network variants without duplicating configuration + +## Network Types + +| Network Type | Description | +| --- | --- | +| `evm` | Ethereum Virtual Machine compatible networks. Supports any EVM chain by configuring chain ID, RPC URLs, and network-specific parameters. | +| `solana` | Solana blockchain networks. Supports all Solana clusters and custom RPC endpoints. | +| `stellar` | Stellar blockchain networks. Supports Stellar Public Network and Testnet. | + +## Configuration Methods + +### Default Network Configuration + +If no `networks` field is specified in your `config.json`, the relayer will automatically load network configurations from the `./config/networks` directory. This is the default behavior. + +```json + + "relayers": [...], + "notifications": [...], + "signers": [...] + // No "networks" field - defaults to "./config/networks" + +``` + + +Once you specify a `networks` field in your configuration, the default `./config/networks` directory will ***not*** be loaded automatically. If you want to use files from that directory, you must explicitly specify the path `"./config/networks"`. + + +You can configure networks in two ways: + +### Method 1: Separate JSON Files + +Specify the path to network configuration files in your main `config.json`: + +```json + + "relayers": [...], + "notifications": [...], + "signers": [...], + "networks": "./config/networks" // Path to directory or file + +``` + + +This is the same as the default behavior, but explicitly specified. You can also point to a different directory or file path. + + +Each JSON file ***must*** contain a top-level `networks` array: + +```json + + "networks": [ + // ... network definitions ... + ] + +``` + +When using a directory structure: +``` +networks/ +├── evm.json # "networks": [...] +├── solana.json # "networks": [...] +└── stellar.json # "networks": [...] +``` + +### Method 2: Direct Configuration + +Define networks directly in your main `config.json` instead of using separate files: + +```json + + "relayers": [...], + "notifications": [...], + "signers": [...], + "networks": [ + { + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, + // ... other fields + + ] +} +``` + +When using this method, the default `./config/networks` directory is ignored, and only the networks defined in this array will be available. + +## Network Field Reference + +### Common Fields + +All network types support these configuration fields: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | Yes | Network type: `"evm"`, `"solana"`, or `"stellar"` | +| `network` | string | Yes | Unique network identifier (e.g., "ethereum-mainnet", "polygon-mumbai") | +| `from` | string | No | Name of parent network to inherit from (same type only) | +| `rpc_urls` | array[string] | Yes* | List of RPC endpoint URLs (*Required for base networks, optional for inherited) | +| `explorer_urls` | array[string] | No | List of blockchain explorer URLs | +| `average_blocktime_ms` | number | No | Estimated average time between blocks in milliseconds | +| `is_testnet` | boolean | No | Whether this is a testnet (affects behavior and validation) | +| `tags` | array[string] | No | Arbitrary tags for categorization and filtering | + +### Special Network Tags + +Some tags have special meaning and affect relayer behavior: + +| Tag | Description and Behavior | +| --- | --- | +| `rollup` | Identifies Layer 2 rollup networks (e.g., Arbitrum, Optimism, Base) | +| `optimism` | Identifies Optimism-based networks using the OP Stack (e.g., Optimism, Base, World Chain) | +| `arbitrum-based` | Identifies Arbitrum-based networks using the Arbitrum Stack | +| `no-mempool` | Indicates networks that lack a traditional mempool (e.g., Arbitrum) | +| `deprecated` | Marks networks that are deprecated and may be removed in future versions | + +#### Example: Using Special Tags + +Here’s an example showing how special tags are used in practice: + +```json + + "type": "evm", + "network": "arbitrum-one", + "chain_id": 42161, + "required_confirmations": 1, + "symbol": "ETH", + "rpc_urls": ["https://arb1.arbitrum.io/rpc"], + "tags": ["rollup", "no-mempool"], // Arbitrum is a rollup without mempool + "is_testnet": false + +``` + +These tags help the relayer: + +* Apply specific transaction handling for rollups +* Use optimized fee calculation for OP Stack chains +* Skip mempool-related operations for networks without mempools +* Warn users about deprecated networks + +### EVM-Specific Fields + + +The OpenZeppelin Relayer supports any EVM-based L1 blockchain, as long as it doesn’t deviate significantly from standard EVM behavior. Some L2 networks may also work, depending on how closely they follow EVM conventions. Users are encouraged to add the networks they need via the JSON configuration and test them thoroughly on testnets before deploying to production. + + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `chain_id` | number | Yes* | Unique chain identifier (e.g., 1 for Ethereum mainnet, 137 for Polygon) (*Required for base networks, optional for inherited) | +| `required_confirmations` | number | Yes* | Number of block confirmations before considering a transaction final (*Required for base networks, optional for inherited) | +| `symbol` | string | Yes* | Native currency symbol (e.g., "ETH", "MATIC", "BNB") (*Required for base networks, optional for inherited) | +| `features` | array[string] | No | Supported features (e.g., ["eip1559", "london"]) | + +#### Example: EVM Network Configuration + +Here’s an example showing an EVM network configuration: + +```json + + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, // Ethereum mainnet chain ID + "required_confirmations": 12, // High security: 12 confirmations + "symbol": "ETH", // Native currency symbol + "features": ["eip1559"], // Supports EIP-1559 fee market + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"], + "is_testnet": false + +``` + +### Solana-Specific Fields + +Currently, Solana networks use only the common fields. Additional Solana-specific configuration options may be added in future versions. + +### Stellar-Specific Fields + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `passphrase` | string | No | Network passphrase for transaction signing and network identification (optional for all networks, including base networks) | + +#### Example: Stellar Network Configuration + +Here’s an example showing a Stellar network configuration with passphrase: + +```json + + "type": "stellar", + "network": "pubnet", + "rpc_urls": ["https://mainnet.sorobanrpc.com"], + "explorer_urls": ["https://stellar.expert/explorer/public"], + "passphrase": "Public Global Stellar Network ; September 2015", // Official mainnet passphrase + "average_blocktime_ms": 5000, + "is_testnet": false + +``` + +## Configuration Examples + +### Basic EVM Network + +```json + + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, + "required_confirmations": 12, + "symbol": "ETH", + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"], + "explorer_urls": ["https://etherscan.io"], + "average_blocktime_ms": 12000, + "is_testnet": false, + "tags": ["mainnet", "ethereum"] + +``` + +### Layer 2 EVM Network with Tags + +```json + + "type": "evm", + "network": "optimism", + "chain_id": 10, + "required_confirmations": 1, + "symbol": "ETH", + "rpc_urls": [ + "https://mainnet.optimism.io", + "https://optimism.drpc.org" + ], + "features": ["eip1559"], + "tags": ["rollup", "optimism"], + "average_blocktime_ms": 2000, + "is_testnet": false + +``` + +### Solana Network + +```json + + "type": "solana", + "network": "mainnet-beta", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "average_blocktime_ms": 400, + "is_testnet": false, + "tags": ["mainnet", "solana"] + +``` + +### Stellar Network + +```json + + "type": "stellar", + "network": "pubnet", + "rpc_urls": ["https://mainnet.sorobanrpc.com"], + "passphrase": "Public Global Stellar Network ; September 2015", + "explorer_urls": ["https://stellar.expert/explorer/public"], + "average_blocktime_ms": 5000, + "is_testnet": false, + "tags": ["mainnet", "stellar"] + +``` + +## Network Inheritance + +Networks can inherit from other networks of the same type, allowing you to create variants without duplicating configuration: + +```json + + "networks": [ + { + "type": "evm", + "network": "ethereum-base", + "chain_id": 1, + "required_confirmations": 12, + "symbol": "ETH", + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"] + , + + "from": "ethereum-base", + "type": "evm", + "network": "ethereum-sepolia", + "chain_id": 11155111, + "required_confirmations": 3, + "rpc_urls": ["https://sepolia.infura.io/v3/YOUR_KEY"], + "is_testnet": true + + ] +} +``` + +When using inheritance: + +* The child network inherits all fields from the parent +* Fields specified in the child override parent values +* The `from` field must reference a network of the same type + +## Using Networks in Relayer Configuration + +Once networks are defined, reference them in your relayer configurations: + +```json + + "relayers": [ + { + "id": "my-evm-relayer", + "name": "My EVM Relayer", + "network": "ethereum-mainnet", // References network ID + "network_type": "evm", + "signer_id": "my-signer" + + ] + } +} +``` + +## Best Practices + +### 1. Network Organization +* Group related networks in separate files (e.g., `ethereum.json`, `polygon.json`) +* Use consistent naming conventions for network identifiers +* Include both mainnet and testnet configurations + +### 2. RPC URLs +* Always configure multiple RPC URLs for redundancy +* Use private/dedicated RPC endpoints for production +* Ensure URLs are secure (HTTPS) when accessing over public networks + +### 3. Confirmation Requirements +* Set appropriate `required_confirmations` based on network security +* Higher values for mainnet, lower for testnets +* Consider network-specific finality characteristics + +### 4. Tags and Features +* Use tags to categorize networks (e.g., "mainnet", "testnet", "rollup") +* Enable appropriate features (e.g., "eip1559" for supported networks) +* Document custom tags used in your organization + +### 5. Inheritance +* Create base configurations for common settings +* Use inheritance to reduce duplication +* Override only necessary fields in child networks + +## Troubleshooting + +### Common Issues + +***Network not found:*** + +* Ensure the network identifier in relayer config matches exactly +* Check that network configuration files are in the correct location +* Verify JSON syntax is valid + +***RPC connection failures:*** + +* Test RPC URLs independently before configuring +* Ensure firewall/network allows outbound HTTPS connections +* Check API keys are included in RPC URLs where required + +***Invalid configuration:*** + +* Validate required fields are present for network type +* Ensure numeric fields (chain_id, confirmations) are numbers, not strings +* Check that inherited networks reference existing parent networks + +## See Also + +* [Relayer Configuration](/relayer/1.1.x/configuration) +* [Quickstart Guide](/relayer/1.1.x/quickstart) +* [Solana Integration](/relayer/1.1.x/solana) +* [API Reference](/relayer/1.1.x/api) diff --git a/docs/content/relayer/1.1.x/plugins/channels.mdx b/docs/content/relayer/1.1.x/plugins/channels.mdx new file mode 100644 index 00000000..72e35f8b --- /dev/null +++ b/docs/content/relayer/1.1.x/plugins/channels.mdx @@ -0,0 +1,621 @@ +--- +title: Channels +--- + +## Overview + +Channels is a plugin for OpenZeppelin Relayer that enables parallel transaction submission on Stellar using channel accounts with automatic fee bumping. Channel accounts provide unique sequence numbers for concurrent transaction processing, eliminating sequence number conflicts. + +The plugin features: + +* ***Parallel Transaction Processing***: Uses a pool of channel accounts for concurrent submissions +* ***Automatic Fee Bumping***: Dedicated fund account pays transaction fees +* ***Dynamic Pool Management***: Channel accounts acquired and released automatically +* ***Transaction Simulation***: Automatically simulates and builds transactions with proper resources +* ***Management API***: Dynamic configuration of channel accounts + +## Using the SDK Client + +The fastest way to interact with Channels is through the OpenZeppelin Relayer SDK, which provides a type-safe, unified interface for transaction submission and management operations. + +### Installation + +```bash +npm install @openzeppelin/relayer-sdk +# or +pnpm add @openzeppelin/relayer-sdk +``` + +### Client Setup + +The SDK supports two connection modes with identical APIs: + +```typescript +import { ChannelsClient, ChannelsRelayerClient } from '@openzeppelin/relayer-sdk'; + +// Direct HTTP connection to Channels service +const directClient = new ChannelsClient({ + baseUrl: 'https://channels.openzeppelin.com', // Channels service URL + apiKey: process.env.CHANNELS_API_KEY, // Service API key + adminSecret: process.env.CHANNELS_ADMIN, // Optional: for management + timeout: 30000, // Optional: request timeout +}); + +// Connection via OpenZeppelin Relayer plugin +const relayerClient = new ChannelsRelayerClient({ + pluginId: 'channels-plugin', // Plugin identifier + baseUrl: 'http://localhost:8080', // Optional: Relayer URL + apiKey: process.env.RELAYER_API_KEY, // Relayer API key + adminSecret: process.env.CHANNELS_ADMIN, // Optional: for management +}); +``` + +### Submitting Transactions + +Channels provides separate methods for different transaction types: + +```typescript +// Submit signed transaction (XDR) +const result = await client.submitTransaction({ + xdr: 'AAAAAgAAAABQEp+s8xGPrF...', // Complete signed envelope +}); + +// Submit Soroban transaction (func + auth) +const result = await client.submitSorobanTransaction({ + func: 'AAAABAAAAAEAAAAGc3ltYm9s...', // Host function XDR + auth: ['AAAACAAAAAEAAAA...', 'AAAACAAAAAEAAAB...'], // Detached auth entries +}); + +console.log('Transaction:', result.transactionId, result.hash, result.status); +``` + +### Managing Channel Accounts + +For managing channel accounts, the SDK provides the following methods: + +```typescript +// List configured channel accounts +const accounts = await client.listChannelAccounts(); +console.log('Channel accounts:', accounts.relayerIds); + +// Update channel accounts (requires adminSecret) +const result = await client.setChannelAccounts(['channel-001', 'channel-002', 'channel-003']); +console.log('Update successful:', result.ok, result.appliedRelayerIds); +``` + +### Example Scripts + +Complete working examples are available in the SDK repository: + +* [channels-direct-example.ts](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/blob/main/examples/clients/channels-direct-example.ts) - Direct HTTP connection +* [channels-relayer-example.ts](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/blob/main/examples/clients/channels-relayer-example.ts) - OpenZeppelin Relayer connection + +## Prerequisites + +* Node.js >= 18 +* pnpm >= 10 +* OpenZeppelin Relayer (installed and configured) + +## Example Setup + +For a complete working example with Docker Compose, refer to the Channels plugin example in the OpenZeppelin Relayer repository: + +* ***Location***: [examples/channels-plugin-example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example) +* ***Documentation***: [README.md](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example/README.md) + +## Installation + +Channels can be added to any OpenZeppelin Relayer installation. + +***Resources:*** + +* ***npm Package***: [@openzeppelin/relayer-plugin-channels](https://www.npmjs.com/package/@openzeppelin/relayer-plugin-channels) +* ***GitHub Repository***: https://github.com/OpenZeppelin/relayer-plugin-channels +* ***Example Setup***: [channels-plugin-example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example) + +### Install from npm + +```bash +# From the root of your Relayer repository +cd plugins +pnpm add @openzeppelin/relayer-plugin-channels +``` + +### Create the plugin wrapper + +Inside your Relayer, create a directory for the plugin and expose its handler: + +```bash +mkdir -p plugins/channels +``` + +Create `plugins/channels/index.ts`: + +```typescript +export { handler } from '@openzeppelin/relayer-plugin-channels'; +``` + +## Configuration + +### Plugin Registration + +Register the plugin in your `config/config.json` file: + +```json +{ + "plugins": [ + { + "id": "channels-plugin", + "path": "channels/index.ts", + "timeout": 60 + } + ] +} +``` + +### Environment Variables + +Configure the required environment variables: + +```bash +# Required environment variables +export STELLAR_NETWORK="testnet" # or "mainnet" +export SOROBAN_RPC_URL="https://soroban-testnet.stellar.org" +export FUND_RELAYER_ID="channels-fund" # ID of the fund relayer +export PLUGIN_ADMIN_SECRET="your-secret-here" # Required for management API + +# Optional environment variables +export LOCK_TTL_SECONDS=30 # Lock timeout (default: 30, range: 3-30) +export MAX_FEE=1000000 # Maximum fee in stroops (default: 1,000,000) +``` + +***Required Variables:*** + +* `STELLAR_NETWORK`: Either "testnet" or "mainnet" +* `SOROBAN_RPC_URL`: Stellar Soroban RPC endpoint URL +* `FUND_RELAYER_ID`: Relayer ID for the account that pays transaction fees + +***Optional Variables:*** + +* `PLUGIN_ADMIN_SECRET`: Secret for accessing the management API (required to manage channel accounts) +* `LOCK_TTL_SECONDS`: TTL for channel account locks in seconds (default: 30, range: 3-30) +* `MAX_FEE`: Maximum transaction fee in stroops (default: 1,000,000) + +### Relayer Configuration + +Channels requires two types of relayers: + +1. ***Fund Account***: The account that pays transaction fees (should have `concurrent_transactions: true` enabled) +2. ***Channel Accounts***: At least one channel account (recommended: 2 or more for better throughput) + +Configure relayers in your `config/config.json`: + +```json +{ + "relayers": [ + { + "id": "channels-fund", + "name": "Channels Fund Account", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "channels-fund-signer", + "policies": { + "concurrent_transactions": true + } + }, + { + "id": "channel-001", + "name": "Channel Account 001", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "channel-001-signer" + }, + { + "id": "channel-002", + "name": "Channel Account 002", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "channel-002-signer" + } + ], + "notifications": [], + "signers": [ + { + "id": "channels-fund-signer", + "type": "local", + "config": { + "path": "config/keys/channels-fund.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE_FUND" + } + } + }, + { + "id": "channel-001-signer", + "type": "local", + "config": { + "path": "config/keys/channel-001.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE_CHANNEL_001" + } + } + }, + { + "id": "channel-002-signer", + "type": "local", + "config": { + "path": "config/keys/channel-002.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE_CHANNEL_002" + } + } + } + ], + "networks": "./config/networks", + "plugins": [ + { + "id": "channels", + "path": "channel/index.ts", + "timeout": 30, + "emit_logs": true, + "emit_traces": true + } + ] +} +``` + +***Important Configuration Notes:*** + +* ***Fund Account*** (`channels-fund`): Must have `"concurrent_transactions": true` in policies to enable parallel transaction processing +* ***Channel Accounts***: Create at least 2 for better throughput (you can add more as `channel-003`, etc.) +* ***Network***: Use `testnet` for testing or `mainnet` for production +* ***Signers***: Each relayer references a signer by `signer_id`, and signers are defined separately with keystore paths +* ***Keystore Files***: You’ll need to create keystore files for each account - see [OpenZeppelin Relayer documentation](https://docs.openzeppelin.com/relayer) for details on creating and managing keys +* ***Plugin Registration***: The plugin `id` should match what you use in environment variables and API calls + +After configuration, fund these accounts on-chain and register them with Channels (see "Initializing Channel Accounts" below). + +## Initializing Channel Accounts + +After configuring your relayers in `config.json` and funding the Stellar accounts, register them with Channels via the Management API: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "setChannelAccounts", + "adminSecret": "your-secret-here", + "relayerIds": ["channel-001", "channel-002"] + } + } + }' +``` + +***Response:*** + +```json +{ + "success": true, + "data": { + "ok": true, + "appliedRelayerIds": ["channel-001", "channel-002"] + }, + "error": null +} +``` + +This tells Channels which relayers to use as channel accounts. All relayer IDs must match your configured relayer IDs in `config.json`. + +Channels is now ready to serve Soroban transactions. + +## Automated Setup + +To skip the manual configuration steps, use the provided automation script. It automates the entire setup process: creating signers and relayers via the API, funding accounts on-chain, and registering them with Channels. + +### Prerequisites + +When using the automated setup, you only need to configure and fund the ***fund account***: + +```json +{ + "relayers": [ + { + "id": "channels-fund", + "chain": "stellar", + "signer": "channels-fund-signer", + "policies": { + "concurrent_transactions": true + } + } + ] +} +``` + +The script creates all channel account signers and relayers dynamically - no config.json entries needed for channel accounts. + +### Running the Script + +```bash +pnpm exec tsx ./scripts/create-channel-accounts.ts \ + --total 3 \ + --base-url http://localhost:8080 \ + --api-key \ + --funding-relayer channels-fund \ + --plugin-id channels-plugin \ + --plugin-admin-secret \ + --network testnet +``` + +### What the Script Does + +1. ***Creates channel account signers and relayers via API***: Following the naming pattern `channel-0001`, `channel-0002`, etc. +2. ***Funds channel accounts on-chain***: Submits funding transactions through the fund relayer and waits for confirmation +3. ***Registers with Channels***: Automatically calls the Management API to register all channel accounts + +### Script Options + +* `--total`: Number of channel accounts to create (recommended: 2-3 for testing, more for production) +* `--fix`: Audit and heal partially created state (use if the script was interrupted) +* `--dry-run`: Preview actions without making changes +* `--prefix`: Customize the naming prefix (default: `channel-`) +* `--starting-balance`: XLM amount for each account (default: 5) + +### Script Location + +* Example directory: [`scripts/create-channel-accounts.ts`](https://github.com/OpenZeppelin/relayer-plugin-channels/blob/main/scripts/create-channel-accounts.ts) + +## API Usage + +Channels is invoked by making POST requests to the plugin endpoint: + +```bash +POST /api/v1/plugins/{plugin-id}/call +``` + +### Submitting Transactions + +There are two ways to submit transactions to Channels: + +#### Option 1: Complete Transaction XDR + +Submit a complete, signed transaction envelope: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "xdr": "AAAAAgAAAAA..." + } + }' +``` + +#### Option 2: Soroban Function + Auth + +Submit just the Soroban function and authorization entries: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "func": "AAAABAAAAAEAAAAGc3ltYm9s...", + "auth": ["AAAACAAAAAEAAAA..."] + } + }' +``` + +### Parameters + +* `xdr` (string): Complete transaction envelope XDR (base64) - must be signed, not a fee-bump envelope +* `func` (string): Soroban host function XDR (base64) +* `auth` (array of strings): Array of Soroban authorization entry XDRs (base64) + +***Important Notes:*** + +* Provide either `xdr` OR `func`+`auth`, not both +* When using `xdr`, the transaction must be a regular signed transaction (not a fee-bump envelope) +* When using `func`+`auth`, Channels will build and simulate the transaction automatically +* Transactions are always submitted with fee bumping from the fund account + +### Generating XDR with Stellar SDK + +Use the `@stellar/stellar-sdk` to generate the required XDR values: + +#### Full Transaction Envelope XDR + +```typescript +import { Networks, TransactionBuilder, rpc } from '@stellar/stellar-sdk'; + +// Build your transaction +const tx = new TransactionBuilder(account, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(/* Operation.invokeHostFunction from Contract.call(...) */) + .setTimeout(30) + .build(); + +// Sign the transaction +tx.sign(keypair); + +// Export base64 envelope XDR +const envelopeXdr = tx.toXDR(); +``` + +#### Soroban Function + Auth XDRs + +```typescript +// Build and simulate first to obtain auth +const baseTx = /* TransactionBuilder(...).addOperation(...).build() */; +const sim = await rpcServer.simulateTransaction(baseTx); + +// Apply simulation, then extract from the InvokeHostFunction operation +const assembled = rpc.assembleTransaction(baseTx, sim).build(); +const op = assembled.operations[0]; // Operation.InvokeHostFunction + +const funcXdr = op.func.toXDR("base64"); +const authXdrs = (op.auth ?? []).map(a => a.toXDR("base64")); +``` + +## Management API + +Channels provides a management API to dynamically configure channel accounts. This API requires authentication via the `PLUGIN_ADMIN_SECRET` environment variable. + +### List Channel Accounts + +Get the current list of configured channel accounts: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "listChannelAccounts", + "adminSecret": "your-secret-here" + } + } + }' +``` + +***Response:*** + +```json +{ + "success": true, + "data": { + "relayerIds": ["channel-001", "channel-002"] + }, + "error": null +} +``` + +### Set Channel Accounts + +Configure the channel accounts that Channels will use. This replaces the entire list: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "setChannelAccounts", + "adminSecret": "your-secret-here", + "relayerIds": ["channel-001", "channel-002", "channel-003"] + } + } + }' +``` + +***Response:*** + +```json +{ + "success": true, + "data": { + "ok": true, + "appliedRelayerIds": ["channel-001", "channel-002", "channel-003"] + }, + "error": null +} +``` + +***Important Notes:*** + +* You must configure at least one channel account before Channels can process transactions +* The management API will prevent removing accounts that are currently locked (in use). On failure it returns HTTP 409 with code `LOCKED_CONFLICT` and `details.locked` listing the blocked IDs +* All relayer IDs must exist in your OpenZeppelin Relayer configuration +* The `adminSecret` must match the `PLUGIN_ADMIN_SECRET` environment variable + +## Responses + +All API responses use the standard Relayer envelope format: ` success, data, error, metadata `. + +### Success Response (HTTP 200) + +```json +{ + "success": true, + "data": { + "transactionId": "tx_123456", + "status": "confirmed", + "hash": "1234567890abcdef..." + }, + "error": null +} +``` + +***Response Fields:*** + +* `success`: `true` when the plugin executed successfully +* `data`: Contains the transaction result +* `transactionId`: The OpenZeppelin Relayer transaction ID +* `status`: Transaction status (e.g., "confirmed") +* `hash`: The Stellar transaction hash +* `error`: `null` on success + +### Error Response (HTTP 4xx) + +```json +{ + "success": false, + "data": { + "code": "POOL_CAPACITY", + "details": {} + }, + "error": "Too many transactions queued. Please try again later", + "metadata": { + "logs": [ + { "level": "error", "message": "All channel accounts in use" } + ] + } +} +``` + +***Error Response Fields:*** + +* `success`: `false` when the plugin encountered an error +* `data`: Contains error details +* `code`: Error code (e.g., "POOL_CAPACITY", "LOCKED_CONFLICT") +* `details`: Additional context about the error +* `error`: Human-readable error message +* `metadata.logs`: Plugin execution logs (if `emit_logs` is enabled) + +### Common Error Codes + +* `CONFIG_MISSING`: Missing required environment variable +* `UNSUPPORTED_NETWORK`: Invalid network type +* `INVALID_PARAMS`: Invalid request parameters +* `INVALID_XDR`: Failed to parse XDR +* `INVALID_ENVELOPE_TYPE`: Not a regular transaction envelope (e.g., fee bump) +* `INVALID_TIME_BOUNDS`: TimeBounds too far in the future +* `NO_CHANNELS_CONFIGURED`: No channel accounts have been configured via management API +* `POOL_CAPACITY`: All channel accounts in use +* `RELAYER_UNAVAILABLE`: Relayer not found +* `SIMULATION_FAILED`: Transaction simulation failed +* `ONCHAIN_FAILED`: Transaction failed on-chain +* `WAIT_TIMEOUT`: Transaction wait timeout +* `MANAGEMENT_DISABLED`: Management API not enabled +* `UNAUTHORIZED`: Invalid admin secret +* `LOCKED_CONFLICT`: Cannot remove locked channel accounts + +## Additional Resources + +* ***Stellar SDK Documentation***: https://stellar.github.io/js-stellar-sdk/ +* ***Soroban Documentation***: https://soroban.stellar.org/docs +* ***OpenZeppelin Relayer Documentation***: https://docs.openzeppelin.com/relayer diff --git a/docs/content/relayer/1.1.x/plugins/index.mdx b/docs/content/relayer/1.1.x/plugins/index.mdx new file mode 100644 index 00000000..d9f55322 --- /dev/null +++ b/docs/content/relayer/1.1.x/plugins/index.mdx @@ -0,0 +1,433 @@ +--- +title: Plugins +--- + +## Overview + +OpenZeppelin Relayer supports plugins to extend the functionality of the relayer. + +Plugins are `TypeScript` functions running in the Relayer server that can include any arbitrary logic defined by the Relayer operator. + +The plugin system features: +- ***Handler Pattern***: Simple export-based plugin development +- ***TypeScript Support***: Full type safety and IntelliSense +- ***Plugin API***: Clean interface for interacting with relayers +- ***Docker Integration***: Seamless development and deployment +- ***Comprehensive Error Handling***: Detailed logging and debugging capabilities + +## Configuration + +### Writing a Plugin + +Plugins are declared under `plugins` directory, and are expected to be TypeScript files (`.ts` extension). + +```bash +openzeppelin-relayer/ +├── plugins/ +│ └── my-plugin.ts # Plugin code +└── config/ + └── config.json # Plugins in configuration file +``` + +#### Handler Pattern (Recommended) + +This approach uses a simple `handler` export pattern: + +```typescript +/// Required imports. +import { Speed, PluginAPI } from "@openzeppelin/relayer-sdk"; + +/// Define your plugin parameters interface +type MyPluginParams = { + destinationAddress: string; + amount?: number; + message?: string; + relayerId?: string; +}; + +/// Define your plugin return type +type MyPluginResult = { + success: boolean; + transactionId: string; + message: string; +}; + +/// Export a handler function - that's it! +export async function handler(api: PluginAPI, params: MyPluginParams): Promise { + console.info("🚀 Plugin started..."); + + // Validate parameters + if (!params.destinationAddress) { + throw new Error("destinationAddress is required"); + } + + // Use the relayer API + const relayer = api.useRelayer(params.relayerId || "my-relayer"); + + const result = await relayer.sendTransaction({ + to: params.destinationAddress, + value: params.amount || 1, + data: "0x", + gas_limit: 21000, + speed: Speed.FAST, + }); + + console.info(`Transaction submitted: ${result.id}`); + + // Wait for confirmation + await result.wait({ + interval: 5000, // Check every 5 seconds + timeout: 120000 // Timeout after 2 minutes + }); + + return { + success: true, + transactionId: result.id, + message: `Successfully sent ${params.amount || 1} wei to ${params.destinationAddress}` + }; +} +``` + +#### Legacy Pattern (Deprecated, but supported) + + +The `runPlugin()` pattern is deprecated and will be removed in a future version. Please migrate to the handler export pattern above. Legacy plugins will continue to work but will show deprecation warnings. + + +```typescript +import { runPlugin, PluginAPI } from "./lib/plugin"; + +async function myPlugin(api: PluginAPI, params: any) { + // Plugin logic here + return "result"; +} + +runPlugin(myPlugin); // ⚠️ Deprecated - shows warning but still works +``` + +***Example legacy plugin*** (`plugins/examples/example-deprecated.ts`): +```typescript +import { PluginAPI, runPlugin } from "../lib/plugin"; +import { Speed } from "@openzeppelin/relayer-sdk"; + +type Params = { + destinationAddress: string; +}; + +async function example(api: PluginAPI, params: Params): Promise { + console.info("Plugin started..."); + + const relayer = api.useRelayer("sepolia-example"); + const result = await relayer.sendTransaction({ + to: params.destinationAddress, + value: 1, + data: "0x", + gas_limit: 21000, + speed: Speed.FAST, + }); + + await result.wait(); + return "done!"; +} + +runPlugin(example); +``` + +### Declaring in config file + +Plugins are configured in the `./config/config.json` file, under the `plugins` key. + +The file contains a list of plugins, each with an id, path and timeout in seconds (optional). + + +The plugin path is relative to the `/plugins` directory + + +Example: + +```json +{ +"plugins": [ + { + "id": "my-plugin", + "path": "my-plugin.ts", + "timeout": 30 + } +] +} +``` + +### Timeout + +The timeout is the maximum time **in seconds** that the plugin can run. If the plugin exceeds the timeout, it will be terminated with an error. + +The timeout is optional, and if not provided, the default is 300 seconds (5 minutes). + +## Plugin Development Guidelines + +### TypeScript Best Practices + +* ***Define Parameter Types***: Always create interfaces or types for your plugin parameters +* ***Define Return Types***: Specify what your plugin returns for better developer experience +* ***Handle Errors Gracefully***: Use try-catch blocks and return structured error responses +* ***Validate Input***: Check required parameters and provide meaningful error messages +* ***Use Async/Await***: Modern async patterns for better readability + +### Testing Your Plugin + +You can test your handler function directly: + +```typescript +import { handler } from './my-plugin'; + +// Mock API for testing (in real scenarios, use proper mocking) +const mockApi = { + useRelayer: (id: string) => ({ + sendTransaction: async (tx: any) => ({ id: "test-tx-123", wait: async () => {} }) + }) +} as any; + +const result = await handler(mockApi, { + destinationAddress: "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", + amount: 1000 +}); +console.log(result); +``` + +## Invocation + +Plugins are invoked by hitting the `api/v1/plugins/plugin-id/call` endpoint. + +The endpoint accepts a `POST` request. Example post request body: + +```json +{ + "destinationAddress": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", + "amount": 1000000000000000, + "message": "Hello from OpenZeppelin Relayer!" +} +``` + +The parameters are passed directly to your plugin's `handler` function. + +## Debugging + +When invoking a plugin, the response will include: + +* `logs`: The logs from the plugin execution. +* `return_value`: The returned value of the plugin execution. +* `error`: An error message if the plugin execution failed. +* `traces`: A list of messages sent between the plugin and the Relayer instance. This includes all the payloads passed through the `PluginAPI` object. + +### Complete Example + +1. ***Plugin Code*** (`plugins/example.ts`): + +```typescript +import { Speed, PluginAPI } from "@openzeppelin/relayer-sdk"; + +type ExampleParams = { + destinationAddress: string; + amount?: number; + message?: string; +}; + +type ExampleResult = { + success: boolean; + transactionId: string; + transactionHash: string | null; + message: string; + timestamp: string; +}; + +export async function handler(api: PluginAPI, params: ExampleParams): Promise { + console.info("🚀 Example plugin started"); + console.info(`📋 Parameters:`, JSON.stringify(params, null, 2)); + + try { + // Validate parameters + if (!params.destinationAddress) { + throw new Error("destinationAddress is required"); + } + + const amount = params.amount || 1; + const message = params.message || "Hello from OpenZeppelin Relayer!"; + + console.info(`💰 Sending ${amount} wei to ${params.destinationAddress}`); + + // Get relayer and send transaction + const relayer = api.useRelayer("my-relayer"); + const result = await relayer.sendTransaction({ + to: params.destinationAddress, + value: amount, + data: "0x", + gas_limit: 21000, + speed: Speed.FAST, + }); + + console.info(`✅ Transaction submitted: ${result.id}`); + + // Wait for confirmation + const confirmation = await result.wait({ + interval: 5000, + timeout: 120000 + }); + + console.info(`🎉 Transaction confirmed: ${confirmation.hash}`); + + return { + success: true, + transactionId: result.id, + transactionHash: confirmation.hash || null, + message: `Successfully sent ${amount} wei to ${params.destinationAddress}. ${message}`, + timestamp: new Date().toISOString() + }; + + } catch (error) { + console.error("❌ Plugin execution failed:", error); + return { + success: false, + transactionId: "", + transactionHash: null, + message: `Plugin failed: ${(error as Error).message}`, + timestamp: new Date().toISOString() + }; + } +} +``` + +1. ***Plugin Configuration*** (`config/config.json`): + +```json +{ + "plugins": [ + { + "id": "example-plugin", + "path": "example-plugin.ts", + "timeout": 30 + } + ] +} +``` + +1. ***API Invocation***: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/example-plugin/call \ +-H "Content-Type: application/json" \ +-H "Authorization: Bearer YOUR_API_KEY" \ +-d '{ + "destinationAddress": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", + "amount": 1000000000000000, + "message": "Test transaction from plugin" +}' +``` + +1. ***API Response***: + +```json +{ + "success": true, + "message": "Plugin called successfully", + "logs": [ + { + "level": "info", + "message": "🚀 Example plugin started" + }, + { + "level": "info", + "message": "💰 Sending 1000000000000000 wei to 0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A" + }, + { + "level": "info", + "message": "✅ Transaction submitted: tx-123456" + }, + { + "level": "info", + "message": "🎉 Transaction confirmed: 0xabc123..." + } + ], + "return_value": { + "success": true, + "transactionId": "tx-123456", + "transactionHash": "0xabc123def456...", + "message": "Successfully sent 1000000000000000 wei to 0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A. Test transaction from plugin", + "timestamp": "2024-01-15T10:30:00.000Z" + }, + "error": "", + "traces": [ + { + "relayer_id": "my-relayer", + "method": "sendTransaction", + "payload": { + "to": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", + "value": "1000000000000000", + "data": "0x", + "gas_limit": 21000, + "speed": "fast" + } + } + ] +} +``` + +## Response Fields + +* ***`logs`***: Terminal output from the plugin (console.log, console.error, etc.) +* ***`return_value`***: The value returned by your plugin's handler function +* ***`error`***: Error message if the plugin execution failed +* ***`traces`***: Messages exchanged between the plugin and the Relayer instance via PluginAPI + +## Migration from Legacy Pattern + +### Current Status + +* ✅ ***Legacy plugins still work*** - No immediate action required +* ⚠️ ***Deprecation warnings*** - Legacy plugins will show console warnings +* 📅 ***Future removal*** - The `runPlugin` pattern will be removed in a future major version +* 🎯 ***Recommended action*** - Migrate to handler pattern for new plugins + +### Migration Steps + +If you have existing plugins using `runPlugin()`, migration is simple: + +***Before (Legacy - still works)***: +```typescript +import { runPlugin, PluginAPI } from "./lib/plugin"; + +async function myPlugin(api: PluginAPI, params: any): Promise { + // Your plugin logic + return result; +} + +runPlugin(myPlugin); // ⚠️ Shows deprecation warning +``` + +***After (Modern - recommended)***: +```typescript +import { PluginAPI } from "@openzeppelin/relayer-sdk"; + +export async function handler(api: PluginAPI, params: any): Promise { + // Same plugin logic - just export as handler! + return result; +} +``` + +### Step-by-Step Migration + +1. ***Remove the `runPlugin()` call*** at the bottom of your file +2. ***Rename your function to `handler`*** (or create a new handler export) +3. ***Export the `handler` function*** using `export async function handler` +4. ***Add proper TypeScript types*** for better development experience +5. ***Test your plugin*** to ensure it works with the new pattern +6. ***Update your documentation*** to reflect the new pattern + +### Backwards Compatibility + +The relayer will automatically detect which pattern your plugin uses: + +* If it finds a `handler` export → uses modern pattern +* If no `handler` but `runPlugin()` was called → uses legacy pattern with warning +* If neither → shows clear error message + +This ensures a smooth transition period where both patterns work simultaneously. diff --git a/docs/content/relayer/1.1.x/plugins/launchtube.mdx b/docs/content/relayer/1.1.x/plugins/launchtube.mdx new file mode 100644 index 00000000..c80cf96b --- /dev/null +++ b/docs/content/relayer/1.1.x/plugins/launchtube.mdx @@ -0,0 +1,499 @@ +--- +title: Launchtube +--- + +## Overview + +Launchtube is a plugin for OpenZeppelin Relayer that simplifies submitting Stellar Soroban transactions by automatically handling the complexity of getting transactions on-chain. + +The plugin features: + +* ***Automatic Fee Bumping***: Uses a dedicated fund account to pay transaction fees +* ***Sequence Number Management***: Maintains a pool of sequence accounts for concurrent transaction processing +* ***Transaction Simulation***: Automatically simulates and rebuilds transactions with proper resources +* ***Retry Logic***: Built-in error handling and retry mechanisms +* ***Management API***: Dynamic configuration of sequence accounts + +## Prerequisites + +* Node.js >= 18 +* pnpm >= 10 +* OpenZeppelin Relayer (installed and configured) +* Stellar testnet or mainnet account +* Funded Stellar accounts for the fund and sequence accounts + +## Installation + +Launchtube can be added to any OpenZeppelin Relayer installation. + +***Resources:*** + +* ***npm Package***: [@openzeppelin/relayer-plugin-launchtube](https://www.npmjs.com/package/@openzeppelin/relayer-plugin-launchtube) +* ***GitHub Repository***: https://github.com/OpenZeppelin/relayer-plugin-launchtube +* ***Example Setup***: [launchtube-plugin-example](https://github.com/OpenZeppelin/openzeppelin-relayer/blob/0550c49444be585c6d40c43514758f57604d818b/examples/launchtube-plugin-example) + +### Install from npm + +```bash +# From the root of your Relayer repository +cd plugins +pnpm add @openzeppelin/relayer-plugin-launchtube +``` + +### Create the plugin wrapper + +Inside your Relayer, create a directory for the plugin and expose its handler: + +```bash +mkdir -p plugins/launchtube +``` + +Create `plugins/launchtube/index.ts`: + +```typescript +export { handler } from '@openzeppelin/relayer-plugin-launchtube'; +``` + +## Configuration + +### Plugin Registration + +Register the plugin in your `config/config.json` file: + +```json +{ + "plugins": [ + { + "id": "launchtube-plugin", + "path": "launchtube/index.ts", + "timeout": 60 + } + ] +} +``` + +### Environment Variables + +Configure the required environment variables: + +```bash +# Required environment variables +export STELLAR_NETWORK="testnet" # or "mainnet" +export SOROBAN_RPC_URL="https://soroban-testnet.stellar.org" +export FUND_RELAYER_ID="launchtube-fund" # ID of the fund relayer +export LAUNCHTUBE_ADMIN_SECRET="your-secret-here" # Required for management API + +# Optional environment variables +export LOCK_TTL_SECONDS=30 # Lock timeout (default: 30, range: 10-30) +``` + +***Required Variables:*** + +* `STELLAR_NETWORK`: Either "testnet" or "mainnet" +* `SOROBAN_RPC_URL`: Stellar Soroban RPC endpoint URL +* `FUND_RELAYER_ID`: Relayer ID for the account that pays transaction fees + +***Optional Variables:*** + +* `LAUNCHTUBE_ADMIN_SECRET`: Secret for accessing the management API (required to manage sequence accounts) +* `LOCK_TTL_SECONDS`: TTL for sequence account locks in seconds (default: 30, range: 10-30) + +### Relayer Configuration + +Launchtube requires two types of relayers: + +1. ***Fund Account***: The account that pays transaction fees (should have `concurrent_transactions: true` enabled) +2. ***Sequence Accounts***: At least one sequence account (recommended: 2 or more for better throughput) + +Configure relayers in your `config/config.json`: + +```json +{ + "relayers": [ + { + "id": "launchtube-fund", + "chain": "stellar", + "signer": "launchtube-fund-signer", + "policies": { + "concurrent_transactions": true + } + }, + { + "id": "launchtube-seq-001", + "chain": "stellar", + "signer": "launchtube-seq-001-signer" + }, + { + "id": "launchtube-seq-002", + "chain": "stellar", + "signer": "launchtube-seq-002-signer" + } + ] +} +``` + + +The fund relayer should have `concurrent_transactions: true` enabled to allow parallel transaction processing. + + +After configuration, fund these accounts on-chain and register them with Launchtube (see "Initializing Sequence Accounts" below). + +## Initializing Sequence Accounts + +After configuring your relayers in `config.json` and funding the Stellar accounts, register them with Launchtube via the Management API: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/launchtube-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "setSequenceAccounts", + "adminSecret": "your-secret-here", + "relayerIds": ["launchtube-seq-001", "launchtube-seq-002"] + } + } + }' +``` + +***Response:*** + +```json +{ + "success": true, + "data": { + "appliedRelayerIds": ["launchtube-seq-001", "launchtube-seq-002"] + }, + "error": null +} +``` + +This tells Launchtube which relayers to use as sequence accounts. All relayer IDs must match your configured relayer IDs in `config.json`. + +Launchtube is now ready to serve Soroban transactions. + +## Automated Setup + +To skip the manual configuration steps, use the provided automation script. It automates the entire setup process: creating signers and relayers via the API, funding accounts on-chain, and registering them with Launchtube. + +### Prerequisites + +When using the automated setup, you only need to configure and fund the ***fund account***: + +```json +{ + "relayers": [ + { + "id": "launchtube-fund", + "chain": "stellar", + "signer": "launchtube-fund-signer", + "policies": { + "concurrent_transactions": true + } + } + ] +} +``` + +The script creates all sequence account signers and relayers dynamically - no config.json entries needed for sequence accounts. + +### Running the Script + +```bash +pnpm exec tsx ./scripts/create-sequence-accounts.ts \ + --total 3 \ + --base-url http://localhost:8080 \ + --api-key \ + --funding-relayer launchtube-fund \ + --plugin-id launchtube-plugin \ + --plugin-admin-secret \ + --network testnet +``` + +### What the Script Does + +1. ***Creates sequence account signers and relayers via API***: Following the naming pattern `lt-seq-0001`, `lt-seq-0002`, etc. +2. ***Funds sequence accounts on-chain***: Submits funding transactions through the fund relayer and waits for confirmation +3. ***Registers with Launchtube***: Automatically calls the Management API to register all sequence accounts + +### Script Options + +* `--total`: Number of sequence accounts to create (recommended: 2-3 for testing, more for production) +* `--fix`: Audit and heal partially created state (use if the script was interrupted) +* `--dry-run`: Preview actions without making changes +* `--prefix`: Customize the naming prefix (default: `lt-seq-`) +* `--starting-balance`: XLM amount for each account (default: 5) + +### Script Location + +* Example directory: [`scripts/create-sequence-accounts.ts`](https://github.com/OpenZeppelin/relayer-plugin-launchtube/blob/main/scripts/create-sequence-accounts.ts) + +## API Usage + +Launchtube is invoked by making POST requests to the plugin endpoint: + +```bash +POST /api/v1/plugins/{plugin-id}/call +``` + +### Submitting Transactions + +There are two ways to submit transactions to Launchtube: + +#### Option 1: Complete Transaction XDR + +Submit a complete, signed transaction envelope: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/launchtube-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "xdr": "AAAAAgAAAAA...", + "sim": false + } + }' +``` + +#### Option 2: Soroban Function + Auth + +Submit just the Soroban function and authorization entries: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/launchtube-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "func": "AAAABAAAAAEAAAAGc3ltYm9s...", + "auth": ["AAAACAAAAAEAAAA..."], + "sim": true + } + }' +``` + +### Parameters + +* `xdr` (string): Complete transaction envelope XDR (base64) +* `func` (string): Soroban host function XDR (base64) +* `auth` (array of strings): Array of Soroban authorization entry XDRs (base64) +* `sim` (boolean): Whether to simulate the transaction before submission + +***Important Notes:*** + +* Provide either `xdr` OR `func`+`auth`, not both +* When using `sim: true`, Launchtube will simulate the transaction and rebuild it with proper resource limits +* When using `sim: false` with `xdr`, you must provide a pre-assembled transaction with resource fees + +### Generating XDR with Stellar SDK + +Use the `@stellar/stellar-sdk` to generate the required XDR values: + +#### Full Transaction Envelope XDR + +```typescript +import { Networks, TransactionBuilder, rpc } from '@stellar/stellar-sdk'; + +// Build your transaction +const tx = new TransactionBuilder(account, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(/* Operation.invokeHostFunction from Contract.call(...) */) + .setTimeout(30) + .build(); + +// Optional: pre-simulate to set resources/fees before signing +const sim = await rpcServer.simulateTransaction(tx); +const prepared = rpc.assembleTransaction(tx, sim).build(); +prepared.sign(keypair); + +// Export base64 envelope XDR +const envelopeXdr = prepared.toXDR(); +``` + +#### Soroban Function + Auth XDRs + +```typescript +// Build and simulate first to obtain auth +const baseTx = /* TransactionBuilder(...).addOperation(...).build() */; +const sim = await rpcServer.simulateTransaction(baseTx); + +// Apply simulation, then extract from the InvokeHostFunction operation +const assembled = rpc.assembleTransaction(baseTx, sim).build(); +const op = assembled.operations[0]; // Operation.InvokeHostFunction + +const funcXdr = op.func.toXDR("base64"); +const authXdrs = (op.auth ?? []).map(a => a.toXDR("base64")); +``` + +## Management API + +Launchtube provides a management API to dynamically configure sequence accounts. This API requires authentication via the `LAUNCHTUBE_ADMIN_SECRET` environment variable. + +### List Sequence Accounts + +Get the current list of configured sequence accounts: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/launchtube-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "listSequenceAccounts", + "adminSecret": "your-secret-here" + } + } + }' +``` + +***Response:*** + +```json +{ + "success": true, + "data": { + "relayerIds": ["launchtube-seq-001", "launchtube-seq-002"] + }, + "error": null +} +``` + +### Set Sequence Accounts + +Configure the sequence accounts that Launchtube will use. This replaces the entire list: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/launchtube-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "setSequenceAccounts", + "adminSecret": "your-secret-here", + "relayerIds": ["launchtube-seq-001", "launchtube-seq-002", "launchtube-seq-003"] + } + } + }' +``` + +***Response:*** + +```json +{ + "success": true, + "data": { + "appliedRelayerIds": ["launchtube-seq-001", "launchtube-seq-002", "launchtube-seq-003"] + }, + "error": null +} +``` + +***Important Notes:*** + +* You must configure at least one sequence account before Launchtube can process transactions +* The management API will prevent removing accounts that are currently locked (in use). On failure it returns HTTP 409 with code `LOCKED_CONFLICT` and `details.locked` listing the blocked IDs +* All relayer IDs must exist in your OpenZeppelin Relayer configuration +* The `adminSecret` must match the `LAUNCHTUBE_ADMIN_SECRET` environment variable + +## Responses + +All API responses use the standard Relayer envelope format: ` success, data, error, metadata `. + +### Success Response (HTTP 200) + +```json +{ + "success": true, + "data": { + "transactionId": "tx_123456", + "hash": "1234567890abcdef..." + }, + "error": null +} +``` + +***Response Fields:*** + +* `success`: `true` when the plugin executed successfully +* `data`: Contains the transaction result +* `transactionId`: The OpenZeppelin Relayer transaction ID +* `hash`: The Stellar transaction hash +* `error`: `null` on success + +### Error Response (HTTP 4xx) + +```json +{ + "success": false, + "data": { + "code": "INVALID_PARAMS", + "details": { "sim": false, "xdrProvided": false } + }, + "error": "Cannot pass `sim = false` without `xdr`", + "metadata": { + "logs": [ + { "level": "error", "message": "Cannot pass `sim = false` without `xdr`" } + ] + } +} +``` + +***Error Response Fields:*** + +* `success`: `false` when the plugin encountered an error +* `data`: Contains error details +* `code`: Error code (e.g., "INVALID_PARAMS", "LOCKED_CONFLICT") +* `details`: Additional context about the error +* `error`: Human-readable error message +* `metadata.logs`: Plugin execution logs (if `emit_logs` is enabled) + +### Common Error Codes + +* `INVALID_PARAMS`: Invalid parameter combination provided +* `LOCKED_CONFLICT`: Attempting to remove sequence accounts that are currently in use +* `MISSING_PARAM`: Required parameter is missing +* `AUTH_FAILED`: Authentication failed (invalid admin secret) + +## How It Works + +Launchtube processes transactions through the following workflow: + +1. ***Request Validation***: Validates input parameters and extracts Soroban data from XDR or func+auth +2. ***Sequence Account Acquisition***: Acquires an available sequence account from the pool using Redis locks +3. ***Authorization Checking***: Validates authorization entries and determines if simulation is possible +4. ***Simulation*** (if enabled): Simulates the transaction and rebuilds it with proper resource limits and fees +5. ***Fee Bumping***: Fund account wraps the transaction in a fee bump envelope +6. ***Submission***: Sends the transaction to the Stellar network via the Soroban RPC +7. ***Confirmation***: Returns the transaction ID and hash for tracking + +This architecture enables: + +* ***Concurrent Processing***: Multiple transactions can be processed in parallel using different sequence accounts +* ***Automatic Resource Management***: Simulation ensures transactions have sufficient resources +* ***Simplified User Experience***: Users don’t need to manage sequence numbers or fee bumping + +## Example Setup + +For a complete working example with Docker Compose, refer to the Launchtube plugin example in the OpenZeppelin Relayer repository: + +* ***Location***: [examples/launchtube-plugin-example](https://github.com/OpenZeppelin/openzeppelin-relayer/blob/0550c49444be585c6d40c43514758f57604d818b/examples/launchtube-plugin-example) +* ***Documentation***: [README.md](https://github.com/OpenZeppelin/openzeppelin-relayer/blob/0550c49444be585c6d40c43514758f57604d818b/examples/launchtube-plugin-example/README.md) + +The example includes: + +* Pre-configured Docker Compose setup +* Scripts for creating and funding accounts +* Complete configuration files +* Step-by-step setup instructions +* Local plugin development workflow + +## Additional Resources + +* ***Stellar SDK Documentation***: https://stellar.github.io/js-stellar-sdk/ +* ***Soroban Documentation***: https://soroban.stellar.org/docs diff --git a/docs/content/relayer/1.1.x/quickstart.mdx b/docs/content/relayer/1.1.x/quickstart.mdx new file mode 100644 index 00000000..7870c2bc --- /dev/null +++ b/docs/content/relayer/1.1.x/quickstart.mdx @@ -0,0 +1,163 @@ +--- +title: Quick Start Guide +--- + +This guide provides step-by-step instructions for setting up OpenZeppelin Relayer. It includes prerequisites, installation, and configuration examples. + +## Prerequisites + +* Rust 2021, version `1.86` or later. +* Redis +* Docker (optional, for containerized deployment) +* Node.js, typescript and ts-node (optional, for plugins) + +## Configuration + +### Step 1: Clone the Repository + +Clone the repository and navigate to the project directory: + +```bash +git clone https://github.com/OpenZeppelin/openzeppelin-relayer +cd openzeppelin-relayer +``` + +### Step 2: Create Configuration Files + +Create environment configuration: + +```bash +cp .env.example .env +``` + +These files are already partially configured. We will add missing data in next steps. + +Ready-to-Use Example Configurations + +For quick setup with various configurations, check the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository: + +### Step 3: Create a Signer + +Generate a new signer keystore for the basic example: + +```bash +cargo run --example create_key -- \ + --password \ + --output-dir examples/basic-example/config/keys \ + --filename local-signer.json +``` +Replace `` with a strong password. + + + + +Your password must contain at least: + +* 12 characters +* One uppercase letter +* One lowercase letter +* One number +* One special character + + + +Next, update the `KEYSTORE_PASSPHRASE` in `.env` with the password you used above. + +### Step 4: Configure Notifications + +#### Configure Webhook URL + +Edit the file `config/config.json` and update the `notifications[0].url` field with your webhook URL. For a quick test, you can use a temporary URL from [Webhook.site](https://webhook.site). + +#### Configure Webhook Signing Key + +Generate a webhook signing key: + +```bash +cargo run --example generate_uuid +``` + + + +Alternatively, you can use any online UUID generator tool if you don’t want to run the included command. + + +Copy the generated UUID and update the `WEBHOOK_SIGNING_KEY` entry in `.env`. + +### Step 5: Configure API Key + +Generate an API key signing key for development: + +```bash +cargo run --example generate_uuid +``` + + + +You can also use UUID generator with a simple command on your terminal. + +```bash +uuidgen +``` + +Alternatively, you can use any online UUID generator tool. + + +Copy the generated UUID and update the `API_KEY` entry in `.env`. + +### Step 6: Run the Service + +#### Local + +Run Redis container: + +```sh +docker run --name openzeppelin-redis \ + -p 6379:6379 \ + -d redis:latest +``` + +Run Relayer service: + +```bash +cargo run +``` + +#### Docker + +Building and Running the docker image: + +```bash +docker compose up -d +``` + +By default docker compose command uses `Dockerfile.development` to build the image. If you want to use `Dockerfile.production`, you can use the following command: + +```bash +DOCKERFILE=Dockerfile.production docker compose up -d +``` + +### Step 7: Test the Relayer + +Verify the service by sending a GET request: + +```bash +curl -X GET http://localhost:8080/api/v1/relayers \ + -H "Content-Type: application/json" \ + -H "AUTHORIZATION: Bearer YOUR_API_KEY" +``` +Replace `YOUR_API_KEY` with the API key you configured in your `.env` file. + +Expected Result: A successful request should return an HTTP 200 status code along with the list of relayers. + +## Using the relayer through the API + +For detailed API usage, refer to the [API Reference](./api) page. + +## Using the relayer through the SDK + +For documentation and examples on how to consume Relayer service via SDK check [SDK documentation](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk). + +## Additional Resources and Troubleshooting + +Troubleshooting: If you encounter issues during setup or deployment, verify your environment variables, check container logs, and review your configuration files for syntax errors. diff --git a/docs/content/relayer/1.1.x/roadmap.mdx b/docs/content/relayer/1.1.x/roadmap.mdx new file mode 100644 index 00000000..80dc0279 --- /dev/null +++ b/docs/content/relayer/1.1.x/roadmap.mdx @@ -0,0 +1,111 @@ +--- +title: OpenZeppelin Relayer Roadmap +--- + +This document outlines the planned development roadmap for the OpenZeppelin Relayer project. Please note that priorities and timelines may shift based on community feedback, security considerations, and emerging blockchain ecosystem needs. + + + +This roadmap represents our current plans and is subject to change. We will update this document regularly to reflect our progress and any changes in direction. + + +## General Roadmap + +* **Stability Improvements** + * Enhanced error handling and recovery mechanisms: Implement robust exception management and failover processes to minimize downtime. + * Improved background job processing: Optimize task scheduling and queuing systems to ensure smooth and reliable asynchronous operations. + * Comprehensive test coverage: Extend unit, integration, and regression tests across all components. + * End-to-End (E2E) testing: Simulate real-world scenarios to verify complete system functionality. + * Performance optimizations: Enhance throughput and reduce latency for high-demand scenarios. + * Stress and load testing: Identify and address performance bottlenecks under extreme conditions. +* **Security Enhancements** + * External security audit: Engage third-party experts to identify and remediate vulnerabilities. + * Continuous security monitoring: Implement ongoing surveillance and incident response protocols to swiftly address threats. +* **Developer Experience** + * SDK improvements: Expand SDK capabilities, add multi-language support, and simplify integration processes. + * Enhanced documentation: Develop interactive guides, detailed API references, and comprehensive troubleshooting tips. + * Additional examples and best practices: Provide real-world usage scenarios and community-contributed tutorials to ease onboarding. +* **Features** + * Redis storage integration: Leverage Redis for fast, scalable data storage across all system components. + * Enhanced relayer balance management: Implement real-time monitoring and alerts to maintain optimal balance status. + * Dynamic gas price updates: Regularly fetch and update gas prices from multiple reliable sources to ensure accurate fee estimations. +* **Scaling Improvements** + * Horizontal scaling capabilities: Design the system architecture to seamlessly distribute workloads across multiple instances. + +## Network-Specific Roadmap + +### EVM Networks (🏗️ In Progress) + +#### Current Status +* Basic Transaction Submission +* Fee Estimation +* Transaction Status Tracking +* Flexible Network Configuration System (any EVM-compatible network via JSON configuration) +* Hosted signers support (AWS KMS, GCP, Turnkey) +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* L2 improvements +* SDK client improvements +* Full CRUD API support + +### Solana (🏗️ In Progress) + +#### Current Status +* Solana Paymaster Specification Support +* Fee estimation +* Gasless transactions +* Hosted Signer Integrations (Vault, GCP, Turnkey) +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* Extended RPC Methods +* Improved Transaction Status Checks +* Full CRUD API support + +### Stellar (🏗️ In Progress) + +#### Current Status +* Supports payment and InvokeHostFunction operations, pre-built XDR transactions, and fee bump transactions, +* Advanced transaction status logic +* Stellar-specific endpoints +* Expanded signer support +* Transaction lifecycle management logic +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* Relayer security policies: Transaction amount limits, destination whitelisting, time bound and limit operations +* Hosted signers +* Full CRUD API support + +## Community and Documentation + +### Continuous +* **Documentation** + * Comprehensive API reference + * Tutorials and guides + * Integration examples +* **Community Engagement** + * Contributing guidelines + * Support for community-driven improvements + +## Notes on Prioritization + + + + +Our development priorities are influenced by several factors: + +1. **Security**: Security enhancements always take precedence +2. **Stability**: Ensuring reliable operation across all supported networks +3. **Community Feedback**: Features requested by the community +4. **Ecosystem Developments**: Adapting to changes in blockchain protocols + + + +This roadmap is a living document and will be updated regularly to reflect changing priorities and completed milestones. We welcome community input on our direction and priorities. + +To contribute to discussions about the roadmap, please join our community channels or open an issue on our GitHub repository with your suggestions. diff --git a/docs/content/relayer/1.1.x/solana.mdx b/docs/content/relayer/1.1.x/solana.mdx new file mode 100644 index 00000000..649eea8f --- /dev/null +++ b/docs/content/relayer/1.1.x/solana.mdx @@ -0,0 +1,220 @@ +--- +title: Solana Integration +--- + +## Overview + +OpenZeppelin Relayer provides robust support for Solana networks, enabling secure transaction relaying, automated token swaps, gasless transactions, and advanced fee management. This page covers everything you need to get started and make the most of Solana-specific features. + +## Features + +* Automated token swaps via Jupiter DEX (mainnet-beta only) +* Gasless transactions (user or relayer pays fees) +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and nonce management +* Custom RPC endpoints and network policies +* Metrics and observability + +## Supported Networks + +Solana networks are defined via JSON configuration files, providing flexibility to: + +* Configure standard Solana clusters: `mainnet-beta`, `devnet`, `testnet` +* Set up custom Solana-compatible networks with specific RPC endpoints +* Create network variants using inheritance from base configurations + +Example Solana network configurations: + +```json +{ + "networks": [ + { + "type": "solana", + "network": "solana-mainnet", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "is_testnet": false, + "tags": ["mainnet", "solana"] + }, + { + "type": "solana", + "network": "solana-devnet", + "rpc_urls": ["https://api.devnet.solana.com"], + "explorer_urls": ["https://explorer.solana.com?cluster=devnet"], + "is_testnet": true, + "tags": ["devnet", "solana"] + }, + { + "type": "solana", + "network": "solana-custom", + "rpc_urls": ["https://your-custom-solana-rpc.example.com"], + "tags": ["custom", "solana"] + } + ] +} +``` + +For detailed network configuration options, see the [Network Configuration](/relayer/1.1.x/network_configuration) guide. + +## Supported Signers + +* `vault_transit` (hosted) +* `turnkey` (hosted) +* `google_cloud_kms` (hosted) +* `local` (local) +* `vault` (local) + + + +In production systems, hosted signers are recommended for the best security model. + + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/1.1.x/quickstart). +Key prerequisites: + +* Rust 2021, version `1.86` or later +* Redis +* Docker (optional) + +Example configuration for a Solana relayer: +```json +{ + "id": "solana-example", + "name": "Solana Example", + "network": "devnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": [ + { + "url": "https://primary-solana-rpc.example.com", + "weight": 100 + }, + { + "url": "https://backup-solana-rpc.example.com", + "weight": 100 + } + ], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "allowed_tokens": [ + { + "mint": "So111...", + "max_allowed_fee": 100000000 + } + ], + "swap_config": { + "strategy": "jupiter-swap", + "cron_schedule": "0 0 * * * *", + "min_balance_threshold": 1000000, + "jupiter_swap_options": { + "dynamic_compute_unit_limit": true, + "priority_level": "high", + "priority_fee_max_lamports": 1000000000 + } + } + } +} +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +In addition to standard relayer configuration and policies, Solana relayers support additional options: + +* `fee_payment_strategy`: `"user"` or `"relayer"` (who pays transaction fees) +* `allowed_tokens`: List of SPL tokens supported for swaps and fee payments +* `allowed_programs`, `allowed_accounts`, `disallowed_accounts`: Restrict relayer operations to specific programs/accounts +* `swap_config`: Automated token swap settings (see below) + +You can check all options in [User Documentation - Relayers](/relayer/1.1.x#3-relayers). + +### Automated token swap configuration options: + +* `strategy`: The swap engine to use. Supported values: `"jupiter-swap"` (Jupiter Swap API), `"jupiter-ultra"` (Jupiter Ultra API). +* `cron_schedule`: Cron expression defining how often scheduled swaps should run (e.g., `"0 0 * * * *"` for every hour). +* `min_balance_threshold`: Minimum token balance (in lamports) that triggers a swap. If the relayer’s balance drops below this, a swap is attempted. +* `jupiter_swap_options`: Advanced options for Jupiter swaps, such as: + * `dynamic_compute_unit_limit`: If `true`, dynamically adjusts compute units for swap transactions. + * `priority_level`: Priority for the swap transaction. Supported values: `"medium"`, `"high"`, `"veryHigh"`. + * `priority_fee_max_lamports`: Maximum priority fee (in lamports) to pay for a swap transaction. +* Per-token swap limits: + * `min_amount`: Minimum amount of a token to swap in a single operation. + * `max_amount`: Maximum amount of a token to swap in a single operation. + * `retain_min_amount`: Minimum amount of a token to retain in the relayer account after a swap (prevents swapping the entire balance). + +## Automated Token Swaps + +The relayer can perform automated token swaps on Solana when user fee_payment_strategy is used for relayer using: + +* ***jupiter-swap*** – via the Jupiter Swap API +* ***jupiter-ultra*** – via the Jupiter Ultra API + +Swaps can be set to work as: + +* ***Scheduled Swaps***: Background jobs run swaps based on your cron schedule. +* ***On-Demand Swaps***: If a transaction fails due to insufficient funds, the relayer attempts a swap before returning an error. + +## API Reference + +The Solana API conforms to the [Paymaster spec](https://docs.google.com/document/d/1lweO5WH12QJaSAu5RG_wUistyk_nFeT6gy1CdvyCEHg/edit?tab=t.0#heading=h.4yldgprkuvav). + +Common endpoints: +- `POST /api/v1/relayers//rpc` + Methods: + +* `feeEstimate`, +* `prepareTransaction`, +* `transferTransaction`, +* `signTransaction`, +* `signAndSendTransaction`, +* `getSupportedTokens` +* `getSupportedFeatures` + +Example: Estimate fee for a transaction +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "feeEstimate", + "params": { + "transaction": "", + "fee_token": "" + }, + "id": 1 +}' +``` + +See [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) and [SDK examples](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/tree/main/examples/solana) for full details and examples. + +## Security + +* Do not expose the relayer directly to the public internet. +* Deploy behind a secure backend (reverse proxy, firewall). +* Use hosted signers in production systems. + +## Troubleshooting + +* Check environment variables and configuration files for errors +* Review container logs for issues + +## Roadmap + +* See [Project Roadmap](/relayer/1.1.x/roadmap) for upcoming features + +## Support + +For help, join our [Telegram](https://t.me/openzeppelin_tg/2) or open an issue on GitHub. + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/1.1.x/stellar.mdx b/docs/content/relayer/1.1.x/stellar.mdx new file mode 100644 index 00000000..11981988 --- /dev/null +++ b/docs/content/relayer/1.1.x/stellar.mdx @@ -0,0 +1,331 @@ +--- +title: Stellar Integration +--- + +## Overview + +OpenZeppelin Relayer provides comprehensive support for Stellar networks, enabling secure transaction relaying, Soroban smart contract operations, and advanced transaction management. This integration supports both standard Stellar operations and the latest Soroban smart contract functionality. + + +Stellar support is currently under active development. The API interactions and specifics described below may evolve. + + +## Features + +* Full Soroban smart contract support (invocation, deployment, WASM uploads) +* Standard Stellar payment operations +* Support for all Stellar operations via XDR transaction submission +* Fee bump transaction support +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and sequence number management +* Custom RPC endpoints and network policies +* Metrics and observability + +## Supported Networks + +Stellar networks are defined via JSON configuration files, providing flexibility to: + +* Configure standard Stellar clusters: `mainnet`, `testnet` +* Set up custom Stellar-compatible networks with specific RPC endpoints +* Define network passphrases for proper transaction signing + +Example Stellar network configurations: + +```json +{ + "networks": [ + { + "type": "stellar", + "network": "mainnet", + "rpc_urls": ["https://mainnet.sorobanrpc.com"], + "explorer_urls": ["https://stellar.expert/explorer/public"], + "average_blocktime_ms": 5000, + "is_testnet": false, + "passphrase": "Public Global Stellar Network ; September 2015" + }, + { + "type": "stellar", + "network": "testnet", + "rpc_urls": ["https://soroban-testnet.stellar.org"], + "explorer_urls": ["https://stellar.expert/explorer/testnet"], + "average_blocktime_ms": 5000, + "is_testnet": true, + "passphrase": "Test SDF Network ; September 2015" + } + ] +} +``` + +For detailed network configuration options, see the [Network Configuration](/relayer/1.1.x/network_configuration) guide. + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/1.1.x/quickstart). +Key prerequisites: + +* Rust 2021, version `1.86` or later +* Redis +* Docker (optional) + +Example configuration for a Stellar relayer: +```json +{ + "id": "stellar-example", + "name": "Stellar Example", + "network": "testnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "stellar" +} +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +Stellar relayers support standard relayer configuration options. Check all options in [User Documentation - Relayers](/relayer/1.1.x#3-relayers). + +## API Reference + +### Transaction Operations + +The Stellar API supports a variety of transaction operations: + +| Method Name | Required Parameters | Description | +| --- | --- | --- | +| Send Transaction | `network`, `operations` (or `transaction_xdr`) | Submit a transaction to the Stellar network. Supports payment and Soroban operations directly, or any Stellar operation via pre-built XDR transactions. Also supports fee bump transactions for managing transaction fees. | +| Get Transaction Details | `transaction_id` | Retrieve a specific transaction by its ID. | +| List Transactions | (none) | List transactions for the relayer with pagination support. | + +### Supported Operation Types + +| Operation Type | Description | +| --- | --- | +| `payment` | Transfer native XLM or other assets between accounts | +| `invoke_contract` | Call a deployed Soroban smart contract function | +| `create_contract` | Deploy a new Soroban smart contract from WASM hash | +| `upload_wasm` | Upload WASM contract code to the Stellar ledger | + +### Transaction Structure + +***Required fields:*** +- `network`: The Stellar network ("testnet", "mainnet", etc.) +- Either `operations` (array of operations) OR `transaction_xdr` (base64-encoded XDR) - but not both + +***Optional fields:*** +- `source_account`: The Stellar account that will be the source of the transaction (defaults to relayer’s address) +- `memo`: Transaction memo (see Memo Types below) +- `valid_until`: Transaction expiration time (ISO 8601 format) +- `transaction_xdr`: Pre-built transaction XDR (base64 encoded, signed or unsigned) - mutually exclusive with `operations` +- `fee_bump`: Boolean flag to request fee-bump wrapper (only valid with signed `transaction_xdr`) +- `max_fee`: Maximum fee for fee bump transactions in stroops (defaults to 1,000,000 = 0.1 XLM) + +### Transaction Input Methods + +The relayer supports three ways to submit transactions: + +1. ***Operations-based***: Build a transaction by specifying the `operations` array (recommended for most use cases) +2. ***Transaction XDR (unsigned)***: Submit a pre-built unsigned transaction using `transaction_xdr` field (advanced use case) +3. ***Transaction XDR (signed) with fee bump***: Submit a signed transaction using `transaction_xdr` with `fee_bump: true` to wrap it in a fee bump transaction + +Example: Send Transaction +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "payment", + "destination": "GD77B6LYQ5XDCW6CND7CQMA23FSV7MZQGLBAU5OMEOXQM6XFTCMWQQCJ", + "asset": { + "type": "native" + }, + "amount": 1000000 + } + ], + "memo": { + "type": "text", + "value": "Payment for services" + } +}' +``` + +See [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) for full details and examples. + +### Asset Types + +Assets in Stellar operations must be specified with a type field: + +***Native XLM:*** +```json +"type": "native" +``` + +***Credit Asset (4 characters or less):*** +```json +{ + "type": "credit_alphanum4", + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" +} +``` + +***Credit Asset (5-12 characters):*** +```json +{ + "type": "credit_alphanum12", + "code": "LONGASSET", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" +} +``` + +### Memo Types + +Transactions can include optional memos: + +* ***No Memo***: `"type": "none"` +* ***Text Memo***: `"type": "text", "value": "Payment for services"` (max 28 UTF-8 bytes) +* ***ID Memo***: `"type": "id", "value": "12345"` +* ***Hash Memo***: `"type": "hash", "value": "deadbeef..."` (32 bytes hex) +* ***Return Memo***: `"type": "return", "value": "deadbeef..."` (32 bytes hex) + + + +Memos are not supported for Soroban contract operations (invoke_contract, create_contract, upload_wasm). Attempting to include a memo with these operations will result in an error. + + +### Soroban Contract Operations + +#### Invoke Contract + +Call a deployed Soroban smart contract: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw ' + "network": "testnet", + "operations": [ + { + "type": "invoke_contract", + "contract_address": "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA", + "function_name": "transfer", + "args": [ + {"address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY", + "address": "GD77B6LYQ5XDCW6CND7CQMA23FSV7MZQGLBAU5OMEOXQM6XFTCMWQQCJ", + "i128": {"hi": "0", "lo": "1000000"} + ], + "auth": "type": "source_account" + } + ] +}' +``` + +#### Create Contract + +Deploy a new Soroban smart contract: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "create_contract", + "source": { + "from": "address", + "address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY" + }, + "wasm_hash": "d3b2f6f8a1c5e9b4a7d8c2e1f5a9b3c6e8d4f7a2b5c8e1d4f7a0b3c6e9d2f5a8", + "salt": "0000000000000000000000000000000000000000000000000000000000000001" + } + ] +}' +``` + +#### Upload WASM + +Upload contract code to the Stellar ledger: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "upload_wasm", + "wasm": { + "type": "base64", + "base64": "AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAQcPAgVoZWxsbwAACG1lbW9yeTIDCgQAAAAL" + } + } + ] +}' +``` + +### ScVal Argument Format + +When invoking contract functions, arguments must be provided as ScVal values in JSON format: + +| Type | Format | Description | +| --- | --- | --- | +| U64 | `"u64": "1000000"` | Unsigned 64-bit integer | +| I64 | `"i64": "-500"` | Signed 64-bit integer | +| U32 | `"u32": 42` | Unsigned 32-bit integer | +| I32 | `"i32": -42` | Signed 32-bit integer | +| Boolean | `"bool": true` | Boolean value | +| String | `"string": "hello world"` | UTF-8 string | +| Symbol | `"symbol": "transfer"` | Symbol (used for function names) | +| Address | `"address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY"` | Stellar account or contract address | +| Bytes | `"bytes": "deadbeef"` | Hex-encoded byte array | +| Vector | `"vec": [{"u32": 1, "u32": 2, "u32": 3]}` | Array of ScVal values | +| Map | `"map": [{"key": {"symbol": "name", "val": "string": "MyToken"}]}` | Key-value pairs | + +Additional types like U128, I128, U256, and I256 are also supported using multi-part representations. + +### Authorization Modes + +Soroban operations support different authorization modes: + +| Type | Description | +| --- | --- | +| `none` | No authorization required | +| `source_account` | Use the transaction source account (default) | +| `addresses` | Use specific addresses (future feature) | +| `xdr` | Advanced: provide base64-encoded XDR entries. This allows you to provide pre-signed SorobanAuthorizationEntry objects for complex authorization scenarios. See the [official Stellar documentation on authorization](https://developers.stellar.org/docs/learn/smart-contract-internals/authorization) for detailed information about SorobanAuthorizationEntries. | + +## Security + +* Do not expose the relayer directly to the public internet +* Deploy behind a secure backend (reverse proxy, firewall) +* Use hosted signers in production systems +* Ensure proper network passphrases are configured for transaction signing + +## Troubleshooting + +* Check environment variables and configuration files for errors +* Verify network passphrase matches the target network +* Review container logs for Stellar-specific errors +* Ensure Soroban RPC endpoints are properly configured for contract operations + +## Roadmap + +* See [Project Roadmap](/relayer/1.1.x/roadmap) for upcoming features + +## Support + +For help, join our [Telegram](https://t.me/openzeppelin_tg/2) or open an issue on GitHub. + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/1.1.x/structure.mdx b/docs/content/relayer/1.1.x/structure.mdx new file mode 100644 index 00000000..0704bb4a --- /dev/null +++ b/docs/content/relayer/1.1.x/structure.mdx @@ -0,0 +1,106 @@ +--- +title: Project Structure +--- + +This document provides detailed information about each directory in the OpenZeppelin Relayer project. + +## Source Code Organization + +### `src/` Directory +The main source code directory contains the core implementation files organized into several modules: + +* `api/`: Route and controllers logic + * Manages HTTP routing and delegates incoming requests to controllers +* `bootstrap/`: Service initialization + * Bootstraps and initializes application services +* `config/`: Configuration management + * Handles system configuration and environment settings +* `constants/`: Global constants + * Provides static values used across the application +* `domain/`: Business domain logic + * Encapsulates core business rules and domain-specific functionality +* `jobs/`: Asynchronous job processing + * Manages background task queueing and execution +* `logging/`: Logging and file rotation + * Implements logging functionalities and log file management +* `metrics/`: Metrics collection + * Collects and reports application performance and usage metrics +* `models/`: Core data models and types + * Defines data structures and type definitions for the system +* `repositories/`: Configuration storage + * Provides interfaces for storing and retrieving configuration data +* `services/`: Business service logic + * Implements core business functionalities and service operations +* `utils/`: Utility functions + * Offers helper functions and common utilities for the application + +## Documentation + +### `docs/` Directory +Project documentation: + +* User guides +* API documentation +* Configuration examples +* Architecture diagrams + +## Configuration + +### `config/` Directory + +Houses system configuration file and keys: + +* `config.json` configuration file +* keystore files referenced from config.json file + +## Tests + +### `test/` Directory + +Includes comprehensive testing suites to ensure system reliability: + +* End-to-end tests that simulate real-world user scenarios + +## Scripts + +### `scripts/` Directory + +Utility scripts. + +## Examples + +### `examples/` Directory + +Provides practical examples and sample configurations to help users get started: + +* Demonstrates typical service configurations for various environments +* Acts as a quick-start guide for customizing and deploying the relayer +* Serves as a reference for best practices in configuration and deployment + +## Development Tools + +### Pre-commit Hooks +Located in the project root: + +* Code formatting checks +* Linting rules +* Commit message validation + +### Build Configuration +Core build files: + +* `Cargo.toml`: Project dependencies and metadata +* `rustfmt.toml`: Code formatting rules +* `rust-toolchain.toml`: Rust version and components + +## Docker Support + +The project includes Docker configurations for different environments: + +* `Dockerfile.development`: Development container setup +* `Dockerfile.production`: Production-ready container + + + +For detailed information about running the relayers in containers, see the Docker deployment section in the main documentation. + diff --git a/docs/content/relayer/api/callPlugin.mdx b/docs/content/relayer/api/callPlugin.mdx new file mode 100644 index 00000000..d720f088 --- /dev/null +++ b/docs/content/relayer/api/callPlugin.mdx @@ -0,0 +1,28 @@ +--- +title: Execute a plugin and receive the sanitized result +full: true +_openapi: + method: POST + route: /api/v1/plugins/{plugin_id}/call + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Logs and traces are only returned when the plugin is configured with + `emit_logs` / `emit_traces`. + + Plugin-provided errors are normalized into a consistent payload + (`code`, `details`) and a derived + + message so downstream clients receive a stable shape regardless of how + the handler threw. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Logs and traces are only returned when the plugin is configured with `emit_logs` / `emit_traces`. +Plugin-provided errors are normalized into a consistent payload (`code`, `details`) and a derived +message so downstream clients receive a stable shape regardless of how the handler threw. + + diff --git a/docs/content/relayer/api/cancelTransaction.mdx b/docs/content/relayer/api/cancelTransaction.mdx new file mode 100644 index 00000000..342de9b2 --- /dev/null +++ b/docs/content/relayer/api/cancelTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Cancels a specific transaction by its ID. +full: true +_openapi: + method: DELETE + route: /api/v1/relayers/{relayer_id}/transactions/{transaction_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/createNotification.mdx b/docs/content/relayer/api/createNotification.mdx new file mode 100644 index 00000000..d7d916be --- /dev/null +++ b/docs/content/relayer/api/createNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Creates a new notification. +full: true +_openapi: + method: POST + route: /api/v1/notifications + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/createRelayer.mdx b/docs/content/relayer/api/createRelayer.mdx new file mode 100644 index 00000000..6004316f --- /dev/null +++ b/docs/content/relayer/api/createRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Creates a new relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/createSigner.mdx b/docs/content/relayer/api/createSigner.mdx new file mode 100644 index 00000000..5cf9ec01 --- /dev/null +++ b/docs/content/relayer/api/createSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Creates a new signer. +full: true +_openapi: + method: POST + route: /api/v1/signers + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/deleteNotification.mdx b/docs/content/relayer/api/deleteNotification.mdx new file mode 100644 index 00000000..0f81bb7e --- /dev/null +++ b/docs/content/relayer/api/deleteNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes a notification by ID. +full: true +_openapi: + method: DELETE + route: /api/v1/notifications/{notification_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/deletePendingTransactions.mdx b/docs/content/relayer/api/deletePendingTransactions.mdx new file mode 100644 index 00000000..52fccbfd --- /dev/null +++ b/docs/content/relayer/api/deletePendingTransactions.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes all pending transactions for a specific relayer. +full: true +_openapi: + method: DELETE + route: /api/v1/relayers/{relayer_id}/transactions/pending + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/deleteRelayer.mdx b/docs/content/relayer/api/deleteRelayer.mdx new file mode 100644 index 00000000..19dc6cde --- /dev/null +++ b/docs/content/relayer/api/deleteRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes a relayer by ID. +full: true +_openapi: + method: DELETE + route: /api/v1/relayers/{relayer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/deleteSigner.mdx b/docs/content/relayer/api/deleteSigner.mdx new file mode 100644 index 00000000..8ff8d171 --- /dev/null +++ b/docs/content/relayer/api/deleteSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Deletes a signer by ID. +full: true +_openapi: + method: DELETE + route: /api/v1/signers/{signer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getNotification.mdx b/docs/content/relayer/api/getNotification.mdx new file mode 100644 index 00000000..683be6fa --- /dev/null +++ b/docs/content/relayer/api/getNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves details of a specific notification by ID. +full: true +_openapi: + method: GET + route: /api/v1/notifications/{notification_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getRelayer.mdx b/docs/content/relayer/api/getRelayer.mdx new file mode 100644 index 00000000..ab762fed --- /dev/null +++ b/docs/content/relayer/api/getRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves details of a specific relayer by ID. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getRelayerBalance.mdx b/docs/content/relayer/api/getRelayerBalance.mdx new file mode 100644 index 00000000..ac75d13c --- /dev/null +++ b/docs/content/relayer/api/getRelayerBalance.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves the balance of a specific relayer. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/balance + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getRelayerStatus.mdx b/docs/content/relayer/api/getRelayerStatus.mdx new file mode 100644 index 00000000..a2cb963b --- /dev/null +++ b/docs/content/relayer/api/getRelayerStatus.mdx @@ -0,0 +1,15 @@ +--- +title: Fetches the current status of a specific relayer. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/status + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getSigner.mdx b/docs/content/relayer/api/getSigner.mdx new file mode 100644 index 00000000..eadf133a --- /dev/null +++ b/docs/content/relayer/api/getSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves details of a specific signer by ID. +full: true +_openapi: + method: GET + route: /api/v1/signers/{signer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getTransactionById.mdx b/docs/content/relayer/api/getTransactionById.mdx new file mode 100644 index 00000000..77c008b2 --- /dev/null +++ b/docs/content/relayer/api/getTransactionById.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves a specific transaction by its ID. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/transactions/{transaction_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/getTransactionByNonce.mdx b/docs/content/relayer/api/getTransactionByNonce.mdx new file mode 100644 index 00000000..810eb101 --- /dev/null +++ b/docs/content/relayer/api/getTransactionByNonce.mdx @@ -0,0 +1,15 @@ +--- +title: Retrieves a transaction by its nonce value. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/transactions/by-nonce/{nonce} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/health.mdx b/docs/content/relayer/api/health.mdx new file mode 100644 index 00000000..bf6af450 --- /dev/null +++ b/docs/content/relayer/api/health.mdx @@ -0,0 +1,29 @@ +--- +title: Health routes implementation +full: true +_openapi: + method: GET + route: /v1/health + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + Handles the `/health` endpoint. + + + Returns an `HttpResponse` with a status of `200 OK` and a body of + `"OK"`. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file +Handles the `/health` endpoint. + +Returns an `HttpResponse` with a status of `200 OK` and a body of `"OK"`. + + diff --git a/docs/content/relayer/api/index.mdx b/docs/content/relayer/api/index.mdx new file mode 100644 index 00000000..414d82da --- /dev/null +++ b/docs/content/relayer/api/index.mdx @@ -0,0 +1,114 @@ +--- +title: Relayer API Reference +--- + +## Relayers + +### [List Relayers](/relayer/api/listRelayers) +Lists all relayers with pagination support + +### [Create Relayer](/relayer/api/createRelayer) +Creates a new relayer + +### [Get Relayer](/relayer/api/getRelayer) +Retrieves details of a specific relayer by ID + +### [Delete Relayer](/relayer/api/deleteRelayer) +Deletes a relayer by ID + +### [Update Relayer](/relayer/api/updateRelayer) +Updates a relayer's information + +### [Get Relayer Balance](/relayer/api/getRelayerBalance) +Retrieves the balance of a specific relayer + +### [RPC](/relayer/api/rpc) +Performs a JSON-RPC call using the specified relayer + +### [Sign](/relayer/api/sign) +Signs data using the specified relayer + +### [Sign Transaction](/relayer/api/signTransaction) +Signs a transaction using the specified relayer (Stellar only) + +### [Sign Typed Data](/relayer/api/signTypedData) +Signs typed data using the specified relayer + +### [Get Relayer Status](/relayer/api/getRelayerStatus) +Fetches the current status of a specific relayer + +### [Send Transaction](/relayer/api/sendTransaction) +Sends a transaction through the specified relayer + +### [List Transactions](/relayer/api/listTransactions) +Lists all transactions for a specific relayer with pagination + +### [Get Transaction by Nonce](/relayer/api/getTransactionByNonce) +Retrieves a transaction by its nonce value + +### [Delete Pending Transactions](/relayer/api/deletePendingTransactions) +Deletes all pending transactions for a specific relayer + +### [Get Transaction by ID](/relayer/api/getTransactionById) +Retrieves a specific transaction by its ID + +### [Replace Transaction](/relayer/api/replaceTransaction) +Replaces a specific transaction with a new one + +### [Cancel Transaction](/relayer/api/cancelTransaction) +Cancels a specific transaction by its ID + +## Plugins + +### [Call Plugin](/relayer/api/callPlugin) +Calls a plugin method + +## Notifications + +### [List Notifications](/relayer/api/listNotifications) +Lists all notifications with pagination support + +### [Create Notification](/relayer/api/createNotification) +Creates a new notification + +### [Get Notification](/relayer/api/getNotification) +Retrieves details of a specific notification by ID + +### [Delete Notification](/relayer/api/deleteNotification) +Deletes a notification by ID + +### [Update Notification](/relayer/api/updateNotification) +Updates an existing notification + +## Signers + +### [List Signers](/relayer/api/listSigners) +Lists all signers with pagination support + +### [Create Signer](/relayer/api/createSigner) +Creates a new signer + +### [Get Signer](/relayer/api/getSigner) +Retrieves details of a specific signer by ID + +### [Delete Signer](/relayer/api/deleteSigner) +Deletes a signer by ID + +### [Update Signer](/relayer/api/updateSigner) +Updates an existing signer + +## Metrics + +### [Scrape Metrics](/relayer/api/scrape_metrics) +Triggers an update of system metrics and returns the result in plain text format + +### [List Metrics](/relayer/api/list_metrics) +Returns a list of all available metric names in JSON format + +### [Metric Detail](/relayer/api/metric_detail) +Returns the details of a specific metric in plain text format + +## Health + +### [Health](/relayer/api/health) +Health routes implementation diff --git a/docs/content/relayer/api/listNotifications.mdx b/docs/content/relayer/api/listNotifications.mdx new file mode 100644 index 00000000..e736cba8 --- /dev/null +++ b/docs/content/relayer/api/listNotifications.mdx @@ -0,0 +1,25 @@ +--- +title: Notification routes implementation +full: true +_openapi: + method: GET + route: /api/v1/notifications + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + + Lists all notifications with pagination support. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file + +Lists all notifications with pagination support. + + diff --git a/docs/content/relayer/api/listRelayers.mdx b/docs/content/relayer/api/listRelayers.mdx new file mode 100644 index 00000000..073c35b4 --- /dev/null +++ b/docs/content/relayer/api/listRelayers.mdx @@ -0,0 +1,25 @@ +--- +title: Relayer routes implementation +full: true +_openapi: + method: GET + route: /api/v1/relayers + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + + Lists all relayers with pagination support. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file + +Lists all relayers with pagination support. + + diff --git a/docs/content/relayer/api/listSigners.mdx b/docs/content/relayer/api/listSigners.mdx new file mode 100644 index 00000000..78a67216 --- /dev/null +++ b/docs/content/relayer/api/listSigners.mdx @@ -0,0 +1,25 @@ +--- +title: Signer routes implementation +full: true +_openapi: + method: GET + route: /api/v1/signers + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + + Lists all signers with pagination support. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file + +Lists all signers with pagination support. + + diff --git a/docs/content/relayer/api/listTransactions.mdx b/docs/content/relayer/api/listTransactions.mdx new file mode 100644 index 00000000..2a90441d --- /dev/null +++ b/docs/content/relayer/api/listTransactions.mdx @@ -0,0 +1,15 @@ +--- +title: Lists all transactions for a specific relayer with pagination. +full: true +_openapi: + method: GET + route: /api/v1/relayers/{relayer_id}/transactions/ + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/list_metrics.mdx b/docs/content/relayer/api/list_metrics.mdx new file mode 100644 index 00000000..8ea7a892 --- /dev/null +++ b/docs/content/relayer/api/list_metrics.mdx @@ -0,0 +1,33 @@ +--- +title: Metrics routes implementation +full: true +_openapi: + method: GET + route: /metrics + toc: [] + structuredData: + headings: [] + contents: + - content: >- + Note: OpenAPI documentation for these endpoints can be found in the + `openapi.rs` file + + Returns a list of all available metric names in JSON format. + + + # Returns + + + An `HttpResponse` containing a JSON array of metric names. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file +Returns a list of all available metric names in JSON format. + +# Returns + +An `HttpResponse` containing a JSON array of metric names. + + diff --git a/docs/content/relayer/api/metric_detail.mdx b/docs/content/relayer/api/metric_detail.mdx new file mode 100644 index 00000000..e252ce6f --- /dev/null +++ b/docs/content/relayer/api/metric_detail.mdx @@ -0,0 +1,38 @@ +--- +title: Returns the details of a specific metric in plain text format. +full: true +_openapi: + method: GET + route: /metrics/{metric_name} + toc: [] + structuredData: + headings: [] + contents: + - content: >- + # Parameters + + + - `path`: The name of the metric to retrieve details for. + + + # Returns + + + An `HttpResponse` containing the metric details in plain text, or a + 404 error if the metric is + + not found. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +# Parameters + +- `path`: The name of the metric to retrieve details for. + +# Returns + +An `HttpResponse` containing the metric details in plain text, or a 404 error if the metric is +not found. + + diff --git a/docs/content/relayer/api/replaceTransaction.mdx b/docs/content/relayer/api/replaceTransaction.mdx new file mode 100644 index 00000000..6ff72edd --- /dev/null +++ b/docs/content/relayer/api/replaceTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Replaces a specific transaction with a new one. +full: true +_openapi: + method: PUT + route: /api/v1/relayers/{relayer_id}/transactions/{transaction_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/rpc.mdx b/docs/content/relayer/api/rpc.mdx new file mode 100644 index 00000000..1def5de6 --- /dev/null +++ b/docs/content/relayer/api/rpc.mdx @@ -0,0 +1,15 @@ +--- +title: Performs a JSON-RPC call using the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/rpc + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/scrape_metrics.mdx b/docs/content/relayer/api/scrape_metrics.mdx new file mode 100644 index 00000000..0c8509b4 --- /dev/null +++ b/docs/content/relayer/api/scrape_metrics.mdx @@ -0,0 +1,30 @@ +--- +title: >- + Triggers an update of system metrics and returns the result in plain text + format. +full: true +_openapi: + method: GET + route: /debug/metrics/scrape + toc: [] + structuredData: + headings: [] + contents: + - content: >- + # Returns + + + An `HttpResponse` containing the updated metrics in plain text, or an + error message if the + + update fails. +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + +# Returns + +An `HttpResponse` containing the updated metrics in plain text, or an error message if the +update fails. + + diff --git a/docs/content/relayer/api/sendTransaction.mdx b/docs/content/relayer/api/sendTransaction.mdx new file mode 100644 index 00000000..e70a0dba --- /dev/null +++ b/docs/content/relayer/api/sendTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Sends a transaction through the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/transactions + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/sign.mdx b/docs/content/relayer/api/sign.mdx new file mode 100644 index 00000000..28cf96de --- /dev/null +++ b/docs/content/relayer/api/sign.mdx @@ -0,0 +1,15 @@ +--- +title: Signs data using the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/sign + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/signTransaction.mdx b/docs/content/relayer/api/signTransaction.mdx new file mode 100644 index 00000000..a53aa136 --- /dev/null +++ b/docs/content/relayer/api/signTransaction.mdx @@ -0,0 +1,15 @@ +--- +title: Signs a transaction using the specified relayer (Stellar only). +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/sign-transaction + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/signTypedData.mdx b/docs/content/relayer/api/signTypedData.mdx new file mode 100644 index 00000000..63a7d4d2 --- /dev/null +++ b/docs/content/relayer/api/signTypedData.mdx @@ -0,0 +1,15 @@ +--- +title: Signs typed data using the specified relayer. +full: true +_openapi: + method: POST + route: /api/v1/relayers/{relayer_id}/sign-typed-data + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/updateNotification.mdx b/docs/content/relayer/api/updateNotification.mdx new file mode 100644 index 00000000..711b8b8d --- /dev/null +++ b/docs/content/relayer/api/updateNotification.mdx @@ -0,0 +1,15 @@ +--- +title: Updates an existing notification. +full: true +_openapi: + method: PATCH + route: /api/v1/notifications/{notification_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/updateRelayer.mdx b/docs/content/relayer/api/updateRelayer.mdx new file mode 100644 index 00000000..0af41d58 --- /dev/null +++ b/docs/content/relayer/api/updateRelayer.mdx @@ -0,0 +1,15 @@ +--- +title: Updates a relayer's information based on the provided update request. +full: true +_openapi: + method: PATCH + route: /api/v1/relayers/{relayer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/api/updateSigner.mdx b/docs/content/relayer/api/updateSigner.mdx new file mode 100644 index 00000000..000843a6 --- /dev/null +++ b/docs/content/relayer/api/updateSigner.mdx @@ -0,0 +1,15 @@ +--- +title: Updates an existing signer. +full: true +_openapi: + method: PATCH + route: /api/v1/signers/{signer_id} + toc: [] + structuredData: + headings: [] + contents: [] +--- + +{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */} + + diff --git a/docs/content/relayer/changelog.mdx b/docs/content/relayer/changelog.mdx new file mode 100644 index 00000000..38c7b004 --- /dev/null +++ b/docs/content/relayer/changelog.mdx @@ -0,0 +1,505 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v1.1.0) - 2025-08-11 + +## [1.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v1.0.0...v1.1.0) (2025-08-11) + + +### 🚀 Features + +* Add Arbitrum support ([#373](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/373)) ([7b5372b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b5372bf54fe26756ca5db6cb393e0d9d79ae621)) +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* add evm intristic gas_limit validation ([dd1b2d6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dd1b2d6768d09f051791d0db68c912a38d273715)) +* Add get_status method for EVM and Stellar ([#229](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/229)) ([e84217e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e84217e0fa941fcd580ad6b84ab6bfac939dd5f4)) +* Add Launctube plugin example ([#414](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/414)) ([5bda763](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5bda7635f304923fcd4031f855009228eeefee4b)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add node support to environment ([#236](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/236)) ([3ab46f8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ab46f848e7e4c6dee2545d62dc646b33623d63d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* Add plugin invoker service ([#290](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/290)) ([489ce02](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/489ce0285cd88a18b1616af94bfc970a4a674228)) +* Add plugins call endpoint ([#279](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/279)) ([c278589](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c278589f4c6bf88be86788fdd9b68c2f166f5f33)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* Add status_reason field to transaction responses ([#369](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/369)) ([c489e5d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c489e5d39e3cec555caf92ac93266016c547b2bb)) +* Add stellar launchtube plugin ([#401](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/401)) ([801e2f7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/801e2f7efc8f0cb7eb54f545ce398e6ee24cf6b9)) +* Add support for feebumped tx ([#309](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/309)) ([b4efd2e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b4efd2e894fb6534b61a10c5f8872a73d923410c)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add support for stellar InvokeHostFunction transactions ([#284](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/284)) ([32ba63e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/32ba63e58e3dfc1359b7a5c9f61f9ff2a8b6c317)) +* Add support to plugin list endpoint ([#358](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/358)) ([6517af0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6517af0a753db41638b006fa2b896a3ccec0d4ef)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* Add transaction status handling for stellar ([#223](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/223)) ([9496eb6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9496eb63514afb0bd29c731bebe86ffdcf393362)) +* Add wait API for plugin transactions ([#345](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/345)) ([6069af2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6069af256e6cfe8470244731d4bb444b87bd175f)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Added resolve_plugin_path for script_path ([#340](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/340)) ([0b30739](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0b30739e51f5ef6c0b97c1da585d403496b2bbac)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* Create initial js plugins library ([#302](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/302)) ([98238e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/98238e9a6a30de8dba3bf8d308a82658e29de46f)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* Enhance Stellar tx handling with fee updates ([#368](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/368)) ([05617d7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/05617d7cb06ab378c2c2207f9d0a2e11a04cc472)) +* **evm:** Add AWS KMS signer support ([#287](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/287)) ([723a9a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/723a9a8d7e625dd3f52b2d678d0e1cd842053e06)) +* **evm:** Implement delete pending txs ([#289](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/289)) ([bc6f829](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc6f829e580d42359adebceeddaf38002390e10b)) +* **evm:** Implement json rpc endpoint ([#286](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/286)) ([91528aa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/91528aab82e3fa3cba08f63feb4ac9879aa8940e)) +* extract networks to json files ([#238](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/238)) ([5ac07b3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ac07b3c570485d7cdbc419a23f373867d7ebe81)) +* handle non-retriable errors and provider health errors ([#233](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/233)) ([7add348](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7add348da4d06af5ebebcce78d856485e9894ac3)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Implement get_balance method for StellarRelayer ([#228](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/228)) ([d92c75f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d92c75fe7da1b02ddb7a38df32f98082474e4cd9)) +* implement network config deserializing ([#235](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/235)) ([6d537f9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6d537f9298626fefc0d5a45c311a95208e1c8ef5)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* Pass arbitrary payloads to script exectution ([#312](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/312)) ([adecaf5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/adecaf5d73c3df9083c6a3fcf62ed669bc90b25c)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* plat-6215 add support for rpc failovers and retries ([#231](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/231)) ([ca6d24f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ca6d24f1bcdbb912795dcb1496519b49b5e81bf1)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6303 store solana submitted transactions to db and run status check logic ([#398](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/398)) ([e8420bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e8420bca02c20a53b02d9bedc8da1b7a784716dc)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* plat-6522 allow for the use of on chain defi to automatically swap spl ([#198](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/198)) ([dc9e2e2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dc9e2e2dd1d46830bc6479c1928a2e7ef7f91fb3)) +* plat-6571 add support for gcp signer ([#221](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/221)) ([0170fa1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0170fa12c3ecc64d1c48ed3a726358ed74d4596b)) +* Plat-6677 implement redis repositories for existing collections ([#350](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/350)) ([5fee731](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5fee731c5f19013f41a12a5b93af79d65bdf777e)) +* Plat-6679 implement startup logic to populate redis from config file ([#359](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/359)) ([5e1c0c8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5e1c0c825d3c1185a5c59360a2c857d79b46abba)) +* Plat-6681 expose crud api endpoints ([#365](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/365)) ([f3c3426](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f3c34266f3f035cd240105833ef4e67711cb0356)) +* Plat-6684 add support for transaction entries expiration ([#394](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/394)) ([6f6f765](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6f6f765556b2fc16764f8afe02ceedf268c26c13)) +* plat-6817 EVM add support for gas limit calculation ([#355](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/355)) ([dd1b2d6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dd1b2d6768d09f051791d0db68c912a38d273715)) +* plat-6873 add storage documentation ([#395](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/395)) ([ffd4ed5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ffd4ed58d322bad63be500a084a0b082ac7b59d9)) +* Plugins improvements ([#410](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/410)) ([648a0f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/648a0f121a6308e8bde0e09010d2e0c83de5c6ec)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Relayer plugins - add support to plugins in configs ([#253](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/253)) ([6a14239](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6a14239486900b2ef121b5de9e87410c412b65fe)) +* **replace_tx:** Implement replace tx for evm ([#272](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/272)) ([b48e71f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b48e71f55fda03bea83e90255b0d180db704cb52)) +* Set default network folder ([#313](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/313)) ([b28c99c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b28c99c43bedd921a55660622d845e63890e0d74)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* **signer:** Add GCP Signer to EVM ([#305](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/305)) ([a8817b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a8817b6c87c65731232d0a141338f3996aef2510)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Stellar RPC config ([#213](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/213)) ([6fd75ea](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd75ea65bf1a945ba891f99d83b0cdacdf30014)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) +* support for retries and failovers in EVM Provider ([#197](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/197)) ([542f21a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/542f21a9346def9b7fe47e0a29a2bbd5ab2af349)) +* Support plugin timeouts ([#348](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/348)) ([0a1c51e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0a1c51e9fe540ba570af25146538992a26b9a8a0)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Types introduced for plugin params and result ([#351](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/351)) ([dda83a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dda83a296fd5bd5bfca7f7902f4ca035e1bd8796)) +* Update Stellar network config and docs ([#380](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/380)) ([a4e1a0f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a4e1a0f38590f21c6d5e917a02fee4f6bef4f075)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Add memo validation for InvokeHostFunction ([#294](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/294)) ([6bb4ffa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6bb4ffaf9ceb4a8daef29ec5878595cca7041300)) +* change the ampersand to and, as as the shell interpret it ([#206](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/206)) ([d164d6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d164d6a4d63fbf0acdfe1330cf25147e86280af8)) +* Changing base image to wolfi, added node and npm ([#266](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/266)) ([1181996](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1181996dac6da52f96e164b1c937828a3940d5b8)) +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Config example file ([#285](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/285)) ([a020c6f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a020c6fcd6f9b638d955d5f2c99aa0e199d8bf6e)) +* Correct env var value in semgrep.yml ([#375](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/375)) ([2e98e21](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e98e2149135b97a62b90c302675379642fdf7b3)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* Docker readme file ([#339](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/339)) ([2db9933](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2db9933def061046cc3585a07249107a236ef98c)) +* docker-scan - chainguard issue ([#255](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/255)) ([c9ab94b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c9ab94bcee7b386a33b063504b3e6d2cf188d8b5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Implement stellar sequence sync and tx reset ([#367](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/367)) ([60b5deb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/60b5deb4915041d60a064cfac1a066406c339517)) +* Inheritance validation ([#374](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/374)) ([f8b921b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f8b921b4d6d85b8068428f1e34de121183a02179)) +* Make plugins entry in configs optional ([#300](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/300)) ([f299779](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f299779318429677fd672d4a2433828971a1b62e)) +* Minor fixes in Plugin docs ([#325](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/325)) ([33bb6a1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/33bb6a1841f2e84723e49cc81258a930241dc735)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) +* Plat-6815 resubmission bug ([#353](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/353)) ([72ac174](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/72ac17471e3a0a6ac35e9a9bb9ff8fe5e8b94bf2)) +* plat-6888 aws kms signer issue ([#411](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/411)) ([3c12c88](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3c12c88703c92526fe975eabba6ba0ffa9ca9c79)) +* Plugin result + adds tests for plugin ts lib ([#336](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/336)) ([b30246e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b30246e8922d3cb5bd3c5b92a7678f7591db5b97)) +* Relayer plugins format output ([#307](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/307)) ([8f25e5f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f25e5f55812e3d346c8bc0ff063cf07e2f0b753)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* remove the ci job dependant from the test job ([#222](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/222)) ([4056610](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40566108b66c701323145c2889ce0141b84714b8)) +* Replace automatic minor version bumps ([#315](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/315)) ([85784b4](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/85784b486a9508429ae94373a7f3db13d78b39d6)) +* Replace tx request body ([#326](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/326)) ([a20c916](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a20c916b592891b7a2afafd2e62b32723fc05dc2)) +* SBOM upload error ([#342](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/342)) ([1f9318e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1f9318e22cbe59ca03bc617b0986379574e5f770)) +* Semgrep CI integration ([#371](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/371)) ([6b9a6d2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6b9a6d24e22b78743f16c566026b34f9912669ad)) +* Semgrep send metrics value ([#381](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/381)) ([315ccbc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/315ccbca9a48816fc6e0c8133301aa3e3186ff93)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) +* Solve issues with new solana_sdk version ([#324](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/324)) ([ab97253](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab972533259506bb21e22ec7f899a45d2fc97db5)) +* Switch Redocly build to use standalone html file ([#291](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/291)) ([97a8698](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/97a86980bec6260920a469018fee0d3541d1a063)) +* syntax error in codeql.yml ([#385](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/385)) ([987fd33](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/987fd33566b66b2821490d0769a3c863a778c271)) +* Update configs and dockerfiles in examples ([#298](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/298)) ([2e505ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e505ad827ab7544f7c6a3fdf4018b1e9428f1d6)) +* Update semgrep.yml ([#347](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/347)) ([5ffb803](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ffb8036ca6d3fb5a8cdb34fa5484e7732c842a1)) +* Update Stellar API docs to match implementation ([#292](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/292)) ([96d95e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/96d95e35784c25f39afe626b56f11477fd213196)) +* Use unicode character for emoji ([#343](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/343)) ([784e89f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/784e89fae4ad2ddad037ddbbd0bec6df160e9a6a)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v1.0.0) - 2025-06-30 + +## [1.0.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.2.0...v1.0.0) (2025-06-30) + + +### 🚀 Features + +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* Add get_status method for EVM and Stellar ([#229](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/229)) ([e84217e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e84217e0fa941fcd580ad6b84ab6bfac939dd5f4)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add node support to environment ([#236](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/236)) ([3ab46f8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ab46f848e7e4c6dee2545d62dc646b33623d63d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* Add plugin invoker service ([#290](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/290)) ([489ce02](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/489ce0285cd88a18b1616af94bfc970a4a674228)) +* Add plugins call endpoint ([#279](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/279)) ([c278589](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c278589f4c6bf88be86788fdd9b68c2f166f5f33)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* Add support for feebumped tx ([#309](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/309)) ([b4efd2e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b4efd2e894fb6534b61a10c5f8872a73d923410c)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add support for stellar InvokeHostFunction transactions ([#284](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/284)) ([32ba63e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/32ba63e58e3dfc1359b7a5c9f61f9ff2a8b6c317)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* Add transaction status handling for stellar ([#223](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/223)) ([9496eb6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9496eb63514afb0bd29c731bebe86ffdcf393362)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* Create initial js plugins library ([#302](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/302)) ([98238e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/98238e9a6a30de8dba3bf8d308a82658e29de46f)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* **evm:** Add AWS KMS signer support ([#287](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/287)) ([723a9a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/723a9a8d7e625dd3f52b2d678d0e1cd842053e06)) +* **evm:** Implement delete pending txs ([#289](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/289)) ([bc6f829](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc6f829e580d42359adebceeddaf38002390e10b)) +* **evm:** Implement json rpc endpoint ([#286](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/286)) ([91528aa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/91528aab82e3fa3cba08f63feb4ac9879aa8940e)) +* extract networks to json files ([#238](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/238)) ([5ac07b3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/5ac07b3c570485d7cdbc419a23f373867d7ebe81)) +* handle non-retriable errors and provider health errors ([#233](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/233)) ([7add348](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7add348da4d06af5ebebcce78d856485e9894ac3)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Implement get_balance method for StellarRelayer ([#228](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/228)) ([d92c75f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d92c75fe7da1b02ddb7a38df32f98082474e4cd9)) +* implement network config deserializing ([#235](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/235)) ([6d537f9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6d537f9298626fefc0d5a45c311a95208e1c8ef5)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* Pass arbitrary payloads to script exectution ([#312](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/312)) ([adecaf5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/adecaf5d73c3df9083c6a3fcf62ed669bc90b25c)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* plat-6215 add support for rpc failovers and retries ([#231](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/231)) ([ca6d24f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ca6d24f1bcdbb912795dcb1496519b49b5e81bf1)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* plat-6522 allow for the use of on chain defi to automatically swap spl ([#198](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/198)) ([dc9e2e2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/dc9e2e2dd1d46830bc6479c1928a2e7ef7f91fb3)) +* plat-6571 add support for gcp signer ([#221](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/221)) ([0170fa1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0170fa12c3ecc64d1c48ed3a726358ed74d4596b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Relayer plugins - add support to plugins in configs ([#253](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/253)) ([6a14239](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6a14239486900b2ef121b5de9e87410c412b65fe)) +* **replace_tx:** Implement replace tx for evm ([#272](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/272)) ([b48e71f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b48e71f55fda03bea83e90255b0d180db704cb52)) +* Set default network folder ([#313](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/313)) ([b28c99c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b28c99c43bedd921a55660622d845e63890e0d74)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* **signer:** Add GCP Signer to EVM ([#305](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/305)) ([a8817b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a8817b6c87c65731232d0a141338f3996aef2510)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Stellar RPC config ([#213](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/213)) ([6fd75ea](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd75ea65bf1a945ba891f99d83b0cdacdf30014)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) +* support for retries and failovers in EVM Provider ([#197](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/197)) ([542f21a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/542f21a9346def9b7fe47e0a29a2bbd5ab2af349)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Add memo validation for InvokeHostFunction ([#294](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/294)) ([6bb4ffa](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6bb4ffaf9ceb4a8daef29ec5878595cca7041300)) +* change the ampersand to and, as as the shell interpret it ([#206](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/206)) ([d164d6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d164d6a4d63fbf0acdfe1330cf25147e86280af8)) +* Changing base image to wolfi, added node and npm ([#266](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/266)) ([1181996](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1181996dac6da52f96e164b1c937828a3940d5b8)) +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Config example file ([#285](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/285)) ([a020c6f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a020c6fcd6f9b638d955d5f2c99aa0e199d8bf6e)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* docker-scan - chainguard issue ([#255](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/255)) ([c9ab94b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c9ab94bcee7b386a33b063504b3e6d2cf188d8b5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Make plugins entry in configs optional ([#300](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/300)) ([f299779](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f299779318429677fd672d4a2433828971a1b62e)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) +* Relayer plugins format output ([#307](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/307)) ([8f25e5f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f25e5f55812e3d346c8bc0ff063cf07e2f0b753)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* remove the ci job dependant from the test job ([#222](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/222)) ([4056610](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40566108b66c701323145c2889ce0141b84714b8)) +* Replace automatic minor version bumps ([#315](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/315)) ([85784b4](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/85784b486a9508429ae94373a7f3db13d78b39d6)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) +* Update configs and dockerfiles in examples ([#298](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/298)) ([2e505ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2e505ad827ab7544f7c6a3fdf4018b1e9428f1d6)) +* Update Stellar API docs to match implementation ([#292](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/292)) ([96d95e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/96d95e35784c25f39afe626b56f11477fd213196)) + +[Changes][v1.0.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.2.0) - 2025-05-14 + +## [0.2.0](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.1...v0.2.0) (2025-05-14) + + +### 🚀 Features + +* add optimism extra cost calculation ([#146](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/146)) ([b85e070](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b85e070074ecc0aa4fbd7d5dc3af6ca0d600220b)) +* add timeout_seconds to EVM relayer configuration ([#169](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/169)) ([6fd59bc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6fd59bc0e5993d63608d47e7ba7825a027e26b99)) +* implement balance validation in EVM ([#168](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/168)) ([27fe333](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/27fe333806c28c268af981f5377e188160c845b9)) +* Local signing for stellar ([#178](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/178)) ([f69270a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f69270ade4c9a9239bba874ac74858c8e7375298)) +* plat-6471 add Solana Token 2022 extensions support ([#166](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/166)) ([d35c506](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d35c506ea298a86897ede5702481403f839f2451)) +* Plat-6521 add turnkey hosted signer support (evm, solana) ([#174](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/174)) ([b24688e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b24688ead4fe3015ca3b7c74e56f1906085a5aa3)) +* Stellar RPC service ([#183](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/183)) ([9943ffd](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9943ffd67a709df487264f50eccd03b06cc817d4)) +* Stellar transaction submission ([#199](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/199)) ([c6b72bf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c6b72bfba82c7fb9288c07e49bef04cf527d1245)) +* support for multiple custom RPCs with weighted configuration ([#182](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/182)) ([92ea5ad](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/92ea5ad324323b957fcbdce85c37517ec6f963ba)) + + +### 🐛 Bug Fixes + +* CLA assistant ([#171](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/171)) ([b326a56](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b326a5680722e812263aab949003c214795fd2c0)) +* CLA labels ([#173](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/173)) ([e31405b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e31405b8cba9ffd2ff991d56444320ff3d069ad0)) +* Docker Compose ([#156](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/156)) ([6ca012f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6ca012fb9b50d5c2159c498679673cb27530fc3c)) +* Missing libssl and workflow ([#155](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/155)) ([9de7133](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9de7133c2ba1768f4d989158f19c27444e522f9e)) +* Release merge conflicts ([#163](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/163)) ([4cac422](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4cac4221817373a1ae7eff92db187dbae2f1665b)) +* Skip releases ([ccafcbe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ccafcbe11bc6ea46dacb9c59be578abd45112ad3)) + +[Changes][v0.2.0] + + + +# [v0.1.1](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.1.1) - 2025-04-08 + +## [0.1.1](https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.0...v0.1.1) (2025-04-08) + + +### 🐛 Bug Fixes + +* Skip releases ([e79b2e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e79b2e963439721dd8e151fa0827654e4019df5f)) +* Skip releases with release please ([#158](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/158)) ([e79b2e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e79b2e963439721dd8e151fa0827654e4019df5f)) + + +### ⚙️ Miscellaneous Chores + +* Fix workflow and missing libs in docker file ([#157](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/157)) ([c7a681d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c7a681dea154b06b675a286e936606e2f9ce087b)) +* plat-7575 Docs fixes ([#153](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/153)) ([#154](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/154)) ([44257e8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44257e8ea3e658adbf40f69ad809e4e3503e9af4)) + +[Changes][v0.1.1] + + + +# [v0.1.0](https://github.com/OpenZeppelin/openzeppelin-relayer/releases/tag/v0.1.0) - 2025-04-07 + +## 0.1.0 (2025-04-07) + + +### 🚀 Features + +* add base models ([#5](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/5)) ([55db42b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/55db42b16d88e95ca8f6927e3b4d07c939e677c8)) +* Add CLA assistant bot ([#130](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/130)) ([4ad5733](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4ad5733daadefe5e52bd617eaa47039677443745)) +* add directory structure and example ([d946c10](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d946c10fd96ee2d1ce2e373ba4ccfced31f985f9)) +* Add logging improvements ([#28](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/28)) ([bb6751a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bb6751a4f868eb82787e7763a7995d3974ecfd49)) +* Add logic to resubmit transaction ([#102](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/102)) ([6c258b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6c258b625dc7edb1d028b771647ff25b12c2b07d)) +* Add noop support and refactor status ([#134](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/134)) ([f0e3a17](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f0e3a177a536c53fe8eff834243d417bb673b744)) +* Add queue processing support ([#6](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/6)) ([3ebbac2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3ebbac25f1ecb403dec7d090d39882a85227d883)) +* Add release workflow ([#148](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/148)) ([bd9a7e9](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bd9a7e91a300e6650b08f799aecea4478bb4b974)) +* Add sign tx for evm local signer ([#65](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/65)) ([b17fb36](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b17fb3625677f1dbcf1ddf3963db13b9b88ca25e)) +* add support for relayer paused and system disabled state ([#13](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/13)) ([44968a2](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/44968a29ec4f1cf1166c2ad726f2c9a1bac246c3)) +* Add worldchain testnet support ([#137](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/137)) ([25751ef](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/25751ef97b7b9fbe0c4b53fab5b762d1696f8c93)) +* Adding job tests ([#110](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/110)) ([4d2dd98](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d2dd98efedacaded8d4ace118c43dbe25907278)) +* enabling it to listen on all interfaces - allows for easy docker config ([74a59da](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/74a59da79b314160baf35ec9750e372fbad0f360)) +* enabling it to listen on all interfaces - allows for easy docker config ([23f94c0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/23f94c07ce46254f7b80df77ce8c4fc59fb4eef6)) +* improve examples ([#119](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/119)) ([7e59aa6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7e59aa64f75f3470807396b293e71cd68d3292d1)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* Improve Redis startup logic ([#120](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/120)) ([8618ecf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8618ecf00b4739891fe4ce98caf14f729face896)) +* initial repo setup ([d8815b6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/d8815b6752931003536aa427370ca8fb1c57231c)) +* Integrate Netlify with antora ([#74](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/74)) ([09e3d48](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/09e3d4894b54c58754b373da239e9d564df69aa9)) +* Plat 5744 implement an api key authentication mechanism ([#11](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/11)) ([8891887](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/88918872d51ab10632ec6d590689d52e59dfd640)) +* Plat 5768 setup metrics endpoint ([#50](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/50)) ([7c292a5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7c292a572a7aef8213969fc72cadca74f9016fe8)) +* Plat 6434 improve authorization header validation ([#122](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/122)) ([eed7c31](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/eed7c31e938c7b6ecaa82774ca5d3a508bb89281)) +* Plat-5749 implement basic webhook notifications service ([#12](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/12)) ([1b47b64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b47b64c318208eb7dc2ec6d62020fab30ccafbb)) +* Plat-5802 openapi sdk client ([#109](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/109)) ([1b4b681](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1b4b681a3755f60e2934548a9666c60a4465dabb)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* PLAT-6026 Imp cancel transaction ([#101](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/101)) ([1e5cc47](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1e5cc47bdc54acafeeefb60489db410b42722b0f)) +* Plat-6118 implement logic for syncing relayer state upon service start ([#19](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/19)) ([2ba3629](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ba36292a0b8d0d67ddab42d2845a6a0d5f31e3a)) +* Plat-6153 add network definitions for Solana networks ([#26](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/26)) ([ff453d5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ff453d59724eeaa194ccf7f83993ce8d649f7432)) +* Plat-6154 add support for solana local signer ([#29](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/29)) ([40caead](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/40caeadde5f08200410912b98943346971084163)) +* plat-6158 implement Solana rpc service ([#36](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/36)) ([8fb50a8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8fb50a833e7f9b1773dbe4ca1d77a9609a5d5ec1)) +* Plat-6159 extend relayer config file solana policies ([#38](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/38)) ([4f4602b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4602b754e71539937447c1743a7f069317598b)) +* Plat-6164 implement feeestimate rpc method ([#61](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/61)) ([43b016c](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/43b016c4e5faa5ee1fedcdadccf3bc768962178e)) +* Plat-6165 implement transfertransaction rpc method ([#63](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/63)) ([c59a3b8](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c59a3b8894c32470adf10770f4804e272aa829d3)) +* Plat-6167 implement signtransaction rpc method ([#57](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/57)) ([ad7a1ff](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ad7a1ffe41eb868f54737c1f1b44a52c6d02d172)) +* Plat-6169 implement getsupportedtokens rpc method ([#45](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/45)) ([3f91199](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3f9119981acd7f92618ba6ec12c3039563368202)) +* Plat-6170 add vault hosted signer support ([#99](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/99)) ([7a9491d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7a9491d4094fc21bc87551c68687b4f44f3edd18)) +* Plat-6207 implement trait abstraction relayer ([#43](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/43)) ([abeb7cf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/abeb7cfccc9e70b26ddd0d41d736352d57d6ade9)) +* Plat-6216 adding network symbol support ([#37](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/37)) ([21f798f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/21f798fc114de47ae0ed7e127e496bb50ca081a8)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6236 adding validation payload ([#42](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/42)) ([a5ff165](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a5ff165df14f48d47adee03e8e2c8ef5a899ff57)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6239 whitelist policy validation ([#44](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/44)) ([3adb45e](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3adb45e17b8b23c70e09e422cfca051ebab266f1)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6248 implementation dummy of legacy price ([#49](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/49)) ([6319d64](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6319d64bf27fd75f5192165df885156ca91ea9f0)) +* Plat-6267 add utility script for generating local keystore files ([#69](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/69)) ([b5df7f6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/b5df7f6b0450118c9123de46689fa115efcdec94)) +* Plat-6291 add webhook notifications for rpc methods ([#72](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/72)) ([2f35d81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2f35d81b3711cf2f87dbc6df31b9e0f90432164e)) +* Plat-6299 clean transaction response ([#76](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/76)) ([fc5dd05](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc5dd05154bca4a1d740cef058bb797cd3f513a0)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6300 returning the balance of the relayer ([#78](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/78)) ([e0ce8e0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/e0ce8e04f3950c094c9af3e3413d61cd7162c8e7)) +* Plat-6304 use Authorization header instead of x api key ([#94](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/94)) ([34e8a81](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/34e8a813234ee6aaf2a6956f6dd45f82e47e7861)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* Plat-6309 Fetching eip1559 prices ([#83](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/83)) ([68d574f](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/68d574fcb159ae3b6502167a9bcf34bb1a56ea7e)) +* plat-6340 store private keys securely in memory ([#104](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/104)) ([28c2fab](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/28c2fab84f3db6b9d971126cf917263da395c421)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6350 - Sign EIP-1559 ([#98](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/98)) ([673e420](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/673e4202f9d98bfd02512090fa3daacfa40831fe)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6374 EIP-1559 default if network support it and not explicit false ([#100](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/100)) ([c982dde](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c982ddefeba93381ac7d2c5e09f616a60820b8b8)) +* PLAT-6416 Use generics transaction factory ([#105](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/105)) ([7b94662](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/7b946625af77c6aabd336d34646e9ae62ece3b6a)) +* plat-6433 add minimum length validations for config sensitive values ([#125](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/125)) ([31453c5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/31453c5586ca4fef70e7ea0e2dcd0260a8a721a6)) +* Plat-6441 document upcoming work ([#131](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/131)) ([377a8bb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/377a8bb57ff5b3b23abb58d1c3378489c40218cf)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* PLAT-6442 - Abstraction and unit tests relayer domain ([#117](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/117)) ([643194a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/643194acd9079ac3ac157e909f0b30199af8b0c9)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6457 Ignore utoipa ([#127](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/127)) ([234854a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/234854afbf30a9a94fa3365f60f035e53e068938)) +* Plat-6459 create mermaid architecture diagram ([#126](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/126)) ([3de147b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3de147b907c28d3e9a8a38a2d6b8cd665253c423)) +* plat-6476 Add support to collect transaction fee ([#135](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/135)) ([4f4a07b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f4a07b2846d2980bbf09734602315702ded9dbe)) +* Plat-6479 added support for rpc custom endpoints ([#138](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/138)) ([3df3d49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/3df3d49ec6a662698a90630811d717920b7cdf3b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Pricing validation on receiving payload EVM ([#59](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/59)) ([1206d42](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1206d4241dbda84bc861f501d322f6bd33234f0b)) +* Signer service ([#8](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/8)) ([4f85b7b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4f85b7bf5b6aa83903ed8febdfe244d54e803642)) +* Speed support transaction ([#62](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/62)) ([a572af6](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/a572af65ca4f664dce13e705eac37b56dee306fa)) +* Tx submissions and status mgmt ([#81](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/81)) ([9f829f1](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9f829f1c59c4221c9cf38c6cb1ff36351a348cd1)) +* Update transaction status to mined/expired ([#85](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/85)) ([8f5ee53](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8f5ee53bbe64d55ccf8015a1c8d203cf5e391f08)) + + +### 🐛 Bug Fixes + +* Codecov changes and adjustments ([#113](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/113)) ([6e62dcf](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/6e62dcf212a917421c7559566136c018e17c38f5)) +* Docs link ([#128](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/128)) ([8263828](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/82638284cf13a4da376624362f5353b57365302a)) +* Docs path for crate ([#129](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/129)) ([51cf556](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/51cf556411c9c1f79dbee7f4c3aa25df7fe2af49)) +* **docs:** replaced Monitor for Relayer ([2ff196b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/2ff196bf772668556210a895d4f83315e579577f)) +* Documentation name for antora ([#121](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/121)) ([63c36f5](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/63c36f5393b1369a169c8617b20952bca30aef0c)) +* Environment variables ([#124](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/124)) ([8d31131](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/8d31131c087a6d0a64ae2dadecb5ae395ad1b575)) +* Fix the codecov yaml syntax ([#108](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/108)) ([ab9ab5b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/ab9ab5b0c9313d083cd47c71d7faade867c58deb)) +* Flaky logging tests ([#89](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/89)) ([bc909cc](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/bc909cc336613bb5a191c562632278bd3c270b09)) +* Plat 6286 write tests for metrics and middleware functions ([#70](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/70)) ([18124fb](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/18124fbbfbc26f300648a7a4050ebf9be72465ac)) +* PLAT-6426 Increase test coverage ([#118](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/118)) ([1fa41f0](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/1fa41f0f225c9d515690738e960073396dce66ce)) +* PLAT-6478 create unit test for use of on relayers dotenv ([#139](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/139)) ([509e166](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/509e1664518823ef3844e52e818707f3371ddbff)) +* plat-6480 allow transfering wrapped sol tokens ([#132](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/132)) ([f04e66a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/f04e66a568c877c2a4c5c5378fb6017c2e41d2c6)) + + +### 📚 Documentation + +* add cargo docs ([#75](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/75)) ([c4dd8e3](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c4dd8e30525ccaeb563560bc2ef87cdcec5b1790)) +* Add testing instructions ([#107](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/107)) ([c7c2ed7](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/c7c2ed7772d99b4b68ced9fbf8835fa9e46da5e1)) +* Adding configuration documentation ([#48](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/48)) ([929cc1b](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/929cc1bf1e0c6b3be872daf6654abe24eb79b907)) +* Fixing formatting and location of files ([#41](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/41)) ([4d4f153](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/4d4f1530f466a5bd597d0338559ccb33815286f0)) +* Move README to a similar format to Monitor ([#39](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/39)) ([5985339](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/59853396b3786a972ce7bbc793d4dbacc62fe6c0)) +* Readability improvements ([#133](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/133)) ([9220727](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/9220727cc2b4349052c2d96a48c5d9c3012b38b9)) +* Small doc updates - policy field descriptions ([#51](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/51)) ([cc83c49](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/cc83c496bbe2593018b03c414a864691c967ff41)) +* Small fixes and Antora docs improvements ([#40](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/40)) ([655d16d](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/655d16dc658a74b7413ce785dee5b8e33cfb40f7)) +* TG link and other minor doc updates ([#116](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/116)) ([fc68b6a](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/fc68b6afa844d2c2638d031fce44fcc514d59a7d)) +* Update API docs. Fix Dockerfiles ([#77](https://github.com/OpenZeppelin/openzeppelin-relayer/issues/77)) ([0bd6bfe](https://github.com/OpenZeppelin/openzeppelin-relayer/commit/0bd6bfea69d60c1a7e9d6b8a690ba1a2d0e44b74)) + +[Changes][v0.1.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.2.0...v1.0.0 +[v0.2.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.1...v0.2.0 +[v0.1.1]: https://github.com/OpenZeppelin/openzeppelin-relayer/compare/v0.1.0...v0.1.1 +[v0.1.0]: https://github.com/OpenZeppelin/openzeppelin-relayer/tree/v0.1.0 diff --git a/docs/content/relayer/configuration/index.mdx b/docs/content/relayer/configuration/index.mdx new file mode 100644 index 00000000..9d06c4ef --- /dev/null +++ b/docs/content/relayer/configuration/index.mdx @@ -0,0 +1,541 @@ +--- +title: Configuration +--- + +## Overview + +Most configuration files should live under `./config`, including the signer configurations, under `./config/keys`. +Please ensure appropriate access permissions on all configuration files (for `./config/keys/*`, we recommend `0500`. + + + + +The OpenZeppelin Relayer supports two configuration approaches: + +***File-based Configuration:*** +1. ***`config.json`***: Contains relayer definitions, signer configurations, and network policies +2. ***`.env`*** file: Contains environment variables like API keys and connection strings + +***API-based Configuration:*** +- Full CRUD operations for relayers, signers, and notifications via REST API +- Changes take effect immediately (no container restart required) +- See the ***API Reference*** page for detailed endpoints documentation + +See [Storage Configuration](./configuration/storage) for detailed information about how file-based and API-based configurations work together, storage behavior, and best practices. + +For quick setup examples with pre-configured files, see the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository. + + + +## Environment configuration (.env) + +This defines some base configurations for the Relayer application: + +Copy the example environment file and update values according to your needs + +```bash +cp .env.example .env +``` + +This table lists the environment variables and their default values. + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `RUST_LOG` | `info` | `info, debug, warn, error, trace` | Log level. | +| `REPOSITORY_STORAGE_TYPE` | `in-memory` | `in-memory, redis` | Type of storage used for storing repository config and resources. See [Storage Configuration](./configuration/storage) for detailed information. | +| `RESET_STORAGE_ON_START` | `false` | `bool` | Clears all resources from storage on startup and reloads entries from the config file. See [Storage Configuration](./configuration/storage) for usage details. | +| `TRANSACTION_EXPIRATION_HOURS` | `4` | `number` | Number of hours after which transactions in a final state are removed from storage. See [Storage Configuration](./configuration/storage) for more information. | +| `CONFIG_DIR` | `./config` | `` | Relative path of directory where config files reside | +| `CONFIG_FILE_NAME` | `config.json` | `` | File Name of the configuration file. | +| `RATE_LIMIT_REQUESTS_PER_SECOND` | `100` | `` | Rate limit for the API in requests per second. | +| `RATE_LIMIT_BURST_SIZE` | `300` | `` | Rate limit burst size. | +| `API_KEY` | `` | `string`, | API key to use for authentication to the relayer server. Minimum length 32 characters. | +| `WEBHOOK_SIGNING_KEY` | `` | `string` | Signing key to use for webhook notifications. Minimum length 32 characters. | +| `LOG_MODE` | `stdout` | `stdout, file` | Write logs either to console or to file. | +| `LOG_DATA_DIR` | `./logs` | `` | Directory to persist log files on host. | +| `LOG_MAX_SIZE (in bytes)` | `1073741824` | `` | Size after which logs needs to be rolled. | +| `METRICS_ENABLED` | `false` | `bool` | Enable metrics server for external tools to scrape metrics. | +| `METRICS_PORT` | `8081` | `` | Port to use for metrics server. | +| `REDIS_URL` | `redis://localhost:6379` | `` | Redis connection URL for the relayer. See [Storage Configuration](./configuration/storage) for Redis setup details. | +| `REDIS_CONNECTION_TIMEOUT_MS` | `10000` | `` | Connection timeout for Redis in milliseconds. See [Storage Configuration](./configuration/storage) for Redis configuration. | +| `REDIS_KEY_PREFIX` | `oz-relayer` | `string` | Redis key prefix for namespacing. See [Storage Configuration](./configuration/storage) for more information. | +| `STORAGE_ENCRYPTION_KEY` | `` | `string` | Encryption key used to encrypt data at rest in Redis storage. See [Storage Configuration](./configuration/storage) for security details. | +| `RPC_TIMEOUT_MS` | `10000` | `` | Sets the maximum time to wait for RPC connections before timing out. | +| `PROVIDER_MAX_RETRIES` | `3` | `` | Maximum number of retry attempts for provider operations. | +| `PROVIDER_RETRY_BASE_DELAY_MS` | `100` | `` | Base delay between retry attempts in milliseconds. | +| `PROVIDER_RETRY_MAX_DELAY_MS` | `2000` | `` | Maximum delay between retry attempts in milliseconds. | +| `PROVIDER_MAX_FAILOVERS` | `3` | `` | Maximum number of failovers (switching to different providers). | +| `ENABLE_SWAGGER` | `false` | `true, false` | Enable or disable Swagger UI for API documentation. | +| `KEYSTORE_PASSPHRASE` | `` | `` | Passphrase for the keystore file used for signing transactions. | +| `BACKGROUND_WORKER_TRANSACTION_REQUEST_CONCURRENCY` | `50` | `` | Maximum number of concurrent transaction request jobs that can be processed simultaneously. | +| `BACKGROUND_WORKER_TRANSACTION_SENDER_CONCURRENCY` | `75` | `` | Maximum number of concurrent transaction submission jobs that can be processed simultaneously. | +| `BACKGROUND_WORKER_TRANSACTION_STATUS_CHECKER_CONCURRENCY` | `50` | `` | Maximum number of concurrent generic/default transaction status check jobs that can be processed simultaneously. This worker handles Solana and any future networks that don’t have dedicated status checkers. | +| `BACKGROUND_WORKER_TRANSACTION_STATUS_CHECKER_EVM_CONCURRENCY` | `100` | `` | Maximum number of concurrent EVM transaction status check invocations that can be processed simultaneously. EVM handles the highest volume (~75% of all status checks) and uses optimized retries (8-20s) to avoid triggering premature resubmission. | +| `BACKGROUND_WORKER_TRANSACTION_STATUS_CHECKER_STELLAR_CONCURRENCY` | `50` | `` | Maximum number of concurrent Stellar transaction status checks that can be processed simultaneously. Stellar status checker uses fast retries (2-3s) optimized for Stellar’s faster block times. | +| `BACKGROUND_WORKER_NOTIFICATION_SENDER_CONCURRENCY` | `30` | `` | Maximum number of concurrent notifications that can be processed simultaneously. | +| `BACKGROUND_WORKER_SOLANA_TOKEN_SWAP_REQUEST_CONCURRENCY` | `10` | `` | Maximum number of concurrent Solana token swap requests that can be processed simultaneously. Low volume worker. | +| `BACKGROUND_WORKER_TRANSACTION_CLEANUP_CONCURRENCY` | `1` | `` | Maximum number of concurrent transaction cleanup invocations that can be processed simultaneously. Defaults to 1 to avoid database conflicts. | +| `BACKGROUND_WORKER_RELAYER_HEALTH_CHECK_CONCURRENCY` | `10` | `` | Maximum number of concurrent relayer health check invocations that can be processed simultaneously. Low volume worker. | + +### Environment configuration example + +`.env` file config example: + +``` +RUST_LOG=DEBUG +CONFIG_DIR=./config +CONFIG_FILE_NAME=config.json +WEBHOOK_SIGNING_KEY=e1d42480-6f74-4d0b-85f4-b7f0bb690fae +API_KEY=5eefd216-0e44-4ca7-b421-2925f90d30d5 +RATE_LIMIT_REQUESTS_PER_SECOND=100 +RATE_LIMIT_BURST_SIZE=300 +METRICS_ENABLED=true +METRICS_PORT=8081 +REDIS_URL=redis://localhost:6379 +REDIS_CONNECTION_TIMEOUT_MS=10000 +REDIS_KEY_PREFIX=oz-relayer +RPC_TIMEOUT_MS=10000 +PROVIDER_MAX_RETRIES=3 +PROVIDER_RETRY_BASE_DELAY_MS=100 +PROVIDER_RETRY_MAX_DELAY_MS=2000 +PROVIDER_MAX_FAILOVERS=3 +ENABLE_SWAGGER=false +KEYSTORE_PASSPHRASE=your_keystore_passphrase +STORAGE_ENCRYPTION_KEY=X67aXacJB+krEldv9i2w7NCSFwwOzVV/1ELM2KJJjQw= +REPOSITORY_STORAGE_TYPE=redis +RESET_STORAGE_ON_START=false +TRANSACTION_EXPIRATION_HOURS=8 +``` + +## Main configuration file (config.json) + +This file can exist in any directory, but the default location is `./config/config.json`. + + + + +All components defined in `config.json` can also be managed via REST API endpoints. This provides runtime flexibility for adding, updating, or removing relayers, signers, and notifications without restarting the service. See the ***API Reference*** page for detailed endpoints documentation. + + + +Key sections in this file include: + +* Signers: Defines transaction signing methods. +* Notifications: Sets up status alerts +* Relayers: Configures networks, notifications channels, policies & singers. +* Networks: Defines blockchain network configurations. +* Plugins: Configures plugins. + +### 1. Signers + +Transaction signers are responsible for cryptographically signing transactions before they are submitted to blockchain networks. + +For comprehensive details on configuring all supported signer types including: + +* Local keystore file signers +* HashiCorp Vault (secret and transit) +* Cloud KMS providers (Google Cloud, AWS) +* Turnkey signers +* CDP signers +* Security best practices and troubleshooting + +See the dedicated [Signers Configuration](./configuration/signers) guide. + + + +Signers can also be managed via API endpoints. + +See the ***API Reference*** page for detailed endpoints documentation. + + +### 2. Notifications + +* `notifications` array containing notification entries: + +```json +"notifications": [ + { + "id": "notification-test", + "type": "webhook", + "url": "https://webhook.site/f95cf78d-742d-4b21-88b7-d683e6fd147b", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } +] +``` +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the notification | +| type | String | Type of notification (only `webhook` available, for now) | +| url | String | Notification URL | +| signing_key.type | String | Type of key used in signing the notification (`env` or `plain`) | +| signing_key.value | String | Signing key value, env variable name, ... | + + + +Notifications can also be managed via API endpoints. + +See the ***API Reference*** page for detailed endpoints documentation. + + +### 3. Relayers + +* `relayers` array, containing relayer entries: + +```json +"relayers": [ + { + "id": "solana-testnet", + "name": "Solana Testnet", + "paused": false, + "notification_id": "notification-test", + "signer_id": "local-signer", + "network_type": "solana", + "network": "testnet", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 // Higher weight routes more requests to this endpoint. The value must be an integer between 0 and 100 (inclusive). + }, + { + "url": "https://backup-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "allowed_programs": [ + "11111111111111111111111111111111", + "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "BPFLoaderUpgradeab1e11111111111111111111111" + ] + } + } +] +``` + +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the relayer | +| name | String | Human readable name for the relayer | +| paused | Boolean | Whether or not the relayer is paused (`true`, `false`) | +| notification_id | String | ID of a configured notification object | +| signer_id | String | ID of a configured signer | +| network_type | String | Type of network the relayer will connect to (`evm`, `solana`) | +| network | String | Network the relayer will connect to. Must match a network identifier defined in your network configuration files. See [Network Configuration](/relayer/network_configuration) for details on defining networks. | +| custom_rpc_urls | list | Optional custom RPC URLs for the network. If provided, this will be used instead of the public RPC URLs. This is useful for using your own RPC node or a paid service provider. The first url of the list is going to be used as the default | +| policies | list | Overrides default policies. Please refer to the [`Policies`](./configuration#network-policies) table | + +Policies +| Network type | Policy | Type | Description | +| --- | --- | --- | --- | +| solana, evm | min_balance | `unsigned 128` | Minimum balance (in lamports or wei) required for the relayer to operate. Optional. | +| solana | fee_payment_strategy | `enum(user,relayer)` | Specifies who pays the fee. "user" (default) means the sender pays; "relayer" means the relayer pays. For "user", RPC methods add an instruction to transfer SPL tokens (calculated from the current SOL price plus a configurable margin) from the user to the relayer, ensuring fees are sustainably covered in tokens rather than SOL. | +| solana | swap_config | `SwapConfig` | Optional object configuring automated token‐swaps on Solana. | +| solana | fee_margin_percentage | `f32` | Additional margin percentage added to estimated transaction fees to account for price fluctuations. For example, a value of 10 will add 10% to estimated fees. Optional. | +| solana | max_allowed_fee_lamports | `unsigned 64` | Maximum allowed fee (in lamports) for a transaction. Optional. | +| solana | allowed_tokens | `Vector` | List of allowed tokens. Only these tokens are supported if provided. Optional. | +| solana | allowed_programs | `Vector` | List of allowed programs by their identifiers. Only these programs are supported if provided. | +| solana | allowed_accounts | `Vector` | List of allowed accounts by their public keys. The relayer will only operate with these accounts if provided. | +| solana | disallowed_accounts | `Vector` | List of disallowed accounts by their public keys. These accounts will be explicitly blocked. | +| solana | max_tx_data_size | `unsigned 16` | Maximum transaction size. Optional. | +| solana | max_signatures | `unsigned 8` | Maximum supported signatures. Optional. | +| evm | gas_price_cap | `unsigned 128` | Specify a maximum gas price for every transaction sent with the Relayer. When enabled, any transaction exceeding the cap will have its gasPrice or maxFeePerGas overwritten. (Optional) | +| evm | gas_limit_estimation | `bool` | Automatic gas_limit calculation. Enabled by default. (Optional) | +| evm | whitelist_receivers | `Vector` | A list of authorized contracts for each transaction sent using the Relayer. Transactions will be rejected if the destination address is not on the list. (Optional) | + +#### RPC URL Configuration + +The relayer supports two ways to configure RPC URLs: + +1. ***Public RPC URLs***: These are the default RPC endpoints provided by the network. They are automatically selected based on the network configuration. +2. ***Custom RPC URLs***: You can specify custom RPC URLs using the `custom_rpc_urls` field in the relayer configuration. Each URL can be configured with an optional weight for high availability: + +```json +"custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 // Higher weight routes more requests to this endpoint. The value must be an integer between 0 and 100 (inclusive). + }, + { + "url": "https://secondary-rpc.example.com", + "weight": 100, // Max allowed weight + }, + { + "url": "https://backup-rpc.example.com" // No weight specified, defaults to 100 + }, + { + "url": "https://backup2-rpc.example.com", + "weight": 0, // A value of 0 disables the endpoint. + } +] +``` + +This is useful when you want to: + * Use your own RPC nodes with load balancing + * Use a paid service provider for better reliability and performance + * Override the default public RPC URLs + * Access custom network endpoints + * Configure primary and backup endpoints with different weights + +When both are available, the relayer will: +1. First attempt to use the `custom_rpc_urls` if configured. +2. Fall back to the public RPC URLs if no custom URL is configured. + +For backward compatibility, string arrays are still supported: + +```json +"custom_rpc_urls": ["https://your-rpc.example.com"] +``` + + + + +When using custom RPC URLs: + +* Ensure the URLs are secure (HTTPS) when accessing over public networks +* Keep your API keys and authentication tokens secure +* Test the RPC endpoints' reliability and performance before using it in production +* Configure weights to prioritize endpoints, assigning higher values to more reliable or performant ones. +* The weight must be an integer between 0 and 100 (inclusive). +* A weight of 0 disables the endpoint. +* If a weight is not specified for an endpoint, it defaults to 100. + + + + + +Relayers could also be managed via API endpoints. + +See the ***API Reference*** page for detailed endpoints documentation. + + +### 4. Plugins + +For more information on how to write a plugin, please refer to the [Plugins](/relayer/plugins) page. + +* `plugins` array, containing plugin configurations: + +```json +"plugins": [ + { + "id": "my-plugin", + "path": "my-plugin.ts" + } +] +``` + +Available configuration fields +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique id for the plugin | +| path | String | Path to the plugin file | + +### 5. Networks + +You can configure networks either: + +* In separate JSON files (recommended for better organization) +* Directly in your main `config.json` + +For comprehensive network configuration details, including: + +* Network field reference +* Configuration examples for all network types +* Network inheritance +* Special tags and their behavior +* Best practices and troubleshooting + +See the dedicated [Network Configuration](/relayer/network_configuration) guide. + +## Configuration File Example + +Full `config/config.json` example with evm and solana relayers definitions using keystore signer: + +```json +{ + "relayers": [ + { + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "evm", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 2 + }, + { + "url": "https://backup-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "gas_price_cap": 30000000000000, + "eip1559_pricing": true + } + }, + { + "id": "solana-example", + "name": "Solana Example", + "network": "devnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": [ + { + "url": "https://primary-solana-rpc.example.com", + "weight": 2 + }, + { + "url": "https://backup-solana-rpc.example.com", + "weight": 1 + } + ], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "allowed_tokens": [ + { + "mint": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr", + "max_allowed_fee": 100000000 + }, + { + "mint": "So11111111111111111111111111111111111111112" + } + ] + } + }, + { + "id": "solana-mainnet-example", + "name": "Solana Mainnet Example", + "network": "mainnet-beta", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": ["https://your-private-solana-rpc.example.com"], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "swap_config": { + "cron_schedule": "0 0 * * * *", + "min_balance_threshold": 0, + "strategy": "jupiter-ultra" + }, + "allowed_tokens": [ + { + "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", + "max_allowed_fee": 100000000, + "swap_config": { + "min_amount": 0, + "max_amount": 0, + "retain_min_amount": 0 + } + }, + { + "mint": "So11111111111111111111111111111111111111112" + } + ] + } + } + ], + "notifications": [ + { + "id": "notification-example", + "type": "webhook", + "url": "https://webhook.site/1384d4d9-21b1-40a0-bcd1-d3f3b66be955", + "signing_key": { + "type": "env", + "value": "WEBHOOK_SIGNING_KEY" + } + } + ], + "signers": [ + { + "id": "local-signer", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } + } + ], + "networks": [ + { + "average_blocktime_ms": 12000, + "chain_id": 11155111, + "explorer_urls": [ + "https://api-sepolia.etherscan.io/api", + "https://sepolia.etherscan.io" + ], + "features": [ + "eip1559" + ], + "is_testnet": true, + "network": "sepolia", + "required_confirmations": 6, + "rpc_urls": [ + "https://sepolia.drpc.org", + "https://1rpc.io/sepolia", + "https://ethereum-sepolia-rpc.publicnode.com", + "https://ethereum-sepolia-public.nodies.app" + ], + "symbol": "ETH", + "tags": [ + "deprecated" + ], + "type": "evm" + }, + { + "type": "solana", + "network": "devnet", + "rpc_urls": ["https://api.devnet.solana.com"], + "explorer_urls": ["https://explorer.solana.com?cluster=devnet"], + "average_blocktime_ms": 400, + "is_testnet": true + }, + { + "type": "solana", + "network": "mainnet-beta", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "average_blocktime_ms": 400, + "is_testnet": false + } + ] +} +``` + +## Configuration Management Approaches + +The OpenZeppelin Relayer supports two complementary approaches for configuration management: + +### File-based Configuration +* Ideal for initial setup and deployment +* Configuration persists across restarts +* Requires container restart for changes to take effect +* Suitable for infrastructure-as-code workflows + +### API-based Configuration +* Enables runtime configuration changes +* No service restarts required +* Perfect for dynamic environments +* Supports automated configuration management + + + + +See [Storage Configuration](./configuration/storage) for detailed information about how file-based and API-based configurations work together, storage behavior, and best practices. + + diff --git a/docs/content/relayer/configuration/signers.mdx b/docs/content/relayer/configuration/signers.mdx new file mode 100644 index 00000000..6332f3bb --- /dev/null +++ b/docs/content/relayer/configuration/signers.mdx @@ -0,0 +1,374 @@ +--- +title: Signers Configuration +--- + +## Overview + +Signers are responsible for cryptographically signing transactions before they are submitted to blockchain networks. OpenZeppelin Relayer supports multiple signer types to accommodate different security requirements and infrastructure setups. + +Each signer is referenced by its `id` in relayer configurations. + +## Configuration Structure + +Example signer configuration: +```json +"signers": [ + { + "id": "my_id", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } + } +] +``` + +## Supported Signer Types + +OpenZeppelin Relayer supports the following signer types: + +* `local`: Keystore file signer +* `vault`: HashiCorp Vault secret signer +* `vault_transit`: HashiCorp Vault Transit signer +* `turnkey`: Turnkey signer +* `google_cloud_kms`: Google Cloud KMS signer +* `aws_kms`: Amazon AWS KMS signer +* `cdp`: Coinbase Developer Platform signer + +## Network Compatibility Matrix + +The following table shows which signer types are compatible with each network type: + +| Signer Type | EVM Networks | Solana Networks | Stellar Networks | +| --- | --- | --- | --- | +| `local` | ✅ Supported | ✅ Supported | ✅ Supported | +| `vault` | ✅ Supported | ✅ Supported | ❌ Not supported | +| `vault_transit` | ❌ Not supported | ✅ Supported | ❌ Not supported | +| `turnkey` | ✅ Supported | ✅ Supported | ✅ Supported | +| `google_cloud_kms` | ✅ Supported | ✅ Supported | ✅ Supported | +| `aws_kms` | ✅ Supported | ❌ Not supported | ❌ Not supported | +| `cdp` | ✅ Supported | ✅ Supported | ❌ Not supported | + + + + +***Network-specific considerations:*** + +* ***EVM Networks***: Use secp256k1 cryptography. Most signers support EVM networks with proper key generation. +* ***Solana Networks***: Use ed25519 cryptography. Ensure your signer supports ed25519 key generation and signing. +* ***Stellar Networks***: Use ed25519 cryptography with specific Stellar requirements. Supported by local, Google Cloud KMS, and Turnkey signers. +* ***AWS KMS***: Currently optimized for EVM networks with secp256k1 support. +* ***Google Cloud KMS***: Supports secp256k1 (EVM) and ed25519 (Solana, Stellar) key types. +* ***Turnkey***: Supports EVM, Solana, and Stellar networks with appropriate key management. + + + +## Common Configuration Fields + +All signer types share these common configuration fields: + +| Field | Type | Description | +| --- | --- | --- | +| id | String | Unique identifier for the signer (used to reference this signer in relayer configurations) | +| type | String | Type of signer (see supported signer types above) | +| config | Map | Signer type-specific configuration object | + +## Local Signer Configuration + +The local signer uses encrypted keystore files stored on the filesystem. + +```json +{ + "id": "local-signer", + "type": "local", + "config": { + "path": "config/keys/local-signer.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE" + } + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| path | String | Path to the signer JSON file. Should be under the `./config` directory | +| passphrase.type | String | Type of passphrase source (`env` or `plain`) | +| passphrase.value | String | Passphrase value or environment variable name | + +## HashiCorp Vault Signer Configuration + +### Vault Secret Signer + +Uses HashiCorp Vault’s secret engine to store private keys. + +```json +{ + "id": "vault-signer", + "type": "vault", + "config": { + "address": "https://vault.example.com", + "role_id": { + "type": "env", + "value": "VAULT_ROLE_ID" + }, + "secret_id": { + "type": "env", + "value": "VAULT_SECRET_ID" + }, + "key_name": "relayer-key", + "mount_point": "secret" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| address | String | Specifies the Vault API endpoint | +| role_id.type | String | Type of value source (`env` or `plain`) | +| role_id.value | String | The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored | +| secret_id.type | String | Type of value source (`env` or `plain`) | +| secret_id.value | String | The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored | +| key_name | String | The name of the cryptographic key within Vault’s Secret engine that is used for signing operations | +| mount_point | String | The mount point for the Secrets engine in Vault. Defaults to `secret` if not explicitly specified. Optional. | + +### Vault Transit Signer + +Uses HashiCorp Vault’s Transit secrets engine for cryptographic operations. + +```json +{ + "id": "vault-transit-signer", + "type": "vault_transit", + "config": { + "address": "https://vault.example.com", + "role_id": { + "type": "env", + "value": "VAULT_ROLE_ID" + }, + "secret_id": { + "type": "env", + "value": "VAULT_SECRET_ID" + }, + "key_name": "relayer-transit-key", + "mount_point": "transit", + "namespace": "production", + "pubkey": "your-public-key-here" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| address | String | Specifies the Vault API endpoint | +| role_id.type | String | Type of value source (`env` or `plain`) | +| role_id.value | String | The Vault AppRole role identifier value, or the environment variable name where the AppRole role identifier is stored | +| secret_id.type | String | Type of value source (`env` or `plain`) | +| secret_id.value | String | The Vault AppRole role secret value, or the environment variable name where the AppRole secret value is stored | +| key_name | String | The name of the cryptographic key within Vault’s Transit engine that is used for signing operations | +| mount_point | String | The mount point for the Transit secrets engine in Vault. Defaults to `transit` if not explicitly specified. Optional. | +| namespace | String | The Vault namespace for API calls. This is used only in Vault Enterprise environments. Optional. | +| pubkey | String | Public key of the cryptographic key within Vault’s Transit engine that is used for signing operations | + +## Turnkey Signer Configuration + +Uses Turnkey’s secure key management infrastructure. + +```json +{ + "id": "turnkey-signer", + "type": "turnkey", + "config": { + "api_public_key": "your-api-public-key", + "api_private_key": { + "type": "env", + "value": "TURNKEY_API_PRIVATE_KEY" + }, + "organization_id": "your-org-id", + "private_key_id": "your-private-key-id", + "public_key": "your-public-key" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| api_public_key | String | The public key associated with your Turnkey API access credentials. Used for authentication to the Turnkey signing service | +| api_private_key.type | String | Type of value source (`env` or `plain`) | +| api_private_key.value | String | The Turnkey API private key or environment variable name containing it. Used with the public key to authenticate API requests | +| organization_id | String | Your unique Turnkey organization identifier. Required to access resources within your specific organization | +| private_key_id | String | The unique identifier of the private key in your Turnkey account that will be used for signing operations | +| public_key | String | The public key corresponding to the private key identified by private_key_id. Used for address derivation and signature verification | + +## Google Cloud KMS Signer Configuration + +Uses Google Cloud Key Management Service for secure key operations. + + + + +***Network-specific key requirements:*** + +For ***EVM*** transaction signing, ensure your Google Cloud KMS key is created with: +- Protection level: HSM +- Purpose: Asymmetric sign +- Algorithm: "Elliptic Curve secp256k1 - SHA256 Digest" + +For ***Solana*** and ***Stellar*** transaction signing, ensure your Google Cloud KMS key is created with: +- Protection level: Software or HSM +- Purpose: Asymmetric sign +- Algorithm: "Elliptic Curve ED25519 Key" + + + +```json +{ + "id": "gcp-kms-signer", + "type": "google_cloud_kms", + "config": { + "service_account": { + "project_id": "your-gcp-project", + "private_key_id": { + "type": "env", + "value": "GCP_PRIVATE_KEY_ID" + }, + "private_key": { + "type": "env", + "value": "GCP_PRIVATE_KEY" + }, + "client_email": { + "type": "env", + "value": "GCP_CLIENT_EMAIL" + }, + "client_id": "your-client-id" + }, + "key": { + "location": "us-west2", + "key_ring_id": "relayer-keyring", + "key_id": "relayer-key", + "key_version": 1 + } + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| service_account.project_id | String | The Google Cloud project ID where your KMS resources are located | +| service_account.private_key_id.type | String | Type of value source for the private key ID (`env` or `plain`) | +| service_account.private_key_id.value | String | The private key ID value or the environment variable name containing it | +| service_account.private_key.type | String | Type of value source for the private key (`env` or `plain`) | +| service_account.private_key.value | String | The Google Cloud service account private key (PEM format) or the environment variable name containing it | +| service_account.client_email.type | String | Type of value source for the client email (`env` or `plain`) | +| service_account.client_email.value | String | The Google Cloud service account client email or the environment variable name containing it | +| service_account.client_id | String | The Google Cloud service account client ID | +| key.location | String | The Google Cloud location (region) where your KMS key ring is located (e.g., "us-west2", "global") | +| key.key_ring_id | String | The KMS key ring ID containing your cryptographic key | +| key.key_id | String | The KMS key ID used for signing operations | +| key.key_version | Integer | The version of the KMS key to use for signing operations. Defaults to 1 | + +## AWS KMS Signer Configuration + +Uses Amazon Web Services Key Management Service for cryptographic operations. + +```json +{ + "id": "aws-kms-signer", + "type": "aws_kms", + "config": { + "region": "us-west-2", + "key_id": "arn:aws:kms:us-west-2:123456789012:key/12345678-1234-1234-1234-123456789012" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| region | String | AWS region. If the key is non-replicated across regions, this must match the key’s original region. Optional. If not specified, the default region from shared credentials is used | +| key_id | String | ID of the key in AWS KMS (can be key ID, key ARN, alias name, or alias ARN) | + +## CDP Signer Configuration + +Uses CDP’s secure key management infrastructure. + +```json +{ + "id": "cdp-signer", + "type": "cdp", + "config": { + "api_key_id": "your-cdp-api-key-id", + "api_key_secret": { + "type": "env", + "value": "CDP_API_KEY_SECRET" + }, + "wallet_secret": { + "type": "env", + "value": "CDP_WALLET_SECRET" + }, + "account_address": "your-cdp-evm-or-solana-account-address" + } +} +``` + +Configuration fields: +| Field | Type | Description | +| --- | --- | --- | +| api_key_id | String | The Key ID of a Secret API Key. Used for authentication to the CDP signing service | +| api_key_secret.type | String | Type of value source (`env` or `plain`) | +| api_key_secret.value | String | The API key secret or environment variable name containing it. Used with the Key ID to authenticate API requests | +| wallet_secret.type | String | Type of value source (`env` or `plain`) | +| wallet_secret.value | String | The Wallet Secret or environment variable name containing it. Used to authorize API requests for signing operations. | +| account_address | String | The address of the CDP EVM EOA or CDP Solana Account used for signing operations. | + +## Security Best Practices + +### File Permissions +* Set restrictive permissions on keystore files: `chmod 0500 config/keys/*` +* Ensure configuration directories are properly secured +* Use environment variables for sensitive data like passphrases and API keys + +### Key Management +* Use HSM-backed keys for production environments when available +* Implement proper key rotation policies +* Never commit private keys or sensitive configuration to version control +* Use dedicated service accounts with minimal required permissions + +### Environment Separation +* Use different signers for different environments (development, staging, production) +* Implement proper secrets management in production deployments +* Consider using cloud-native key management services for enhanced security + +## Troubleshooting + +### Common Issues + +***Invalid keystore passphrase*** + +* Verify the passphrase environment variable is correctly set +* Check that the keystore file is not corrupted +* Ensure the keystore format is compatible + +***Cloud KMS authentication failures*** + +* Verify service account credentials are valid and properly formatted +* Check that the service account has necessary permissions for KMS operations +* Ensure the KMS key exists and is in the correct region/project + +***Vault connection issues*** + +* Verify Vault server address and network connectivity +* Check AppRole credentials and permissions +* Ensure the secret/transit engine is properly mounted and configured + +For additional troubleshooting help, check the application logs and refer to the specific cloud provider or service documentation. diff --git a/docs/content/relayer/configuration/storage.mdx b/docs/content/relayer/configuration/storage.mdx new file mode 100644 index 00000000..61133950 --- /dev/null +++ b/docs/content/relayer/configuration/storage.mdx @@ -0,0 +1,152 @@ +--- +title: Storage Configuration +--- + +## Overview + +OpenZeppelin Relayer supports two storage backends for persisting configuration data and transaction state. The choice of storage backend affects how configuration is managed, data persistence, and performance characteristics. + + + + +Storage type determines how your configuration changes persist and how file-based and API-based configuration interact. Choose the right storage type for your deployment needs. + + + + + + +***Community Contributions Welcome***: Additional storage backends (such as PostgreSQL, MongoDB, or other databases) are welcomed as contributions from the open source community. The storage system is designed to be extensible, making it straightforward to add new storage implementations. + + + +## Storage Types + +### In-Memory Storage + +In-memory storage keeps all configuration and transaction data in the application’s memory. + +#### Use Cases +* ***Development and testing environments*** +* ***Temporary deployments*** +* ***Single-instance deployments*** +* ***When data persistence across restarts is not required*** + +#### Characteristics +* ***Fast Performance***: No network overhead for data access +* ***No External Dependencies***: Does not require Redis or other external services +* ***No Persistence***: All data is lost when the container restarts +* ***Single Instance***: Cannot be shared across multiple relayer instances + +#### Configuration Sync Behavior +* Configuration from `config.json` is loaded on every startup +* API changes are ***not*** synchronized back to `config.json` file +* All API-based configuration changes are lost on restart +* File-based configuration always takes precedence on startup + +```bash +# Enable in-memory storage +REPOSITORY_STORAGE_TYPE=in-memory +``` + +### Redis Storage + +Redis storage persists all configuration and transaction data in a Redis database. + +#### Use Cases +* ***Production deployments*** +* ***Multi-instance deployments*** +* ***When data persistence is required*** +* ***Scalable environments*** +* ***When API-based configuration changes should persist*** + +#### Characteristics +* ***Persistent***: Data survives container restarts +* ***Network Dependency***: Requires Redis connection +* ***Encryption***: Supports encryption at rest for sensitive data + +#### Configuration Sync Behavior +* Configuration from `config.json` is loaded into Redis ***only once*** during the first startup +* Subsequent startups use the configuration stored in Redis +* API changes are persisted and survive restarts +* File-based configuration can override Redis by setting `RESET_STORAGE_ON_START=true` + +```bash +# Enable Redis storage +REPOSITORY_STORAGE_TYPE=redis +REDIS_URL=redis://localhost:6379 +STORAGE_ENCRYPTION_KEY=your-encryption-key-here +``` + +## Configuration Reference + +### Core Storage Settings + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `REPOSITORY_STORAGE_TYPE` | `in-memory` | `in-memory, redis` | Type of storage backend used for storing configuration and transaction data. | +| `RESET_STORAGE_ON_START` | `false` | `true, false` | When `true`, clears all data from storage on startup and reloads from config files. Useful for forcing file-based configuration to override stored data. | +| `TRANSACTION_EXPIRATION_HOURS` | `4` | `number` | Number of hours after which transactions in a final state are automatically removed from storage to prevent storage bloat. | + +### Redis-Specific Settings + +| Environment Variable | Default Value | Accepted Values | Description | +| --- | --- | --- | --- | +| `REDIS_URL` | `redis://localhost:6379` | Redis connection string | Full connection URL for the Redis instance. Supports Redis, Redis Sentinel, and Redis Cluster configurations. | +| `REDIS_CONNECTION_TIMEOUT_MS` | `10000` | `number` (milliseconds) | Maximum time to wait when connecting to Redis before timing out. | +| `REDIS_KEY_PREFIX` | `oz-relayer` | `string` | Prefix added to all Redis keys. Useful for namespacing when sharing Redis with other applications. | +| `STORAGE_ENCRYPTION_KEY` | `` | `string` (base64) | Encryption key used to encrypt sensitive data at rest in Redis. Generate using `cargo run --example generate_encryption_key`. | + +## Security Considerations + +### Redis Security + + + + +When using Redis storage in production: + +* ***Use encryption at rest***: Always set `STORAGE_ENCRYPTION_KEY` +* ***Secure Redis access***: Use Redis AUTH, TLS, and network security +* ***Network isolation***: Deploy Redis in a private network +* ***Regular backups***: Implement Redis backup strategy +* ***Monitor access***: Log and monitor Redis access patterns + + + +### Encryption at Rest + +Sensitive configuration data is encrypted before being stored in Redis when `STORAGE_ENCRYPTION_KEY` is provided. + +***Encrypted Data Includes:*** +- Signer private keys and passphrases +- Webhook signing keys +- API keys (when stored in configuration) +- Other sensitive configuration values + +***Generate Encryption Key:*** +```bash +# Generate a secure encryption key +cargo run --example generate_encryption_key + +# Alternative using OpenSSL +openssl rand -base64 32 +``` + +## Transaction Storage Management + +### Automatic Cleanup + +Transactions are automatically removed from storage after reaching their final state to prevent storage bloat: + +```bash +# Configure transaction retention (default: 4 hours) +TRANSACTION_EXPIRATION_HOURS=8 +``` + +***Final Transaction States:*** + +* `confirmed` - Transaction confirmed on blockchain +* `failed` - Transaction failed and will not be retried +* `cancelled` - Transaction was cancelled by user +* `expired`: - Transaction was expired diff --git a/docs/content/relayer/evm.mdx b/docs/content/relayer/evm.mdx new file mode 100644 index 00000000..dc50de0a --- /dev/null +++ b/docs/content/relayer/evm.mdx @@ -0,0 +1,311 @@ +--- +title: EVM Integration +--- + +## Overview + +OpenZeppelin Relayer provides comprehensive support for EVM (Ethereum Virtual Machine) networks, enabling secure transaction relaying, advanced gas management, EIP-1559 support, and robust fee estimation. This page covers everything you need to get started and make the most of EVM-specific features. + +## Features + +* Advanced gas price management with EIP-1559 support +* Dynamic gas limit estimation with fallback mechanisms +* Transaction replacement and acceleration +* Multi-network support (Ethereum, Arbitrum, Optimism, BSC, Polygon, etc.) +* Custom RPC endpoints with load balancing and failover +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and confirmation tracking +* Whitelist-based security policies +* Metrics and observability + +## Supported Networks + +EVM networks are defined via JSON configuration files, providing flexibility to: + +* Configure any EVM-compatible network (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* Set up custom EVM-compatible networks with specific RPC endpoints +* Create network variants using inheritance from base configurations +* Support both Layer 1 and Layer 2 networks + +For detailed network configuration options, see the [Network Configuration](/relayer/network_configuration) guide. + +## Supported Signers + +* `local` (local keystore files) +* `vault` (HashiCorp Vault secret storage) +* `vault_cloud` (hosted HashiCorp Vault) +* `turnkey` (hosted Turnkey signer) +* `google_cloud_kms` (Google Cloud KMS) +* `aws_kms` (Amazon AWS KMS) +* `cdp` (hosted Coinbase Developer Platform signer) + +For detailed signer configuration options, see the [Signers](/relayer/configuration/signers) guide. + + + + +In production systems, hosted signers (AWS KMS, Google Cloud KMS, Turnkey, CDP) are recommended for the best security model. + + + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/quickstart). +Key prerequisites: + +* Rust 2021, version `1.86` or later +* Redis +* Docker (optional) + +Example configuration for an EVM relayer: +```json +{ + "id": "sepolia-example", + "name": "Sepolia Example", + "network": "sepolia", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "evm", + "custom_rpc_urls": [ + { + "url": "https://primary-rpc.example.com", + "weight": 100 + }, + { + "url": "https://backup-rpc.example.com", + "weight": 100 + } + ], + "policies": { + "gas_price_cap": 100000000000, + "eip1559_pricing": true, + "gas_limit_estimation": true, + "whitelist_receivers": [ + "0x1234567890123456789012345678901234567890", + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + ], + "min_balance": 1000000000000000000 + } +}, +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository, window=_blank](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +In addition to standard relayer configuration and policies, EVM relayers support additional options: + +* `gas_price_cap`: Maximum gas price limit (in wei) for transactions +* `gas_limit_estimation`: Enable/disable automatic gas limit estimation +* `whitelist_receivers`: List of authorized contract addresses for transactions +* `min_balance`: Minimum balance required for the relayer to operate (in wei) +* `eip1559_pricing`: Enable/disable EIP-1559 pricing methodology for transaction fees + +You can check all options in [User Documentation - Relayers](/relayer#3_relayers). + +### Gas Management Configuration + +#### Gas Price Cap +Set a maximum gas price to protect against extreme network congestion: + +```json +{ + "policies": { + "gas_price_cap": 100000000000 // 100 Gwei maximum + } +} +``` + +#### Gas Limit Estimation +Enable or disable automatic gas limit estimation: + +```json +{ + "policies": { + "gas_limit_estimation": true // Enable automatic estimation + } +} +``` + +When disabled, gas limits must be provided explicitly in transaction requests. + +The relayer uses a two-tier approach for gas limit estimation: + +1. ***Primary Method***: Uses the RPC `estimate_gas` method to calculate gas requirements + * The estimated value is increased by 10% as a safety buffer + * Provides accurate estimates for most transaction types +2. ***Fallback Method***: When RPC estimation fails, default gas limits are applied based on transaction type: + * ***Simple ETH transfer*** (no data): 21,000 gas + * ***ERC20 transfer*** (`0xa9059cbb`): 65,000 gas + * ***ERC721/ERC20 transferFrom*** (`0x23b872dd`): 80,000 gas + * ***Complex contracts*** (all other function calls): 200,000 gas + + + + +For advanced users working with complex transactions or custom contracts, it is recommended to include an explicit `gas_limit` parameter in the transaction request to ensure optimal gas usage and avoid estimation errors. + + + +#### Whitelist Receivers +Restrict transactions to specific contract addresses: + +```json +{ + "policies": { + "whitelist_receivers": [ + "0x1234567890123456789012345678901234567890", + "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd" + ] + } +} +``` + +## API Reference + +The EVM API provides comprehensive transaction management capabilities. + +Common endpoints: + +* `POST /api/v1/relayers//transactions` send transaction +* `GET /api/v1/relayers//transactions` list transactions +* `GET /api/v1/relayers//transactions/` get transaction by id + +### Send Transaction - Speed params + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "speed": "average" +}' +``` + +### Send Transaction - Speed params with gas limit included + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "speed": "average", + "gas_limit": 21000 +}' +``` + +### Transaction with EIP-1559 Pricing + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "max_fee_per_gas": 30000000000, + "max_priority_fee_per_gas": 20000000000, +}' +``` + +### Transaction with Legacy Pricing - gas estimation included + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "value": 1, + "data": "0x", + "to": "0xd9b55a2ba539031e3c18c9528b0dc3a7f603a93b", + "gas_price": "12312313123" +}' +``` + +### Get Transaction Status + +```bash +curl --location --request GET 'http://localhost:8080/api/v1/relayers/sepolia-example/transactions/' \ +--header 'Authorization: Bearer ' +``` + +See [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) for full details and examples. + +## Transaction Lifecycle + +### 1. Transaction Submission +* Validate transaction parameters +* Check whitelist policies (if enabled) +* Estimate gas limit (if not provided) +* Calculate gas price based on network conditions + +### 2. Transaction Signing +* Sign transaction using configured signer +* Generate appropriate signature format + +### 3. Transaction Broadcasting +* Submit to network via RPC endpoints +* Handle RPC failures with automatic retries +* Switch to backup RPC endpoints if needed + +### 4. Transaction Monitoring +* Track transaction status and confirmations +* Handle transaction replacements if needed +* Send notifications on status changes + +### 5. Transaction Confirmation +* Wait for required number of confirmations +* Mark transaction as confirmed or failed +* Clean up resources + +## Security Best Practices + +### Network Security +* Use private RPC endpoints in production +* Configure appropriate `gas_price_cap` to prevent excessive fees +* Enable `whitelist_receivers` for controlled environments +* Monitor relayer balance and set appropriate `min_balance` + +### Signer Security +* Use hosted signers (AWS KMS, Google Cloud KMS, Turnkey) in production +* Rotate signer keys regularly +* Implement proper access controls and audit logging +* Never store private keys in plain text + +### Operational Security +* Deploy behind a secure reverse proxy +* Use HTTPS for all communications +* Implement proper rate limiting +* Monitor for unusual transaction patterns + +### Monitoring and Observability + +Enable metrics and monitor: + +* Transaction success rates +* Gas price trends +* RPC endpoint performance +* Relayer balance levels +* Failed transaction patterns + +## Support + +For help with EVM integration: + +* Join our [Telegram](https://t.me/openzeppelin_tg/2) community +* Open an issue on our [GitHub repository](https://github.com/OpenZeppelin/openzeppelin-relayer) +* Check our [comprehensive documentation](https://docs.openzeppelin.com/relayer) + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/guides/index.mdx b/docs/content/relayer/guides/index.mdx new file mode 100644 index 00000000..7fca7f9c --- /dev/null +++ b/docs/content/relayer/guides/index.mdx @@ -0,0 +1,15 @@ +--- +title: Guides +--- + +## Overview + +Step-by-step guides for integrating with OpenZeppelin Relayer services and implementing common patterns for blockchain applications. + +## Available Guides + +### Stellar Channels Guide + +A comprehensive guide to using the OpenZeppelin Stellar Channels Service - a managed infrastructure for submitting Stellar Soroban transactions with automatic parallel processing and fee management. + +[Read the Stellar Channels Guide →](./stellar-channels-guide.mdx) diff --git a/docs/content/relayer/guides/stellar-channels-guide.mdx b/docs/content/relayer/guides/stellar-channels-guide.mdx new file mode 100644 index 00000000..1ba655ba --- /dev/null +++ b/docs/content/relayer/guides/stellar-channels-guide.mdx @@ -0,0 +1,219 @@ +--- +title: Stellar Channels Guide +--- + +## Overview + +OpenZeppelin Stellar Channels Service is a managed infrastructure for submitting Stellar Soroban transactions with automatic parallel processing and fee management. The service handles all the complexity of transaction submission, allowing you to focus on building your application. + +**Key Benefits:** + +- **_Zero Infrastructure Management_**: No servers, relayers, or channel accounts to configure +- **_Automatic Fee Payment_**: Gas fees paid by the service on your behalf +- **_Parallel Processing_**: High throughput via managed pool of channel accounts +- **_Simple Integration_**: Type-safe SDK with minimal setup +- **_Free to Use_**: No credits, subscriptions, or payment systems + +## Service Endpoints + +- **Mainnet**: `https://channels.openzeppelin.com` +- **Testnet**: `https://channels.openzeppelin.com/testnet` + +## Getting Started + +### 1. Get Your API Key + +Visit the service endpoint to generate an API key: + +- **Mainnet**: https://channels.openzeppelin.com/gen +- **Testnet**: https://channels.openzeppelin.com/testnet/gen + +Save your API key securely - you'll need it for all requests. + +### 2. Install the Client + +```bash +npm install @openzeppelin/relayer-plugin-channels +# or +pnpm add @openzeppelin/relayer-plugin-channels +# or +yarn add @openzeppelin/relayer-plugin-channels +``` + +### 3. Initialize the Client + +```typescript +import { ChannelsClient } from '@openzeppelin/relayer-plugin-channels'; + +const client = new ChannelsClient({ + baseUrl: 'https://channels.openzeppelin.com/testnet', + apiKey: process.env.CHANNELS_API_KEY, +}); +``` + +## Submitting Transactions + +The Channels service supports two transaction submission methods depending on your use case. + +### Method 1: Soroban Function + Auth (Recommended) + +This method is ideal when you want the service to handle transaction building and simulation. Submit the Soroban function and authorization entries, and the service builds the complete transaction using a channel account from the pool, enabling high-throughput parallel processing. + +```typescript +import { Contract, Networks, SorobanRpc } from '@stellar/stellar-sdk'; + +// Initialize your contract +const contract = new Contract('CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA'); + +// Build the transaction (don't sign yet) +const rpc = new SorobanRpc.Server('https://soroban-testnet.stellar.org'); +const source = await rpc.getAccount(sourceAddress); + +const tx = new TransactionBuilder(source, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(contract.call('transfer' /* args */)) + .setTimeout(30) + .build(); + +// Simulate to get auth entries +const simulation = await rpc.simulateTransaction(tx); +const assembled = SorobanRpc.assembleTransaction(tx, simulation).build(); + +// Extract function and auth XDRs +const op = assembled.operations[0]; +const func = op.func.toXDR('base64'); +const auth = (op.auth ?? []).map((a) => a.toXDR('base64')); + +// Submit to Channels +const result = await client.submitSorobanTransaction({ + func: func, + auth: auth, +}); + +console.log('Transaction submitted:', result.hash); +console.log('Status:', result.status); +``` + +**When to use this method:** + +- You want the service to handle transaction assembly +- You're working with standard Soroban contract calls +- You need automatic simulation and resource calculation +- You need high-throughput parallel transaction processing + +### Method 2: Pre-Signed Transaction XDR + +This method gives you full control over the transaction structure. You build, sign, and submit a complete transaction envelope. + +```typescript +import { Keypair, Networks, TransactionBuilder } from '@stellar/stellar-sdk'; + +// Build and sign your transaction +const sourceKeypair = Keypair.fromSecret('S...'); +const tx = new TransactionBuilder(source, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(/* your operation */) + .setTimeout(30) + .build(); + +// Sign the transaction +tx.sign(sourceKeypair); + +// Submit to Channels +const result = await client.submitTransaction({ + xdr: tx.toXDR(), // base64 envelope XDR +}); + +console.log('Transaction submitted:', result.hash); +console.log('Status:', result.status); +``` + +**When to use this method:** + +- You need precise control over transaction structure +- You're using advanced Stellar features +- Your transaction is already signed by another system + +## Response Format + +All successful submissions return: + +```typescript +{ + transactionId: string; // Internal tracking ID + hash: string; // Stellar transaction hash + status: string; // Transaction status (e.g., "confirmed") +} +``` + +## Error Handling + +The SDK provides structured error handling with three error types: + +```typescript +import { + PluginTransportError, + PluginExecutionError, + PluginUnexpectedError, +} from '@openzeppelin/relayer-plugin-channels'; + +try { + const result = await client.submitSorobanTransaction({ func, auth }); + console.log('Success:', result.hash); +} catch (error) { + if (error instanceof PluginTransportError) { + // Network failures (connection, timeout, 5xx errors) + console.error('Service unavailable:', error.message); + console.error('Status:', error.statusCode); + } else if (error instanceof PluginExecutionError) { + // Transaction rejected (validation, simulation failure, on-chain failure) + console.error('Transaction failed:', error.message); + console.error('Error code:', error.errorDetails?.code); + console.error('Details:', error.errorDetails?.details); + } else if (error instanceof PluginUnexpectedError) { + // Client-side errors (parsing, validation) + console.error('Client error:', error.message); + } +} +``` + +### Common Error Codes + +| Code | Description | Resolution | +| --------------------- | ------------------------------------- | --------------------------------------------------------- | +| `INVALID_PARAMS` | Invalid request parameters | Check that you're providing either `xdr` OR `func`+`auth` | +| `INVALID_XDR` | Failed to parse XDR | Verify XDR is valid base64 and properly encoded | +| `POOL_CAPACITY` | All channel accounts in use | Retry after a short delay | +| `SIMULATION_FAILED` | Transaction simulation failed | Check contract address and function arguments | +| `ONCHAIN_FAILED` | Transaction failed on-chain | Review transaction logic and on-chain state | +| `INVALID_TIME_BOUNDS` | Transaction timeout too far in future | Set timeout to ≤30 seconds | + +## TypeScript Support + +The SDK is fully typed + +```typescript +import type { + ChannelsClient, + ChannelsFuncAuthRequest, + ChannelsXdrRequest, + ChannelsTransactionResponse, +} from '@openzeppelin/relayer-plugin-channels'; + +// All parameters and responses are fully typed +const request: ChannelsFuncAuthRequest = { + func: 'AAAABAAAAAEAAAAGc3ltYm9s...', + auth: ['AAAACAAAAAEAAAA...'], +}; + +const response: ChannelsTransactionResponse = await client.submitSorobanTransaction(request); +``` + +## Support & Resources + +- **Stellar SDK Documentation**: https://stellar.github.io/js-stellar-sdk/ +- **Channels Plugin**: https://github.com/OpenZeppelin/relayer-plugin-channels diff --git a/docs/content/relayer/index.mdx b/docs/content/relayer/index.mdx new file mode 100644 index 00000000..8478169c --- /dev/null +++ b/docs/content/relayer/index.mdx @@ -0,0 +1,349 @@ +--- +title: OpenZeppelin Relayer +--- + +## Overview + +OpenZeppelin Relayer is a service that provides infrastructure to relay transactions to the EVM & Non-EVM networks. It is designed to be used as a backend for dApps that need to interact with these networks. + +## Features + +* ***Multi-Chain Support***: Interact with multiple blockchain networks, including Solana and EVM-based chains. +* ***Transaction Relaying***: Submit transactions to supported blockchain networks efficiently. +* ***Transaction Signing***: Securely sign transactions using configurable key management. +* ***Transaction Fee Estimation***: Estimate transaction fees for better cost management. +* ***Solana Gasless Transactions***: Support for gasless transactions on Solana, enabling users to interact without transaction fees. +* ***Transaction Nonce Management***: Handle nonce management to ensure transaction order. +* ***Transaction Status Monitoring***: Track the status of submitted transactions. +* ***SDK Integration***: Easily interact with the relayer through our companion JavaScript/TypeScript SDK. +* ***Extensible Architecture***: Easily add support for new blockchain networks. +* ***Configurable Network Policies***: Define and enforce network-specific policies for transaction processing. +* ***Metrics and Observability***: Monitor application performance using Prometheus and Grafana. +* ***Docker Support***: Deploy the relayer using Docker for both development and production environments. +* ***Plugins***: Extend the functionality of the relayer with custom logic using TypeScript functions. + +## Supported Networks + +OpenZeppelin Relayer supports multiple blockchain networks through a flexible JSON-based configuration system. Networks are defined in configuration files, allowing you to configure: + +* ***Any EVM-compatible network*** (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* ***Solana networks*** (mainnet-beta, devnet, testnet, custom RPC endpoints) +* ***Stellar networks*** (Pubnet, Testnet, custom networks) +* ***Create custom network configurations*** with specific RPC endpoints, chain IDs, and network parameters +* ***Use inheritance*** to create network variants that inherit from base configurations + +### Network Types + +| Network Type | Description | +| --- | --- | +| `evm` | Ethereum Virtual Machine compatible networks. Supports any EVM chain by configuring chain ID, RPC URLs, and network-specific parameters. | +| `solana` | Solana blockchain networks. Supports all Solana clusters and custom RPC endpoints. | +| `stellar` | Stellar blockchain networks (Partial support). Supports Stellar Public Network and Testnet. | + +Networks can be loaded from: + +* ***JSON arrays***: Direct network definitions in configuration files +* ***Directory of files***: Multiple JSON files each containing network definitions + +For detailed network configuration options and examples, see the [Network Configuration](/relayer/network_configuration) page. + + + + +For information about our development plans and upcoming features, see [Project Roadmap](/relayer/roadmap). + + + + + + +To get started immediately, see [Quickstart](/relayer/quickstart). + + + +## Technical Overview + +```mermaid +%%{init: { + 'theme': 'base', + 'themeVariables': { + 'background': '#ffffff', + 'mainBkg': '#ffffff', + 'primaryBorderColor': '#cccccc' + } +}}%% +flowchart TB + subgraph "Clients" + client[API/SDK] + end + + subgraph "OpenZeppelin Relayer" + subgraph "API Layer" + api[API Routes & Controllers] + middleware[Middleware] + plugins[Plugins] + end + + subgraph "Domain Layer" + domain[Domain Logic] + relayer[Relayer Services] + policies[Policy Enforcement] + end + + subgraph "Infrastructure" + repositories[Repositories] + jobs[Job Queue System] + signer[Signer Services] + provider[Network Providers] + end + + subgraph "Services Layer" + transaction[Transaction Services] + vault[Vault Services] + webhook[Webhook Notifications] + monitoring[Monitoring & Metrics] + end + + subgraph "Configuration" + config_files[Config Files] + env_vars[Environment Variables] + end + end + + subgraph "External Systems" + blockchain[Blockchain Networks] + redis[Redis] + vault_ext[HashiCorp Vault] + metrics[Prometheus/Grafana] + notification[Notification Services] + end + + %% Client connections + client -- "HTTP Requests" --> api + + %% API Layer connections + api -- "Processes requests" --> middleware + middleware -- "Validates & routes" --> domain + middleware -- "Invokes" --> plugins + + %% Domain Layer connections + domain -- "Uses" --> relayer + domain -- "Enforces" --> policies + relayer -- "Processes" --> transaction + plugins -- "Interacts with" --> relayer + + %% Services Layer connections + transaction -- "Signs with" --> signer + transaction -- "Connects via" --> provider + transaction -- "Queues jobs" --> jobs + webhook -- "Notifies" --> notification + monitoring -- "Collects" --> metrics + signer -- "May use" --> vault + + %% Infrastructure connections + repositories -- "Stores data" --> redis + jobs -- "Processes async" --> redis + vault -- "Secrets management" --> vault_ext + provider -- "Interacts with" --> blockchain + + %% Configuration connections + config_files -- "Configures" --> domain + env_vars -- "Configures" --> domain + + %% Styling + classDef apiClass fill:#f9f,stroke:#333,stroke-width:2px + classDef domainClass fill:#bbf,stroke:#333,stroke-width:2px + classDef infraClass fill:#bfb,stroke:#333,stroke-width:2px + classDef serviceClass fill:#fbf,stroke:#333,stroke-width:2px + classDef configClass fill:#fbb,stroke:#333,stroke-width:2px + classDef externalClass fill:#ddd,stroke:#333,stroke-width:1px + + class api,middleware,plugins apiClass + class domain,relayer,policies domainClass + class repositories,jobs,signer,provider infraClass + class transaction,vault,webhook,monitoring serviceClass + class config_files,env_vars configClass + class blockchain,redis,vault_ext,metrics,notification externalClass +``` + +## Project Structure + +The project follows a standard Rust project layout: + +``` +openzeppelin-relayer/ +├── src/ +│ ├── api/ # Route and controllers logic +│ ├── bootstrap/ # Service initialization logic +│ ├── config/ # Configuration logic +│ ├── constants/ # Constant values used in the system +│ ├── domain/ # Domain logic +│ ├── jobs/ # Asynchronous processing logic (queueing) +│ ├── logging/ # Logs File rotation logic +│ ├── metrics/ # Metrics logic +│ ├── models/ # Data structures and types +│ ├── repositories/ # Configuration storage +│ ├── services/ # Services logic +│ └── utils/ # Helper functions +│ +├── config/ # Configuration files +├── tests/ # Integration tests +├── docs/ # Documentation +├── scripts/ # Utility scripts +├── examples/ # Configuration examples +├── helpers/ # Rust helper scripts +├── plugins/ # Plugins directory +└── ... other root files (Cargo.toml, README.md, etc.) +``` + +For detailed information about each directory and its contents, see [Project Structure Details](/relayer/structure). + +## Getting Started + +### Prerequisites + +* Rust 2021 edition, version `1.86` or later +* Docker (optional, for containerized deployment) +* Node.js, typescript and ts-node (optional, for plugins) + + + +**Ready-to-Use Example Configurations** + +For quick setup with various configurations, check the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository: + +* `basic-example`: Simple setup with Redis +* `basic-example-logging`: Configuration with file-based logging +* `basic-example-metrics`: Setup with Prometheus and Grafana metrics +* `vault-secret-signer`: Using HashiCorp Vault for key management +* `vault-transit-signer`: Using Vault Transit for secure signing +* `evm-gcp-kms-signer`: Using Google Cloud KMS for EVM secure signing +* `evm-turnkey-signer`: Using Turnkey for EVM secure signing +* `solana-turnkey-signer`: Using Turnkey for Solana secure signing +* `evm-cdp-signer`: Using CDP for EVM secure signing +* `solana-cdp-signer`: Using CDP for Solana secure signing +* `redis-storage`: Using Redis for Storage +* `network-configuration-config-file`: Using Custom network configuration via config file +* `network-configuration-json-file`: Using Custom network configuration via JSON file + +Each example includes a README with step-by-step instructions and Docker Compose configuration. + + +### Install Locally + +1. Clone the repository: + + ```bash + git clone https://github.com/openzeppelin/openzeppelin-relayer + cd openzeppelin-relayer + ``` +2. Verify you have sodium libs installed. If not, follow these instructions: + + * Install a stable libsodium version from [here](https://download.libsodium.org/libsodium/releases/). + * Follow the steps in the [libsodium installation guide](https://doc.libsodium.org/installation). +3. Install dependencies: + + ```bash + cargo build + ``` + +## Running the Relayer + +### Option 1: Run Locally + +```bash +cargo run +``` + + +Before executing the command, ensure that the `.env` and `config.json` files are configured as detailed in the [Configuration References](/relayer#configuration_references) section. + + +### Option 2: Run with Docker + +The Relayer can be run as either a development or production container using the corresponding Dockerfile (`Dockerfile.development` or `Dockerfile.production`). + +#### Step 1: Configure Environment + +* Edit `.env` at the root of the repository to adjust environment variables +* The appropriate .env file will be included during image build + +#### Step 2: Build the Image + +You can build using Docker Compose (v2). + +```bash +# Default build +docker compose build + +# Or, for a leaner image (and using Dockerfile.production) +DOCKERFILE=Dockerfile.production docker compose build +``` + +#### Step 3: Run the Container + +Use Docker Compose to run the container: + +```bash +docker compose up -d +``` + +For production runs, you can use: + +```bash +DOCKERFILE=Dockerfile.production docker compose up -d +``` + +## Configuration + +OpenZeppelin Relayer supports two configuration approaches: + +***File-based Configuration:*** +- ***`config.json`***: Contains relayer definitions, signer configurations, and network policies +- ***`.env`***: Contains environment variables like API keys and connection strings + +***API-based Configuration:*** +- Runtime configuration management via REST API +- No service restarts required for configuration changes +- Full CRUD operations for relayers, signers, and notifications + + + + +Both approaches can be used together. File-based configuration is loaded on startup, while API changes provide runtime flexibility. Changes to environment variables (`.env`) always require restarting the container. + +When used together, API changes are not synced to file-based configuration. File-based configuration is loaded only once when using persistent storage mode. + +For quick setup examples with pre-configured files, see the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository. + + + +For comprehensive configuration details, including: + +* Environment variables and their settings +* Main configuration file structure +* Signer configurations (local, vault, cloud KMS, etc.) +* Notification setup +* Relayer policies and network settings +* Plugin configuration +* Complete configuration examples + +See the dedicated [Configuration Guide](/relayer/configuration). + +## Important Considerations + +## Deployment Considerations + + +The OpenZeppelin Relayer is designed to function as a backend service and is not meant to be directly exposed to the public internet. To protect the service from unauthorized access, deploy it behind your own secure backend infrastructure—such as a reverse proxy or firewall—and restrict access to trusted internal components only. Direct exposure can increase the risk of exploitation and security breaches. + + +## Support + +For support or inquiries, contact us on [Telegram](https://t.me/openzeppelin_tg/2). + +## License +This project is licensed under the GNU Affero General Public License v3.0 - see the LICENSE file for details. + +## Security +For security concerns, please refer to our [Security Policy](https://github.com/OpenZeppelin/openzeppelin-relayer/blob/main/SECURITY.md). diff --git a/docs/content/relayer/latest-versions.js b/docs/content/relayer/latest-versions.js new file mode 100644 index 00000000..0f69c24b --- /dev/null +++ b/docs/content/relayer/latest-versions.js @@ -0,0 +1,17 @@ +/** + * @typedef {Object} VersionConfig + * @property {string} label - Display label for the version + * @property {string} value - Internal value identifier + * @property {string} path - URL path for the version + * @property {boolean} isStable - Whether this is a stable release + */ + +export const latestStable = "1.1.x"; + +/** @type {VersionConfig[]} */ + +export const allVersions = [ + { label: "v1.1.x (latest stable)", value: "1.1.x", path: "/relayer/1.1.x", isStable: true }, + { label: "v1.0.x", value: "1.0.x", path: "/relayer/1.0.x", isStable: true }, + { label: "Development", value: "development", path: "/relayer", isStable: false } +]; diff --git a/docs/content/relayer/network_configuration.mdx b/docs/content/relayer/network_configuration.mdx new file mode 100644 index 00000000..4e4ac53f --- /dev/null +++ b/docs/content/relayer/network_configuration.mdx @@ -0,0 +1,387 @@ +--- +title: Network Configuration +--- + +The OpenZeppelin Relayer supports multiple blockchain networks through a flexible JSON-based configuration system. This guide covers everything you need to know about configuring networks for your relayer instances. + +## Overview + +Networks are defined in JSON configuration files, allowing you to: + +* Configure ***any EVM-compatible network*** (Ethereum, Polygon, BSC, Arbitrum, Optimism, etc.) +* Set up ***Solana networks*** (mainnet-beta, devnet, testnet, custom RPC endpoints) +* Configure ***Stellar networks*** (Pubnet, Testnet, custom networks) +* Create ***custom network configurations*** with specific RPC endpoints, chain IDs, and network parameters +* Use ***inheritance*** to create network variants without duplicating configuration + +## Network Types + +| Network Type | Description | +| --- | --- | +| `evm` | Ethereum Virtual Machine compatible networks. Supports any EVM chain by configuring chain ID, RPC URLs, and network-specific parameters. | +| `solana` | Solana blockchain networks. Supports all Solana clusters and custom RPC endpoints. | +| `stellar` | Stellar blockchain networks. Supports Stellar Public Network and Testnet. | + +## Configuration Methods + +### Default Network Configuration + +If no `networks` field is specified in your `config.json`, the relayer will automatically load network configurations from the `./config/networks` directory. This is the default behavior. + +```json +{ + "relayers": [...], + "notifications": [...], + "signers": [...] + // No "networks" field - defaults to "./config/networks" +} +``` + + +Once you specify a `networks` field in your configuration, the default `./config/networks` directory will ***not*** be loaded automatically. If you want to use files from that directory, you must explicitly specify the path `"./config/networks"`. + + +You can configure networks in two ways: + +### Method 1: Separate JSON Files + +Specify the path to network configuration files in your main `config.json`: + +```json +{ + "relayers": [...], + "notifications": [...], + "signers": [...], + "networks": "./config/networks" // Path to directory or file +} +``` + + +This is the same as the default behavior, but explicitly specified. You can also point to a different directory or file path. + + +Each JSON file ***must*** contain a top-level `networks` array: + +```json +{ + "networks": [ + // ... network definitions ... + ] +} +``` + +When using a directory structure: +``` +networks/ +├── evm.json # {"networks": [...]} +├── solana.json # {"networks": [...]} +└── stellar.json # {"networks": [...]} +``` + +### Method 2: Direct Configuration + +Define networks directly in your main `config.json` instead of using separate files: + +```json +{ + "relayers": [...], + "notifications": [...], + "signers": [...], + "networks": [ + { + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, + // ... other fields + } + ] +} +``` + +When using this method, the default `./config/networks` directory is ignored, and only the networks defined in this array will be available. + +## Network Field Reference + +### Common Fields + +All network types support these configuration fields: + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `type` | string | Yes | Network type: `"evm"`, `"solana"`, or `"stellar"` | +| `network` | string | Yes | Unique network identifier (e.g., "ethereum-mainnet", "polygon-mumbai") | +| `from` | string | No | Name of parent network to inherit from (same type only) | +| `rpc_urls` | array[string] | Yes* | List of RPC endpoint URLs (*Required for base networks, optional for inherited) | +| `explorer_urls` | array[string] | No | List of blockchain explorer URLs | +| `average_blocktime_ms` | number | No | Estimated average time between blocks in milliseconds | +| `is_testnet` | boolean | No | Whether this is a testnet (affects behavior and validation) | +| `tags` | array[string] | No | Arbitrary tags for categorization and filtering | + +### Special Network Tags + +Some tags have special meaning and affect relayer behavior: + +| Tag | Description and Behavior | +| --- | --- | +| `rollup` | Identifies Layer 2 rollup networks (e.g., Arbitrum, Optimism, Base) | +| `optimism-based` | Identifies Optimism-based networks using the OP Stack (e.g., Optimism, Base, World Chain) | +| `optimism` _(deprecated)_ | ***DEPRECATED***: Use `optimism-based` instead. This tag will be removed in a future version. | +| `arbitrum-based` | Identifies Arbitrum-based networks using the Arbitrum Stack | +| `no-mempool` | Indicates networks that lack a traditional mempool (e.g., Arbitrum). Note: The relayer also treats networks tagged as `arbitrum-based` or `optimism-based` as lacking a mempool, even if `no-mempool` is not present. | +| `deprecated` | Marks networks that are deprecated and may be removed in future versions | + +#### Example: Using Special Tags + +Here’s an example showing how special tags are used in practice: + +```json +{ + "type": "evm", + "network": "arbitrum-one", + "chain_id": 42161, + "required_confirmations": 1, + "symbol": "ETH", + "rpc_urls": ["https://arb1.arbitrum.io/rpc"], + "tags": ["rollup", "no-mempool"], // Arbitrum is a rollup without mempool + "is_testnet": false +} +``` + +These tags help the relayer: + +* Apply specific transaction handling for rollups +* Use optimized fee calculation for OP Stack chains +* Skip mempool-related operations for networks without mempools +* Warn users about deprecated networks + +### EVM-Specific Fields + + +The OpenZeppelin Relayer supports any EVM-based L1 blockchain, as long as it doesn’t deviate significantly from standard EVM behavior. Some L2 networks may also work, depending on how closely they follow EVM conventions. Users are encouraged to add the networks they need via the JSON configuration and test them thoroughly on testnets before deploying to production. + + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `chain_id` | number | Yes* | Unique chain identifier (e.g., 1 for Ethereum mainnet, 137 for Polygon) (*Required for base networks, optional for inherited) | +| `required_confirmations` | number | Yes* | Number of block confirmations before considering a transaction final (*Required for base networks, optional for inherited) | +| `symbol` | string | Yes* | Native currency symbol (e.g., "ETH", "MATIC", "BNB") (*Required for base networks, optional for inherited) | +| `features` | array[string] | No | Supported features (e.g., ["eip1559", "london"]) | + +#### Example: EVM Network Configuration + +Here’s an example showing an EVM network configuration: + +```json +{ + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, // Ethereum mainnet chain ID + "required_confirmations": 12, // High security: 12 confirmations + "symbol": "ETH", // Native currency symbol + "features": ["eip1559"], // Supports EIP-1559 fee market + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"], + "is_testnet": false +} +``` + +### Solana-Specific Fields + +Currently, Solana networks use only the common fields. Additional Solana-specific configuration options may be added in future versions. + +### Stellar-Specific Fields + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `passphrase` | string | No | Network passphrase for transaction signing and network identification (optional for all networks, including base networks) | + +#### Example: Stellar Network Configuration + +Here’s an example showing a Stellar network configuration with passphrase: + +```json +{ + "type": "stellar", + "network": "pubnet", + "rpc_urls": ["https://mainnet.sorobanrpc.com"], + "explorer_urls": ["https://stellar.expert/explorer/public"], + "passphrase": "Public Global Stellar Network ; September 2015", // Official mainnet passphrase + "average_blocktime_ms": 5000, + "is_testnet": false +} +``` + +## Configuration Examples + +### Basic EVM Network + +```json +{ + "type": "evm", + "network": "ethereum-mainnet", + "chain_id": 1, + "required_confirmations": 12, + "symbol": "ETH", + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"], + "explorer_urls": ["https://etherscan.io"], + "average_blocktime_ms": 12000, + "is_testnet": false, + "tags": ["mainnet", "ethereum"] +} +``` + +### Layer 2 EVM Network with Tags + +```json +{ + "type": "evm", + "network": "optimism", + "chain_id": 10, + "required_confirmations": 1, + "symbol": "ETH", + "rpc_urls": [ + "https://mainnet.optimism.io", + "https://optimism.drpc.org" + ], + "features": ["eip1559"], + "tags": ["rollup", "optimism-based"], + "average_blocktime_ms": 2000, + "is_testnet": false +} +``` + +### Solana Network + +```json +{ + "type": "solana", + "network": "mainnet-beta", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "average_blocktime_ms": 400, + "is_testnet": false, + "tags": ["mainnet", "solana"] +} +``` + +### Stellar Network + +```json +{ + "type": "stellar", + "network": "pubnet", + "rpc_urls": ["https://mainnet.sorobanrpc.com"], + "passphrase": "Public Global Stellar Network ; September 2015", + "explorer_urls": ["https://stellar.expert/explorer/public"], + "average_blocktime_ms": 5000, + "is_testnet": false, + "tags": ["mainnet", "stellar"] +} +``` + +## Network Inheritance + +Networks can inherit from other networks of the same type, allowing you to create variants without duplicating configuration: + +```json +{ + "networks": [ + { + "type": "evm", + "network": "ethereum-base", + "chain_id": 1, + "required_confirmations": 12, + "symbol": "ETH", + "rpc_urls": ["https://mainnet.infura.io/v3/YOUR_KEY"] + }, + { + "from": "ethereum-base", + "type": "evm", + "network": "ethereum-sepolia", + "chain_id": 11155111, + "required_confirmations": 3, + "rpc_urls": ["https://sepolia.infura.io/v3/YOUR_KEY"], + "is_testnet": true + } + ] +} +``` + +When using inheritance: + +* The child network inherits all fields from the parent +* Fields specified in the child override parent values +* The `from` field must reference a network of the same type + +## Using Networks in Relayer Configuration + +Once networks are defined, reference them in your relayer configurations: + +```json +{ + "relayers": [ + { + "id": "my-evm-relayer", + "name": "My EVM Relayer", + "network": "ethereum-mainnet", // References network ID + "network_type": "evm", + "signer_id": "my-signer" + } + ] +} +``` + +## Best Practices + +### 1. Network Organization +* Group related networks in separate files (e.g., `ethereum.json`, `polygon.json`) +* Use consistent naming conventions for network identifiers +* Include both mainnet and testnet configurations + +### 2. RPC URLs +* Always configure multiple RPC URLs for redundancy +* Use private/dedicated RPC endpoints for production +* Ensure URLs are secure (HTTPS) when accessing over public networks + +### 3. Confirmation Requirements +* Set appropriate `required_confirmations` based on network security +* Higher values for mainnet, lower for testnets +* Consider network-specific finality characteristics + +### 4. Tags and Features +* Use tags to categorize networks (e.g., "mainnet", "testnet", "rollup") +* Enable appropriate features (e.g., "eip1559" for supported networks) +* Document custom tags used in your organization + +### 5. Inheritance +* Create base configurations for common settings +* Use inheritance to reduce duplication +* Override only necessary fields in child networks + +## Troubleshooting + +### Common Issues + +***Network not found:*** + +* Ensure the network identifier in relayer config matches exactly +* Check that network configuration files are in the correct location +* Verify JSON syntax is valid + +***RPC connection failures:*** + +* Test RPC URLs independently before configuring +* Ensure firewall/network allows outbound HTTPS connections +* Check API keys are included in RPC URLs where required + +***Invalid configuration:*** + +* Validate required fields are present for network type +* Ensure numeric fields (chain_id, confirmations) are numbers, not strings +* Check that inherited networks reference existing parent networks + +## See Also + +* [Relayer Configuration](/relayer#relayer_configuration) +* [Quickstart Guide](/relayer/quickstart) +* [Solana Integration](/relayer/solana) +* [API Reference](/relayer/api) diff --git a/docs/content/relayer/plugins/channels.mdx b/docs/content/relayer/plugins/channels.mdx new file mode 100644 index 00000000..76d1b280 --- /dev/null +++ b/docs/content/relayer/plugins/channels.mdx @@ -0,0 +1,616 @@ +--- +title: Channels +--- + +## Overview + +Channels is a plugin for OpenZeppelin Relayer that enables parallel transaction submission on Stellar using channel accounts with automatic fee bumping. Channel accounts provide unique sequence numbers for concurrent transaction processing, eliminating sequence number conflicts. + +The plugin features: + +- **_Parallel Transaction Processing_**: Uses a pool of channel accounts for concurrent submissions +- **_Automatic Fee Bumping_**: Dedicated fund account pays transaction fees +- **_Dynamic Pool Management_**: Channel accounts acquired and released automatically +- **_Transaction Simulation_**: Automatically simulates and builds transactions with proper resources +- **_Management API_**: Dynamic configuration of channel accounts + +## Using the Plugin Client + +The fastest way to interact with Channels is through the plugin's TypeScript client, which provides a type-safe, unified interface for transaction submission and management operations. + +### Installation + +```bash +npm install @openzeppelin/relayer-plugin-channels +# or +pnpm add @openzeppelin/relayer-plugin-channels +``` + +### Client Setup + +The client supports two connection modes that are automatically detected based on configuration: + +```typescript +import { ChannelsClient } from '@openzeppelin/relayer-plugin-channels'; + +// Direct HTTP connection to Channels service +const directClient = new ChannelsClient({ + baseUrl: 'https://channels.openzeppelin.com', // Channels service URL + apiKey: process.env.CHANNELS_API_KEY, // Service API key + adminSecret: process.env.CHANNELS_ADMIN, // Optional: for management + timeout: 30000, // Optional: request timeout +}); + +// Connection via OpenZeppelin Relayer plugin +const relayerClient = new ChannelsClient({ + pluginId: 'channels-plugin', // Plugin identifier (triggers relayer mode) + baseUrl: 'http://localhost:8080', // Relayer URL + apiKey: process.env.RELAYER_API_KEY, // Relayer API key + adminSecret: process.env.CHANNELS_ADMIN, // Optional: for management +}); +``` + +### Submitting Transactions + +Channels provides separate methods for different transaction types: + +```typescript +// Submit signed transaction (XDR) +const result = await client.submitTransaction({ + xdr: 'AAAAAgAAAABQEp+s8xGPrF...', // Complete signed envelope +}); + +// Submit Soroban transaction (func + auth) +const result = await client.submitSorobanTransaction({ + func: 'AAAABAAAAAEAAAAGc3ltYm9s...', // Host function XDR + auth: ['AAAACAAAAAEAAAA...', 'AAAACAAAAAEAAAB...'], // Detached auth entries +}); + +console.log('Transaction:', result.transactionId, result.hash, result.status); +``` + +### Managing Channel Accounts + +For managing channel accounts, the SDK provides the following methods: + +```typescript +// List configured channel accounts +const accounts = await client.listChannelAccounts(); +console.log('Channel accounts:', accounts.relayerIds); + +// Update channel accounts (requires adminSecret) +const result = await client.setChannelAccounts(['channel-001', 'channel-002', 'channel-003']); +console.log('Update successful:', result.ok, result.appliedRelayerIds); +``` + +### Example Scripts + +For complete working examples and additional client usage details, see the [Channels Plugin README](https://github.com/OpenZeppelin/relayer-plugin-channels#plugin-client). + +## Prerequisites + +- Node.js >= 18 +- pnpm >= 10 +- OpenZeppelin Relayer (installed and configured) + +## Example Setup + +For a complete working example with Docker Compose, refer to the Channels plugin example in the OpenZeppelin Relayer repository: + +- **_Location_**: [examples/channels-plugin-example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example) +- **_Documentation_**: [README.md](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example/README.md) + +## Installation + +Channels can be added to any OpenZeppelin Relayer installation. + +**_Resources:_** + +- **_npm Package_**: [@openzeppelin/relayer-plugin-channels](https://www.npmjs.com/package/@openzeppelin/relayer-plugin-channels) +- **_GitHub Repository_**: https://github.com/OpenZeppelin/relayer-plugin-channels +- **_Example Setup_**: [channels-plugin-example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/channels-plugin-example) + +### Install from npm + +```bash +# From the root of your Relayer repository +cd plugins +pnpm add @openzeppelin/relayer-plugin-channels +``` + +### Create the plugin wrapper + +Inside your Relayer, create a directory for the plugin and expose its handler: + +```bash +mkdir -p plugins/channels +``` + +Create `plugins/channels/index.ts`: + +```typescript +export { handler } from '@openzeppelin/relayer-plugin-channels'; +``` + +## Configuration + +### Plugin Registration + +Register the plugin in your `config/config.json` file: + +```json +{ + "plugins": [ + { + "id": "channels-plugin", + "path": "channels/index.ts", + "timeout": 60 + } + ] +} +``` + +### Environment Variables + +Configure the required environment variables: + +```bash +# Required environment variables +export STELLAR_NETWORK="testnet" # or "mainnet" +export SOROBAN_RPC_URL="https://soroban-testnet.stellar.org" +export FUND_RELAYER_ID="channels-fund" # ID of the fund relayer +export PLUGIN_ADMIN_SECRET="your-secret-here" # Required for management API + +# Optional environment variables +export LOCK_TTL_SECONDS=30 # Lock timeout (default: 30, range: 3-30) +export MAX_FEE=1000000 # Maximum fee in stroops (default: 1,000,000) +``` + +**_Required Variables:_** + +- `STELLAR_NETWORK`: Either "testnet" or "mainnet" +- `SOROBAN_RPC_URL`: Stellar Soroban RPC endpoint URL +- `FUND_RELAYER_ID`: Relayer ID for the account that pays transaction fees + +**_Optional Variables:_** + +- `PLUGIN_ADMIN_SECRET`: Secret for accessing the management API (required to manage channel accounts) +- `LOCK_TTL_SECONDS`: TTL for channel account locks in seconds (default: 30, range: 3-30) +- `MAX_FEE`: Maximum transaction fee in stroops (default: 1,000,000) + +### Relayer Configuration + +Channels requires two types of relayers: + +1. **_Fund Account_**: The account that pays transaction fees (should have `concurrent_transactions: true` enabled) +2. **_Channel Accounts_**: At least one channel account (recommended: 2 or more for better throughput) + +Configure relayers in your `config/config.json`: + +```json +{ + "relayers": [ + { + "id": "channels-fund", + "name": "Channels Fund Account", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "channels-fund-signer", + "policies": { + "concurrent_transactions": true + } + }, + { + "id": "channel-001", + "name": "Channel Account 001", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "channel-001-signer" + }, + { + "id": "channel-002", + "name": "Channel Account 002", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "channel-002-signer" + } + ], + "notifications": [], + "signers": [ + { + "id": "channels-fund-signer", + "type": "local", + "config": { + "path": "config/keys/channels-fund.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE_FUND" + } + } + }, + { + "id": "channel-001-signer", + "type": "local", + "config": { + "path": "config/keys/channel-001.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE_CHANNEL_001" + } + } + }, + { + "id": "channel-002-signer", + "type": "local", + "config": { + "path": "config/keys/channel-002.json", + "passphrase": { + "type": "env", + "value": "KEYSTORE_PASSPHRASE_CHANNEL_002" + } + } + } + ], + "networks": "./config/networks", + "plugins": [ + { + "id": "channels", + "path": "channel/index.ts", + "timeout": 30, + "emit_logs": true, + "emit_traces": true + } + ] +} +``` + +**_Important Configuration Notes:_** + +- **_Fund Account_** (`channels-fund`): Must have `"concurrent_transactions": true` in policies to enable parallel transaction processing +- **_Channel Accounts_**: Create at least 2 for better throughput (you can add more as `channel-003`, etc.) +- **_Network_**: Use `testnet` for testing or `mainnet` for production +- **_Signers_**: Each relayer references a signer by `signer_id`, and signers are defined separately with keystore paths +- **_Keystore Files_**: You’ll need to create keystore files for each account - see [OpenZeppelin Relayer documentation](https://docs.openzeppelin.com/relayer) for details on creating and managing keys +- **_Plugin Registration_**: The plugin `id` should match what you use in environment variables and API calls + +After configuration, fund these accounts on-chain and register them with Channels (see "Initializing Channel Accounts" below). + +## Initializing Channel Accounts + +After configuring your relayers in `config.json` and funding the Stellar accounts, register them with Channels via the Management API: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "setChannelAccounts", + "adminSecret": "your-secret-here", + "relayerIds": ["channel-001", "channel-002"] + } + } + }' +``` + +**_Response:_** + +```json +{ + "success": true, + "data": { + "ok": true, + "appliedRelayerIds": ["channel-001", "channel-002"] + }, + "error": null +} +``` + +This tells Channels which relayers to use as channel accounts. All relayer IDs must match your configured relayer IDs in `config.json`. + +Channels is now ready to serve Soroban transactions. + +## Automated Setup + +To skip the manual configuration steps, use the provided automation script. It automates the entire setup process: creating signers and relayers via the API, funding accounts on-chain, and registering them with Channels. + +### Prerequisites + +When using the automated setup, you only need to configure and fund the **_fund account_**: + +```json +{ + "relayers": [ + { + "id": "channels-fund", + "chain": "stellar", + "signer": "channels-fund-signer", + "policies": { + "concurrent_transactions": true + } + } + ] +} +``` + +The script creates all channel account signers and relayers dynamically - no config.json entries needed for channel accounts. + +### Running the Script + +```bash +pnpm exec tsx ./scripts/create-channel-accounts.ts \ + --total 3 \ + --base-url http://localhost:8080 \ + --api-key \ + --funding-relayer channels-fund \ + --plugin-id channels-plugin \ + --plugin-admin-secret \ + --network testnet +``` + +### What the Script Does + +1. **_Creates channel account signers and relayers via API_**: Following the naming pattern `channel-0001`, `channel-0002`, etc. +2. **_Funds channel accounts on-chain_**: Submits funding transactions through the fund relayer and waits for confirmation +3. **_Registers with Channels_**: Automatically calls the Management API to register all channel accounts + +### Script Options + +- `--total`: Number of channel accounts to create (recommended: 2-3 for testing, more for production) +- `--fix`: Audit and heal partially created state (use if the script was interrupted) +- `--dry-run`: Preview actions without making changes +- `--prefix`: Customize the naming prefix (default: `channel-`) +- `--starting-balance`: XLM amount for each account (default: 5) + +### Script Location + +- Example directory: [`scripts/create-channel-accounts.ts`](https://github.com/OpenZeppelin/relayer-plugin-channels/blob/main/scripts/create-channel-accounts.ts) + +## API Usage + +Channels is invoked by making POST requests to the plugin endpoint: + +```bash +POST /api/v1/plugins/{plugin-id}/call +``` + +### Submitting Transactions + +There are two ways to submit transactions to Channels: + +#### Option 1: Complete Transaction XDR + +Submit a complete, signed transaction envelope: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "xdr": "AAAAAgAAAAA..." + } + }' +``` + +#### Option 2: Soroban Function + Auth + +Submit just the Soroban function and authorization entries: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "func": "AAAABAAAAAEAAAAGc3ltYm9s...", + "auth": ["AAAACAAAAAEAAAA..."] + } + }' +``` + +### Parameters + +- `xdr` (string): Complete transaction envelope XDR (base64) - must be signed, not a fee-bump envelope +- `func` (string): Soroban host function XDR (base64) +- `auth` (array of strings): Array of Soroban authorization entry XDRs (base64) + +**_Important Notes:_** + +- Provide either `xdr` OR `func`+`auth`, not both +- When using `xdr`, the transaction must be a regular signed transaction (not a fee-bump envelope) +- When using `func`+`auth`, Channels will build and simulate the transaction automatically +- Transactions are always submitted with fee bumping from the fund account + +### Generating XDR with Stellar SDK + +Use the `@stellar/stellar-sdk` to generate the required XDR values: + +#### Full Transaction Envelope XDR + +```typescript +import { Networks, TransactionBuilder, rpc } from '@stellar/stellar-sdk'; + +// Build your transaction +const tx = new TransactionBuilder(account, { + fee: '100', + networkPassphrase: Networks.TESTNET, +}) + .addOperation(/* Operation.invokeHostFunction from Contract.call(...) */) + .setTimeout(30) + .build(); + +// Sign the transaction +tx.sign(keypair); + +// Export base64 envelope XDR +const envelopeXdr = tx.toXDR(); +``` + +#### Soroban Function + Auth XDRs + +```typescript +// Build and simulate first to obtain auth +const baseTx = /* TransactionBuilder(...).addOperation(...).build() */; +const sim = await rpcServer.simulateTransaction(baseTx); + +// Apply simulation, then extract from the InvokeHostFunction operation +const assembled = rpc.assembleTransaction(baseTx, sim).build(); +const op = assembled.operations[0]; // Operation.InvokeHostFunction + +const funcXdr = op.func.toXDR("base64"); +const authXdrs = (op.auth ?? []).map(a => a.toXDR("base64")); +``` + +## Management API + +Channels provides a management API to dynamically configure channel accounts. This API requires authentication via the `PLUGIN_ADMIN_SECRET` environment variable. + +### List Channel Accounts + +Get the current list of configured channel accounts: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "listChannelAccounts", + "adminSecret": "your-secret-here" + } + } + }' +``` + +**_Response:_** + +```json +{ + "success": true, + "data": { + "relayerIds": ["channel-001", "channel-002"] + }, + "error": null +} +``` + +### Set Channel Accounts + +Configure the channel accounts that Channels will use. This replaces the entire list: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/channels-plugin/call \ + -H "Authorization: Bearer YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "params": { + "management": { + "action": "setChannelAccounts", + "adminSecret": "your-secret-here", + "relayerIds": ["channel-001", "channel-002", "channel-003"] + } + } + }' +``` + +**_Response:_** + +```json +{ + "success": true, + "data": { + "ok": true, + "appliedRelayerIds": ["channel-001", "channel-002", "channel-003"] + }, + "error": null +} +``` + +**_Important Notes:_** + +- You must configure at least one channel account before Channels can process transactions +- The management API will prevent removing accounts that are currently locked (in use). On failure it returns HTTP 409 with code `LOCKED_CONFLICT` and `details.locked` listing the blocked IDs +- All relayer IDs must exist in your OpenZeppelin Relayer configuration +- The `adminSecret` must match the `PLUGIN_ADMIN_SECRET` environment variable + +## Responses + +All API responses use the standard Relayer envelope format: `success, data, error, metadata`. + +### Success Response (HTTP 200) + +```json +{ + "success": true, + "data": { + "transactionId": "tx_123456", + "status": "confirmed", + "hash": "1234567890abcdef..." + }, + "error": null +} +``` + +**_Response Fields:_** + +- `success`: `true` when the plugin executed successfully +- `data`: Contains the transaction result +- `transactionId`: The OpenZeppelin Relayer transaction ID +- `status`: Transaction status (e.g., "confirmed") +- `hash`: The Stellar transaction hash +- `error`: `null` on success + +### Error Response (HTTP 4xx) + +```json +{ + "success": false, + "data": { + "code": "POOL_CAPACITY", + "details": {} + }, + "error": "Too many transactions queued. Please try again later", + "metadata": { + "logs": [{ "level": "error", "message": "All channel accounts in use" }] + } +} +``` + +**_Error Response Fields:_** + +- `success`: `false` when the plugin encountered an error +- `data`: Contains error details +- `code`: Error code (e.g., "POOL_CAPACITY", "LOCKED_CONFLICT") +- `details`: Additional context about the error +- `error`: Human-readable error message +- `metadata.logs`: Plugin execution logs (if `emit_logs` is enabled) + +### Common Error Codes + +- `CONFIG_MISSING`: Missing required environment variable +- `UNSUPPORTED_NETWORK`: Invalid network type +- `INVALID_PARAMS`: Invalid request parameters +- `INVALID_XDR`: Failed to parse XDR +- `INVALID_ENVELOPE_TYPE`: Not a regular transaction envelope (e.g., fee bump) +- `INVALID_TIME_BOUNDS`: TimeBounds too far in the future +- `NO_CHANNELS_CONFIGURED`: No channel accounts have been configured via management API +- `POOL_CAPACITY`: All channel accounts in use +- `RELAYER_UNAVAILABLE`: Relayer not found +- `SIMULATION_FAILED`: Transaction simulation failed +- `ONCHAIN_FAILED`: Transaction failed on-chain +- `WAIT_TIMEOUT`: Transaction wait timeout +- `MANAGEMENT_DISABLED`: Management API not enabled +- `UNAUTHORIZED`: Invalid admin secret +- `LOCKED_CONFLICT`: Cannot remove locked channel accounts + +## Additional Resources + +- **_Stellar SDK Documentation_**: https://stellar.github.io/js-stellar-sdk/ +- **_Soroban Documentation_**: https://soroban.stellar.org/docs +- **_OpenZeppelin Relayer Documentation_**: https://docs.openzeppelin.com/relayer diff --git a/docs/content/relayer/plugins/index.mdx b/docs/content/relayer/plugins/index.mdx new file mode 100644 index 00000000..f8f8b2c2 --- /dev/null +++ b/docs/content/relayer/plugins/index.mdx @@ -0,0 +1,476 @@ +--- +title: Plugins +--- + +## Overview + +OpenZeppelin Relayer supports plugins to extend the functionality of the relayer. + +Plugins are `TypeScript` functions running in the Relayer server that can include any arbitrary logic defined by the Relayer operator. + +The plugin system features: +- ***Handler Pattern***: Simple export-based plugin development +- ***TypeScript Support***: Full type safety and IntelliSense +- ***Plugin API***: Clean interface for interacting with relayers +- ***Key-Value Storage***: Persistent state and locking for plugins +- ***Docker Integration***: Seamless development and deployment +- ***Comprehensive Error Handling***: Detailed logging and debugging capabilities + +## Configuration + +### Writing a Plugin + +Plugins are declared under `plugins` directory, and are expected to be TypeScript files (`.ts` extension). + +```bash +openzeppelin-relayer/ +├── plugins/ +│ └── my-plugin.ts # Plugin code +└── config/ + └── config.json # Plugins in configuration file +``` + +#### Handler Pattern (Recommended) + +This approach uses a simple `handler` export pattern with a single context parameter: + +```typescript +/// Required imports. +import { Speed, PluginContext, pluginError } from "@openzeppelin/relayer-sdk"; + +/// Define your plugin parameters interface +type MyPluginParams = { + destinationAddress: string; + amount?: number; + message?: string; + relayerId?: string; +} + +/// Define your plugin return type +type MyPluginResult = { + transactionId: string; + confirmed: boolean; + note?: string; +} + +/// Export a handler function - that's it! +export async function handler(context: PluginContext): Promise { + const { api, params, kv } = context; + console.info("🚀 Plugin started..."); + + // Validate parameters + if (!params.destinationAddress) { + throw pluginError("destinationAddress is required", { code: 'MISSING_PARAM', status: 400, details: { field: 'destinationAddress' } }); + } + + // Use the relayer API + const relayer = api.useRelayer(params.relayerId || "my-relayer"); + + const result = await relayer.sendTransaction({ + to: params.destinationAddress, + value: params.amount || 1, + data: "0x", + gas_limit: 21000, + speed: Speed.FAST, + }); + + console.info(`Transaction submitted: ${result.id}`); + + // Optionally store something in KV + await kv.set("last_tx_id", result.id); + + // Wait for confirmation + await result.wait({ + interval: 5000, // Check every 5 seconds + timeout: 120000 // Timeout after 2 minutes + }); + + return { + transactionId: result.id, + message: `Successfully sent ${params.amount || 1} wei to ${params.destinationAddress}` + }; +} +``` + +#### Legacy Patterns (Deprecated, but supported) + + +The legacy patterns below are deprecated and will be removed in a future version. Please migrate to the single-context handler pattern. Legacy plugins continue to work but will show deprecation warnings. The two-parameter handler does not have access to the KV store. + + +```typescript +// Legacy: runPlugin pattern (deprecated) +import { runPlugin, PluginAPI } from "../lib/plugin"; + +async function myPlugin(api: PluginAPI, params: any) { + // Plugin logic here (no KV access) + return "result"; +} + +runPlugin(myPlugin); +``` + +***Legacy handler (two-parameter, deprecated, no KV)***: +```typescript +import { PluginAPI } from "@openzeppelin/relayer-sdk"; + +export async function handler(api: PluginAPI, params: any): Promise { + // Same logic as before, but no KV access in this form + return "done!"; +} +``` + +### Declaring in config file + +Plugins are configured in the `./config/config.json` file, under the `plugins` key. + +The file contains a list of plugins, each with an id, path and timeout in seconds (optional). + + +The plugin path is relative to the `/plugins` directory + + +Example: + +```json + +"plugins": [ + { + "id": "my-plugin", + "path": "my-plugin.ts", + "timeout": 30 + } +] +``` + +### Timeout + +The timeout is the maximum time **in seconds** that the plugin can run. If the plugin exceeds the timeout, it will be terminated with an error. + +The timeout is optional, and if not provided, the default is 300 seconds (5 minutes). + +## Plugin Development Guidelines + +### TypeScript Best Practices + +* ***Define Parameter Types***: Always create interfaces or types for your plugin parameters +* ***Define Return Types***: Specify what your plugin returns for better developer experience +* ***Handle Errors Gracefully***: Use try-catch blocks and return structured error responses +* ***Validate Input***: Check required parameters and provide meaningful error messages +* ***Use Async/Await***: Modern async patterns for better readability + +### Testing Your Plugin + +You can test your handler function directly with a mocked context: + +```typescript +import { handler } from './my-plugin'; +import type { PluginContext } from '@openzeppelin/relayer-sdk'; + +const mockContext = { + api: { + useRelayer: (_id: string) => ({ + sendTransaction: async () => ({ id: 'test-tx-123', wait: async () => ({ hash: '0xhash' }) }) + }) + }, + params: { + destinationAddress: '0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A', + amount: 1000 + }, + kv: { + set: async () => true, + get: async () => null, + del: async () => true, + exists: async () => false, + scan: async () => [], + clear: async () => 0, + withLock: async (_k: string, fn: () => Promise) => fn(), + connect: async () => {}, + disconnect: async () => {} + } +} as unknown as PluginContext; + +const result = await handler(mockContext); +console.log(result); +``` + +## Invocation + +Plugins are invoked by hitting the `api/v1/plugins/plugin-id/call` endpoint. + +The endpoint accepts a `POST` request. Example post request body: + +```json +{ + "params": { + "destinationAddress": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", + "amount": 1000000000000000, + "message": "Hello from OpenZeppelin Relayer!" + } +} +``` + +The parameters are passed directly to your plugin’s `handler` function. + +## Responses + +API responses use the `ApiResponse` envelope: ` success, data, error, metadata `. + +### Success responses (HTTP 200) + +* `data` contains your handler return value (decoded from JSON when possible). +* `metadata.logs?` and `metadata.traces?` are only populated if the plugin configuration enables `emit_logs` / `emit_traces`. +* `error` is `null`. + +### Plugin errors (HTTP 4xx) + +* Throwing `pluginError(...)` (or any `Error`) is normalized into a consistent HTTP payload. +* `error` provides the client-facing message, derived from the thrown error or from log output when the message is empty. +* `data` carries ` code?: string, details?: any ` reported by the plugin. +* `metadata` follows the same visibility rules (`emit_logs` / `emit_traces`). + +### Complete Example + +1. ***Plugin Code*** (`plugins/example.ts`): + +```typescript +import { Speed, PluginContext, pluginError } from "@openzeppelin/relayer-sdk"; + +type ExampleResult = { + transactionId: string; + transactionHash: string | null; + message: string; + timestamp: string; +} + +export async function handler(context: PluginContext): Promise { + const { api, params, kv } = context; + console.info("🚀 Example plugin started"); + console.info(`📋 Parameters:`, JSON.stringify(params, null, 2)); + + if (!params.destinationAddress) { + throw pluginError("destinationAddress is required", { code: 'MISSING_PARAM', status: 400, details: { field: 'destinationAddress' } }); + } + + const amount = params.amount || 1; + const message = params.message || "Hello from OpenZeppelin Relayer!"; + + console.info(`💰 Sending ${amount} wei to ${params.destinationAddress}`); + + const relayer = api.useRelayer("my-relayer"); + const result = await relayer.sendTransaction({ + to: params.destinationAddress, + value: amount, + data: "0x", + gas_limit: 21000, + speed: Speed.FAST, + }); + + // Example persistence + await kv.set('last_transaction', result.id); + + const confirmation = await result.wait({ interval: 5000, timeout: 120000 }); + + return { + transactionId: result.id, + transactionHash: confirmation.hash || null, + message: `Successfully sent ${amount} wei to ${params.destinationAddress}. ${message}`, + timestamp: new Date().toISOString(), + }; +} +``` + +1. ***Plugin Configuration*** (`config/config.json`): + +```json +{ + "plugins": [ + { + "id": "example-plugin", + "path": "example-plugin.ts", + "timeout": 30 + } + ] +} +``` + +1. ***API Invocation***: + +```bash +curl -X POST http://localhost:8080/api/v1/plugins/example-plugin/call \ +-H "Content-Type: application/json" \ +-H "Authorization: Bearer YOUR_API_KEY" \ +-d '{ + "params": { + "destinationAddress": "0x742d35Cc6640C21a1c7656d2c9C8F6bF5e7c3F8A", + "amount": 1000000000000000, + "message": "Test transaction from plugin" + } +}' +``` + +1. ***API Response (Success)***: + +```json +{ + "success": true, + "data": { + "transactionId": "tx-123456", + "confirmed": true, + "note": "Sent 1000000000000000 wei to 0x742d35Cc..." + }, + "metadata": { + "logs": [ { "level": "info", "message": "🚀 Example plugin started" } ], + "traces": [ { "relayerId": "my-relayer", "method": "sendTransaction", "payload": { /* ... */ } } ] + }, + "error": null +} + +``` + +2. **API Response (Error)**: + +```json +{ + "success": false, + "data": + { + "code": "MISSING_PARAM", + "details": { "field": "destinationAddress" } + }, + "metadata": { + "logs": [ { "level": "error", "message": "destinationAddress is required" } ] + }, + "error": "destinationAddress is required" +} + +``` + +== Response Fields + +- **`data`**: The value returned by your plugin's handler function (decoded from JSON when possible) +- **`metadata.logs`**: Terminal output from the plugin (console.log, console.error, etc.) when `emit_logs` is true +- **`metadata.traces`**: Messages exchanged between the plugin and the Relayer via PluginAPI when `emit_traces` is true +- **`error`**: Error message if the plugin execution failed (business errors) + +== Key-Value Storage + +The Relayer provides a built-in key-value store for plugins to maintain persistent state across invocations. This addresses the core problem of enabling persistent state management and programmatic configuration updates for plugins. + +=== Why a KV store? + +- Plugins execute as isolated processes with no persistent memory +- No mechanism exists to maintain state between invocations +- Plugins requiring shared state or coordination need safe concurrency primitives + +=== Configuration + +- Reuses the same Redis URL as the Relayer via the `REDIS_URL` environment variable +- No extra configuration is required +- Keys are namespaced per plugin ID to prevent collisions + +=== Usage + +Access the KV store through the `kv` property in the `PluginContext`: + +[source,typescript] +``` +export async function handler(context: PluginContext) { + const { kv } = context; + // Set a value (with optional TTL in seconds) + await kv.set('my-key', { data: 'value' }, { ttlSec: 3600 }); + // Get a value + const value = await kv.get<{ data: string }>('my-key'); + // Atomic update with lock + const updated = await kv.withLock('counter-lock', async () => { + const count = (await kv.get('counter')) ?? 0; + const next = count + 1; + await kv.set('counter', next); + return next; + }, { ttlSec: 10 }); + + return { value, updated }; +} + +``` + +=== Available Methods + +- `get(key: string): Promise` +- `set(key: string, value: unknown, opts?: ttlSec?: number ): Promise` +- `del(key: string): Promise` +- `exists(key: string): Promise` +- `listKeys(pattern?: string, batch?: number): Promise` +- `clear(): Promise` +- `withLock(key: string, fn: () => Promise, opts?: ttlSec?: number; onBusy?: 'throw' | 'skip' ): Promise` + +Keys must match `[A-Za-z0-9:_-]1,512` and are automatically namespaced per plugin. + +== Migration from Legacy Patterns + +=== Current Status + +- ✅ **Legacy plugins still work** - No immediate action required +- ⚠️ **Deprecation warnings** - Legacy plugins will show console warnings +- 📅 **Future removal** - The legacy `runPlugin` and two-parameter `handler(api, params)` will be removed in a future major version +- 🎯 **Recommended action** - Migrate to single-parameter `PluginContext` handler for new plugins and KV access + +=== Migration Steps + +If you have existing plugins using `runPlugin()` or the two-parameter handler, migration is simple: + +**Before (Legacy runPlugin - still works)**: +[source,typescript] +``` +import runPlugin, PluginAPI from "./lib/plugin"; + +async function myPlugin(api: PluginAPI, params: any): Promise + // Your plugin logic + return result; + + +runPlugin(myPlugin); // ⚠️ Shows deprecation warning +``` + +**Intermediate (Legacy two-parameter - still works, no KV)**: +[source,typescript] +``` +import PluginAPI from "@openzeppelin/relayer-sdk"; + +export async function handler(api: PluginAPI, params: any): Promise + // Same plugin logic - ⚠️ Deprecated, no KV access + return result; + +``` + +**After (Modern context - recommended, with KV)**: +[source,typescript] +``` +import PluginContext from "@openzeppelin/relayer-sdk"; + +export async function handler(context: PluginContext): Promise + const api, params, kv = context; + // Same plugin logic plus KV access! + return result; + +``` + +=== Step-by-Step Migration + +1. **Remove the `runPlugin()` call** at the bottom of your file +2. **Rename your function to `handler`** (or create a new handler export) +3. **Export the `handler` function** using `export async function handler` +4. **Add proper TypeScript types** for better development experience +5. **Test your plugin** to ensure it works with the new pattern +6. **Update your documentation** to reflect the new pattern + +=== Backwards Compatibility + +The relayer will automatically detect which pattern your plugin uses: + +- If handler accepts one parameter → modern context pattern (with KV) +- If handler accepts two parameters → legacy pattern (no KV, with warning) +- If `runPlugin()` was called → legacy pattern (no KV, with warning) +- If neither → shows clear error message + +This ensures a smooth transition period where both patterns work simultaneously. diff --git a/docs/content/relayer/quickstart.mdx b/docs/content/relayer/quickstart.mdx new file mode 100644 index 00000000..d74ef682 --- /dev/null +++ b/docs/content/relayer/quickstart.mdx @@ -0,0 +1,167 @@ +--- +title: Quick Start Guide +--- + +This guide provides step-by-step instructions for setting up OpenZeppelin Relayer. It includes prerequisites, installation, and configuration examples. + +## Prerequisites + +* Rust 2021, version `1.86` or later. +* Redis +* Docker (optional, for containerized deployment) +* Node.js, typescript and ts-node (optional, for plugins) + +## Configuration + +### Step 1: Clone the Repository + +Clone the repository and navigate to the project directory: + +```bash +git clone https://github.com/OpenZeppelin/openzeppelin-relayer +cd openzeppelin-relayer +``` + +### Step 2: Create Configuration Files + +Create environment configuration: + +```bash +cp .env.example .env +``` + +These files are already partially configured. We will add missing data in next steps. + +Ready-to-Use Example Configurations + +For quick setup with various configurations, check the [examples directory](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples) in our GitHub repository: + +### Step 3: Create a Signer + +Generate a new signer keystore for the basic example: + +```bash +cargo run --example create_key -- \ + --password \ + --output-dir examples/basic-example/config/keys \ + --filename local-signer.json +``` +Replace `` with a strong password. + + + + +Your password must contain at least: + +* 12 characters +* One uppercase letter +* One lowercase letter +* One number +* One special character + + + +Next, update the `KEYSTORE_PASSPHRASE` in `.env` with the password you used above. + +### Step 4: Configure Notifications + +#### Configure Webhook URL + +Edit the file `config/config.json` and update the `notifications[0].url` field with your webhook URL. For a quick test, you can use a temporary URL from [Webhook.site](https://webhook.site). + +#### Configure Webhook Signing Key + +Generate a webhook signing key: + +```bash +cargo run --example generate_uuid +``` + + + + +Alternatively, you can use any online UUID generator tool if you don’t want to run the included command. + + + +Copy the generated UUID and update the `WEBHOOK_SIGNING_KEY` entry in `.env`. + +### Step 5: Configure API Key + +Generate an API key signing key for development: + +```bash +cargo run --example generate_uuid +``` + + + + +You can also use UUID generator with a simple command on your terminal. + +```bash +uuidgen +``` + +Alternatively, you can use any online UUID generator tool. + + + +Copy the generated UUID and update the `API_KEY` entry in `.env`. + +### Step 6: Run the Service + +#### Local + +Run Redis container: + +```sh +docker run --name openzeppelin-redis \ + -p 6379:6379 \ + -d redis:latest +``` + +Run Relayer service: + +```bash +cargo run +``` + +#### Docker + +Building and Running the docker image: + +```bash +docker compose up -d +``` + +By default docker compose command uses `Dockerfile.development` to build the image. If you want to use `Dockerfile.production`, you can use the following command: + +```bash +DOCKERFILE=Dockerfile.production docker compose up -d +``` + +### Step 7: Test the Relayer + +Verify the service by sending a GET request: + +```bash +curl -X GET http://localhost:8080/api/v1/relayers \ + -H "Content-Type: application/json" \ + -H "AUTHORIZATION: Bearer YOUR_API_KEY" +``` +Replace `YOUR_API_KEY` with the API key you configured in your `.env` file. + +Expected Result: A successful request should return an HTTP 200 status code along with the list of relayers. + +## Using the relayer through the API + +For detailed API usage, refer to the [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) page. + +## Using the relayer through the SDK + +For documentation and examples on how to consume Relayer service via SDK check [SDK documentation](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk). + +## Additional Resources and Troubleshooting + +Troubleshooting: If you encounter issues during setup or deployment, verify your environment variables, check container logs, and review your configuration files for syntax errors. diff --git a/docs/content/relayer/roadmap.mdx b/docs/content/relayer/roadmap.mdx new file mode 100644 index 00000000..0fbfbfbf --- /dev/null +++ b/docs/content/relayer/roadmap.mdx @@ -0,0 +1,113 @@ +--- +title: OpenZeppelin Relayer Roadmap +--- + +This document outlines the planned development roadmap for the OpenZeppelin Relayer project. Please note that priorities and timelines may shift based on community feedback, security considerations, and emerging blockchain ecosystem needs. + + + + +This roadmap represents our current plans and is subject to change. We will update this document regularly to reflect our progress and any changes in direction. + + + +## General Roadmap + +* **Stability Improvements** + * Enhanced error handling and recovery mechanisms: Implement robust exception management and failover processes to minimize downtime. + * Improved background job processing: Optimize task scheduling and queuing systems to ensure smooth and reliable asynchronous operations. + * Comprehensive test coverage: Extend unit, integration, and regression tests across all components. + * End-to-End (E2E) testing: Simulate real-world scenarios to verify complete system functionality. + * Performance optimizations: Enhance throughput and reduce latency for high-demand scenarios. + * Stress and load testing: Identify and address performance bottlenecks under extreme conditions. +* **Security Enhancements** + * External security audit: Engage third-party experts to identify and remediate vulnerabilities. + * Continuous security monitoring: Implement ongoing surveillance and incident response protocols to swiftly address threats. +* **Developer Experience** + * SDK improvements: Expand SDK capabilities, add multi-language support, and simplify integration processes. + * Enhanced documentation: Develop interactive guides, detailed API references, and comprehensive troubleshooting tips. + * Additional examples and best practices: Provide real-world usage scenarios and community-contributed tutorials to ease onboarding. +* **Features** + * Redis storage integration: Leverage Redis for fast, scalable data storage across all system components. + * Enhanced relayer balance management: Implement real-time monitoring and alerts to maintain optimal balance status. + * Dynamic gas price updates: Regularly fetch and update gas prices from multiple reliable sources to ensure accurate fee estimations. +* **Scaling Improvements** + * Horizontal scaling capabilities: Design the system architecture to seamlessly distribute workloads across multiple instances. + +## Network-Specific Roadmap + +### EVM Networks (🏗️ In Progress) + +#### Current Status +* Basic Transaction Submission +* Fee Estimation +* Transaction Status Tracking +* Flexible Network Configuration System (any EVM-compatible network via JSON configuration) +* Hosted signers support (AWS KMS, GCP, Turnkey) +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* L2 improvements +* SDK client improvements +* Full CRUD API support + +### Solana (🏗️ In Progress) + +#### Current Status +* Solana Paymaster Specification Support +* Fee estimation +* Gasless transactions +* Hosted Signer Integrations (Vault, GCP, Turnkey) +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* Extended RPC Methods +* Improved Transaction Status Checks +* Full CRUD API support + +### Stellar (🏗️ In Progress) + +#### Current Status +* Supports payment and InvokeHostFunction operations, pre-built XDR transactions, and fee bump transactions, +* Advanced transaction status logic +* Stellar-specific endpoints +* Expanded signer support +* Transaction lifecycle management logic +* Custom RPC Endpoints +* RPC Retries and Failover Mechanisms + +#### Upcoming Features +* Relayer security policies: Transaction amount limits, destination whitelisting, time bound and limit operations +* Hosted signers +* Full CRUD API support + +## Community and Documentation + +### Continuous +* **Documentation** + * Comprehensive API reference + * Tutorials and guides + * Integration examples +* **Community Engagement** + * Contributing guidelines + * Support for community-driven improvements + +## Notes on Prioritization + + + + +Our development priorities are influenced by several factors: + +1. **Security**: Security enhancements always take precedence +2. **Stability**: Ensuring reliable operation across all supported networks +3. **Community Feedback**: Features requested by the community +4. **Ecosystem Developments**: Adapting to changes in blockchain protocols + + + +This roadmap is a living document and will be updated regularly to reflect changing priorities and completed milestones. We welcome community input on our direction and priorities. + +To contribute to discussions about the roadmap, please join our community channels or open an issue on our GitHub repository with your suggestions. diff --git a/docs/content/relayer/solana.mdx b/docs/content/relayer/solana.mdx new file mode 100644 index 00000000..0ca27f94 --- /dev/null +++ b/docs/content/relayer/solana.mdx @@ -0,0 +1,229 @@ +--- +title: Solana Integration +--- + +## Overview + +OpenZeppelin Relayer provides robust support for Solana networks, enabling secure transaction relaying, automated token swaps, gasless transactions, and advanced fee management. This page covers everything you need to get started and make the most of Solana-specific features. + +## Features + +* Automated token swaps via Jupiter DEX (mainnet-beta only) +* Gasless transactions (user or relayer pays fees) +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and nonce management +* Custom RPC endpoints and network policies +* Metrics and observability + +## Supported Networks + +Solana networks are defined via JSON configuration files, providing flexibility to: + +* Configure standard Solana clusters: `mainnet-beta`, `devnet`, `testnet` +* Set up custom Solana-compatible networks with specific RPC endpoints +* Create network variants using inheritance from base configurations + +Example Solana network configurations: + +```json +{ + "networks": [ + { + "type": "solana", + "network": "solana-mainnet", + "rpc_urls": ["https://api.mainnet-beta.solana.com"], + "explorer_urls": ["https://explorer.solana.com"], + "is_testnet": false, + "tags": ["mainnet", "solana"] + }, + { + "type": "solana", + "network": "solana-devnet", + "rpc_urls": ["https://api.devnet.solana.com"], + "explorer_urls": ["https://explorer.solana.com?cluster=devnet"], + "is_testnet": true, + "tags": ["devnet", "solana"] + }, + { + "type": "solana", + "network": "solana-custom", + "rpc_urls": ["https://your-custom-solana-rpc.example.com"], + "tags": ["custom", "solana"] + } + ] +} +``` + +For detailed network configuration options, see the [Network Configuration](/relayer/network_configuration) guide. + +## Supported Signers + +* `vault_transit` (hosted) +* `turnkey` (hosted) +* `google_cloud_kms` (hosted) +* `local` (local) +* `vault` (local) +* `cdp` (hosted) + + + + +In production systems, hosted signers are recommended for the best security model. + + + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/quickstart). +Key prerequisites: + +* Rust 2021, version `1.86` or later +* Redis +* Docker (optional) + +Example configuration for a Solana relayer: +```json +{ + "id": "solana-example", + "name": "Solana Example", + "network": "devnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "solana", + "custom_rpc_urls": [ + { "url": "https://primary-solana-rpc.example.com", "weight": 100 }, + { "url": "https://backup-solana-rpc.example.com", "weight": 100 } + ], + "policies": { + "fee_payment_strategy": "user", + "min_balance": 0, + "allowed_tokens": [ + { "mint": "So111...", "max_allowed_fee": 100000000 } + ], + "swap_config": { + "strategy": "jupiter-swap", + "cron_schedule": "0 0 * * * *", + "min_balance_threshold": 1000000, + "jupiter_swap_options": { + "dynamic_compute_unit_limit": true, + "priority_level": "high", + "priority_fee_max_lamports": 1000000000 + } + } + } +} +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository, window=_blank](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +In addition to standard relayer configuration and policies, Solana relayers support additional options: + +* `fee_payment_strategy`: `"user"` or `"relayer"` (who pays transaction fees). "user" is default value. + * `"user"`: Users pay transaction fees in tokens (relayer receives fee payment from user) + * `"relayer"`: ***Relayer pays for all transaction fees*** using SOL from the relayer’s account +* `allowed_tokens`: List of SPL tokens supported for swaps and fee payments. Restrict relayer operations to specific tokens. Optional. + * ***When not set or empty, all tokens are allowed*** for transactions and fee payments + * When configured, only tokens in this list can be used for transfers and fee payments +* `allowed_programs`, `allowed_accounts`, `disallowed_accounts`: Restrict relayer operations to specific programs/accounts +* `swap_config`: Automated token swap settings (see below) + +You can check all options in [User Documentation - Relayers](/relayer#3_relayers). + +### Automated token swap configuration options: + +* `strategy`: The swap engine to use. Supported values: `"jupiter-swap"` (Jupiter Swap API), `"jupiter-ultra"` (Jupiter Ultra API). +* `cron_schedule`: Cron expression defining how often scheduled swaps should run (e.g., `"0 0 * * * *"` for every hour). +* `min_balance_threshold`: Minimum token balance (in lamports) that triggers a swap. If the relayer’s balance drops below this, a swap is attempted. +* `jupiter_swap_options`: Advanced options for Jupiter swaps, such as: + * `dynamic_compute_unit_limit`: If `true`, dynamically adjusts compute units for swap transactions. + * `priority_level`: Priority for the swap transaction. Supported values: `"medium"`, `"high"`, `"veryHigh"`. + * `priority_fee_max_lamports`: Maximum priority fee (in lamports) to pay for a swap transaction. +* Per-token swap limits: + * `min_amount`: Minimum amount of a token to swap in a single operation. + * `max_amount`: Maximum amount of a token to swap in a single operation. + * `retain_min_amount`: Minimum amount of a token to retain in the relayer account after a swap (prevents swapping the entire balance). + +## Automated Token Swaps + +The relayer can perform automated token swaps on Solana when user fee_payment_strategy is used for relayer using: + +* ***jupiter-swap*** – via the Jupiter Swap API +* ***jupiter-ultra*** – via the Jupiter Ultra API + +Swaps can be set to work as: + +* ***Scheduled Swaps***: Background jobs run swaps based on your cron schedule. +* ***On-Demand Swaps***: If a transaction fails due to insufficient funds, the relayer attempts a swap before returning an error. + +## API Reference + +The Solana API conforms to the [Paymaster spec, window=_blank](https://docs.google.com/document/d/1lweO5WH12QJaSAu5RG_wUistyk_nFeT6gy1CdvyCEHg/edit?tab=t.0#heading=h.4yldgprkuvav). + +Common endpoints: +- `POST /api/v1/relayers//rpc` + Methods: + +* `feeEstimate`, +* `prepareTransaction`, +* `transferTransaction`, +* `signTransaction`, +* `signAndSendTransaction`, +* `getSupportedTokens` +* `getSupportedFeatures` + + + + +***Fee Token Parameter Behavior:*** + +When using `fee_payment_strategy: "relayer"`, the `fee_token` parameter in RPC methods becomes ***informational only***. The relayer pays all transaction fees in SOL regardless of the specified fee token. In this mode, you can use either `"So11111111111111111111111111111112"` (WSOL) or `"11111111111111111111111111111111"` (native SOL) as the fee_token value. + +When using `fee_payment_strategy: "user"`, the `fee_token` parameter determines which token the user will pay fees in, and must be a supported token from the `allowed_tokens` list (if configured). + + + +Example: Estimate fee for a transaction +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers/solana-example/rpc' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "jsonrpc": "2.0", + "method": "feeEstimate", + "params": { + "transaction": "", + "fee_token": "" + }, + "id": 1 +}' +``` + +See [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) and [SDK examples, window=_blank](https://github.com/OpenZeppelin/openzeppelin-relayer-sdk/tree/main/examples/solana) for full details and examples. + +## Security + +* Do not expose the relayer directly to the public internet. +* Deploy behind a secure backend (reverse proxy, firewall). +* Use hosted signers in production systems. + +## Troubleshooting + +* Check environment variables and configuration files for errors +* Review container logs for issues + +## Roadmap + +* See [Project Roadmap](/relayer/roadmap) for upcoming features + +## Support + +For help, join our [Telegram](https://t.me/openzeppelin_tg/2) or open an issue on GitHub. + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/stellar.mdx b/docs/content/relayer/stellar.mdx new file mode 100644 index 00000000..e9f01bf8 --- /dev/null +++ b/docs/content/relayer/stellar.mdx @@ -0,0 +1,420 @@ +--- +title: Stellar Integration +--- + +## Overview + +OpenZeppelin Relayer provides comprehensive support for Stellar networks, enabling secure transaction relaying, Soroban smart contract operations, and advanced transaction management. This integration supports both standard Stellar operations and the latest Soroban smart contract functionality. + + +Stellar support is currently under active development. The API interactions and specifics described below may evolve. + + +## Features + +* Full Soroban smart contract support (invocation, deployment, WASM uploads) +* Standard Stellar payment operations +* Support for all Stellar operations via XDR transaction submission +* Fee bump transaction support +* Secure transaction signing with multiple signer backends +* Transaction status monitoring and sequence number management +* Custom RPC endpoints and network policies +* Metrics and observability + +## Supported Networks + +Stellar networks are defined via JSON configuration files, providing flexibility to: + +* Configure standard Stellar clusters: `mainnet`, `testnet` +* Set up custom Stellar-compatible networks with specific RPC endpoints +* Define network passphrases for proper transaction signing + +Example Stellar network configurations: + +```json +{ + "networks": [ + { + "type": "stellar", + "network": "mainnet", + "rpc_urls": ["https://mainnet.sorobanrpc.com"], + "explorer_urls": ["https://stellar.expert/explorer/public"], + "average_blocktime_ms": 5000, + "is_testnet": false, + "passphrase": "Public Global Stellar Network ; September 2015" + }, + { + "type": "stellar", + "network": "testnet", + "rpc_urls": ["https://soroban-testnet.stellar.org"], + "explorer_urls": ["https://stellar.expert/explorer/testnet"], + "average_blocktime_ms": 5000, + "is_testnet": true, + "passphrase": "Test SDF Network ; September 2015" + } + ] +} +``` + +For detailed network configuration options, see the [Network Configuration](/relayer/network_configuration) guide. + +## Quickstart + +For a step-by-step setup, see [Quick Start Guide](/relayer/quickstart). +Key prerequisites: + +* Rust 2021, version `1.86` or later +* Redis +* Docker (optional) + +Example configuration for a Stellar relayer: +```json +{ + "id": "stellar-example", + "name": "Stellar Example", + "network": "testnet", + "paused": false, + "notification_id": "notification-example", + "signer_id": "local-signer", + "network_type": "stellar" +} +``` + +For more configuration examples, visit the [OpenZeppelin Relayer examples repository, window=_blank](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples). + +## Configuration + +### Relayer Policies + +Stellar relayers support standard relayer configuration options along with Stellar-specific policies: + +| Policy | Type | Default | Description | +| --- | --- | --- | --- | +| `min_balance` | integer | None | Minimum balance in stroops (1 XLM = 10,000,000 stroops) required for the relayer account | +| `max_fee` | integer | None | Maximum transaction fee in stroops the relayer is willing to pay | +| `timeout_seconds` | integer | None | Transaction timeout in seconds | +| `concurrent_transactions` | boolean | false | Enable concurrent transaction processing. When enabled, bypasses the lane gating mechanism that normally ensures sequential processing for each relayer. Only enable this when your relayer manages transactions from multiple accounts with independent sequence number pools. | + +Example configuration with policies: +```json +{ + "id": "stellar-example", + "name": "Stellar Example", + "network": "testnet", + "paused": false, + "network_type": "stellar", + "signer_id": "local-signer", + "policies": { + "min_balance": 10000000, + "max_fee": 1000000, + "timeout_seconds": 30, + "concurrent_transactions": false + } +} +``` + +For general relayer configuration options, check [User Documentation - Relayers](/relayer#3_relayers). + +## API Reference + +### Transaction Operations + +The Stellar API supports a variety of transaction operations: + +| Method Name | Required Parameters | Description | +| --- | --- | --- | +| Send Transaction | `network`, `operations` (or `transaction_xdr`) | Submit a transaction to the Stellar network. Supports payment and Soroban operations directly, or any Stellar operation via pre-built XDR transactions. Also supports fee bump transactions for managing transaction fees. | +| Get Transaction Details | `transaction_id` | Retrieve a specific transaction by its ID. | +| List Transactions | (none) | List transactions for the relayer with pagination support. | + +### Supported Operation Types + +| Operation Type | Description | +| --- | --- | +| `payment` | Transfer native XLM or other assets between accounts | +| `invoke_contract` | Call a deployed Soroban smart contract function | +| `create_contract` | Deploy a new Soroban smart contract from WASM hash | +| `upload_wasm` | Upload WASM contract code to the Stellar ledger | + +### Transaction Structure + +***Required fields:*** +- `network`: The Stellar network ("testnet", "mainnet", etc.) +- Either `operations` (array of operations) OR `transaction_xdr` (base64-encoded XDR) - but not both + +***Optional fields:*** +- `source_account`: The Stellar account that will be the source of the transaction (defaults to relayer’s address) +- `memo`: Transaction memo (see Memo Types below) +- `valid_until`: Transaction expiration time (ISO 8601 format) +- `transaction_xdr`: Pre-built transaction XDR (base64 encoded, signed or unsigned) - mutually exclusive with `operations` +- `fee_bump`: Boolean flag to request fee-bump wrapper (only valid with signed `transaction_xdr`) +- `max_fee`: Maximum fee for fee bump transactions in stroops (defaults to 1,000,000 = 0.1 XLM) + +### Transaction Input Methods + +The relayer supports three ways to submit transactions: + +1. ***Operations-based***: Build a transaction by specifying the `operations` array (recommended for most use cases) +2. ***Transaction XDR (unsigned)***: Submit a pre-built unsigned transaction using `transaction_xdr` field (advanced use case) +3. ***Transaction XDR (signed)***: Submit a signed transaction using `transaction_xdr` with `fee_bump: true` (required for signed XDR) to wrap it in a fee bump transaction + +#### Example 1: Operations-based Transaction + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "payment", + "destination": "GD77B6LYQ5XDCW6CND7CQMA23FSV7MZQGLBAU5OMEOXQM6XFTCMWQQCJ", + "asset": {"type": "native"}, + "amount": 1000000 + } + ], + "memo": {"type": "text", "value": "Payment for services"} +}' +``` + +#### Example 2: Unsigned Transaction XDR + +Submit a pre-built unsigned transaction. The relayer will sign it with its configured signer: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "transaction_xdr": "AAAAAgAAAACige4lTdwSB/sto4SniEdJ2kOa2X65s5bqkd40J4DjSwAAAGQAAHAkAAAADgAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAKKB7iVN3BIH+y2jhKeIR0naQ5rZfrmzluqR3jQngONLAAAAAAAAAAAAD0JAAAAAAAAAAAA=" +}' +``` + +The `transaction_xdr` field should contain a base64-encoded unsigned transaction envelope. This is useful when: +- You need precise control over transaction structure +- You want to use advanced Stellar features not exposed via the operations API + +#### Example 3: Signed Transaction XDR + +Submit a pre-signed transaction with fee bump wrapper. ***Note: `fee_bump: true` is required when submitting signed XDR***: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "transaction_xdr": "AAAAAgAAAABjc+mbXCnvmVk4lxqVl7s0LAz5slXqmkHBg8PpH7p3DgAAAGQABpK0AAAACQAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAGN0qQBW8x3mfbwGGYndt2uq4O4sZPUrDx5HlwuQke9zAAAAAAAAAAAAAA9CAAAAAQAAAAA=", + "fee_bump": true, + "max_fee": 10000000 +}' +``` + +The fee bump feature is useful when: +- You have a pre-signed transaction from another system or wallet +- You want the relayer to pay transaction fees on behalf of the original signer +- You need to increase the fee for a transaction to ensure timely execution + + + + +When using `transaction_xdr`: +- The XDR must be properly formatted and valid for the target network +- For unsigned XDR, the relayer will add its signature before submission +- ***For signed XDR, `fee_bump: true` is mandatory*** - the relayer requires this to wrap the signed transaction in a fee bump envelope +- The `max_fee` parameter (in stroops) controls the maximum fee for fee bump transactions (defaults to 1,000,000 = 0.1 XLM) + + + +See [API Reference](https://release-v1-0-0%2D%2Dopenzeppelin-relayer.netlify.app/api_docs.html) for full details and examples. + +### Asset Types + +Assets in Stellar operations must be specified with a type field: + +***Native XLM:*** +```json +{"type": "native"} +``` + +***Credit Asset (4 characters or less):*** +```json +{ + "type": "credit_alphanum4", + "code": "USDC", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" +} +``` + +***Credit Asset (5-12 characters):*** +```json +{ + "type": "credit_alphanum12", + "code": "LONGASSET", + "issuer": "GA5ZSEJYB37JRC5AVCIA5MOP4RHTM335X2KGX3IHOJAPP5RE34K4KZVN" +} +``` + +### Memo Types + +Transactions can include optional memos: + +* ***No Memo***: `"type": "none"` +* ***Text Memo***: `"type": "text", "value": "Payment for services"` (max 28 UTF-8 bytes) +* ***ID Memo***: `"type": "id", "value": "12345"` +* ***Hash Memo***: `"type": "hash", "value": "deadbeef..."` (32 bytes hex) +* ***Return Memo***: `"type": "return", "value": "deadbeef..."` (32 bytes hex) + + + + +Memos are not supported for Soroban contract operations (invoke_contract, create_contract, upload_wasm). Attempting to include a memo with these operations will result in an error. + + + +### Soroban Contract Operations + +#### Invoke Contract + +Call a deployed Soroban smart contract: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "invoke_contract", + "contract_address": "CA7QYNF7SOWQ3GLR2BGMZEHXAVIRZA4KVWLTJJFC7MGXUA74P7UJUWDA", + "function_name": "transfer", + "args": [ + {"address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY"}, + {"address": "GD77B6LYQ5XDCW6CND7CQMA23FSV7MZQGLBAU5OMEOXQM6XFTCMWQQCJ"}, + {"i128": {"hi": "0", "lo": "1000000"}} + ], + "auth": {"type": "source_account"} + } + ] +}' +``` + +#### Create Contract + +Deploy a new Soroban smart contract: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "create_contract", + "source": { + "from": "address", + "address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY" + }, + "wasm_hash": "d3b2f6f8a1c5e9b4a7d8c2e1f5a9b3c6e8d4f7a2b5c8e1d4f7a0b3c6e9d2f5a8", + "salt": "0000000000000000000000000000000000000000000000000000000000000001" + } + ] +}' +``` + +#### Upload WASM + +Upload contract code to the Stellar ledger: + +```bash +curl --location --request POST 'http://localhost:8080/api/v1/relayers//transactions' \ +--header 'Authorization: Bearer ' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + "network": "testnet", + "operations": [ + { + "type": "upload_wasm", + "wasm": { + "type": "base64", + "base64": "AGFzbQEAAAABBgFgAX8BfwMCAQAFAwEAAQcPAgVoZWxsbwAACG1lbW9yeTIDCgQAAAAL" + } + } + ] +}' +``` + +### ScVal Argument Format + +When invoking contract functions, arguments must be provided as ScVal values in JSON format: + +| Type | Format | Description | +| --- | --- | --- | +| U64 | `"u64": "1000000"` | Unsigned 64-bit integer | +| I64 | `"i64": "-500"` | Signed 64-bit integer | +| U32 | `"u32": 42` | Unsigned 32-bit integer | +| I32 | `"i32": -42` | Signed 32-bit integer | +| Boolean | `"bool": true` | Boolean value | +| String | `"string": "hello world"` | UTF-8 string | +| Symbol | `"symbol": "transfer"` | Symbol (used for function names) | +| Address | `"address": "GCRID3RFJXOBEB73FWRYJJ4II5E5UQ413F7LTM4W5KI54NBHQDRUXVLY"` | Stellar account or contract address | +| Bytes | `"bytes": "deadbeef"` | Hex-encoded byte array | +| Vector | `"vec": ["u32": 1, "u32": 2, "u32": 3]` | Array of ScVal values | +| Map | `"map": ["key": "symbol": "name", "val": "string": "MyToken"]` | Key-value pairs | + +Additional types like U128, I128, U256, and I256 are also supported using multi-part representations. + +### Authorization Modes + +Soroban operations support different authorization modes: + +| Type | Description | +| --- | --- | +| `none` | No authorization required | +| `source_account` | Use the transaction source account (default) | +| `addresses` | Use specific addresses (future feature) | +| `xdr` | Advanced: provide base64-encoded XDR entries. This allows you to provide pre-signed SorobanAuthorizationEntry objects for complex authorization scenarios. See the [official Stellar documentation on authorization](https://developers.stellar.org/docs/learn/smart-contract-internals/authorization) for detailed information about SorobanAuthorizationEntries. | + +## Signer Support + +Stellar networks support the following signer types: + +* ***Local Signer***: Uses encrypted keystore files (suitable for development) +* ***Google Cloud KMS***: Uses Google Cloud Key Management Service with ED25519 keys (recommended for production) +* ***Turnkey***: Uses Turnkey’s secure key management infrastructure with ED25519 keys (recommended for production) + +For detailed signer configuration, see the [Signers Configuration](/relayer/configuration/signers) guide. + +For complete examples: +- Google Cloud KMS: [stellar-gcp-kms-signer example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/stellar-gcp-kms-signer) +- Turnkey: [stellar-turnkey-signer example](https://github.com/OpenZeppelin/openzeppelin-relayer/tree/main/examples/stellar-turnkey-signer) + +## Security + +* Do not expose the relayer directly to the public internet +* Deploy behind a secure backend (reverse proxy, firewall) +* Use hosted signers in production systems +* Ensure proper network passphrases are configured for transaction signing + +## Troubleshooting + +* Check environment variables and configuration files for errors +* Verify network passphrase matches the target network +* Review container logs for Stellar-specific errors +* Ensure Soroban RPC endpoints are properly configured for contract operations + +## Roadmap + +* See [Project Roadmap](/relayer/roadmap) for upcoming features + +## Support + +For help, join our [Telegram](https://t.me/openzeppelin_tg/2) or open an issue on GitHub. + +## License + +This project is licensed under the GNU Affero General Public License v3.0. diff --git a/docs/content/relayer/structure.mdx b/docs/content/relayer/structure.mdx new file mode 100644 index 00000000..0704bb4a --- /dev/null +++ b/docs/content/relayer/structure.mdx @@ -0,0 +1,106 @@ +--- +title: Project Structure +--- + +This document provides detailed information about each directory in the OpenZeppelin Relayer project. + +## Source Code Organization + +### `src/` Directory +The main source code directory contains the core implementation files organized into several modules: + +* `api/`: Route and controllers logic + * Manages HTTP routing and delegates incoming requests to controllers +* `bootstrap/`: Service initialization + * Bootstraps and initializes application services +* `config/`: Configuration management + * Handles system configuration and environment settings +* `constants/`: Global constants + * Provides static values used across the application +* `domain/`: Business domain logic + * Encapsulates core business rules and domain-specific functionality +* `jobs/`: Asynchronous job processing + * Manages background task queueing and execution +* `logging/`: Logging and file rotation + * Implements logging functionalities and log file management +* `metrics/`: Metrics collection + * Collects and reports application performance and usage metrics +* `models/`: Core data models and types + * Defines data structures and type definitions for the system +* `repositories/`: Configuration storage + * Provides interfaces for storing and retrieving configuration data +* `services/`: Business service logic + * Implements core business functionalities and service operations +* `utils/`: Utility functions + * Offers helper functions and common utilities for the application + +## Documentation + +### `docs/` Directory +Project documentation: + +* User guides +* API documentation +* Configuration examples +* Architecture diagrams + +## Configuration + +### `config/` Directory + +Houses system configuration file and keys: + +* `config.json` configuration file +* keystore files referenced from config.json file + +## Tests + +### `test/` Directory + +Includes comprehensive testing suites to ensure system reliability: + +* End-to-end tests that simulate real-world user scenarios + +## Scripts + +### `scripts/` Directory + +Utility scripts. + +## Examples + +### `examples/` Directory + +Provides practical examples and sample configurations to help users get started: + +* Demonstrates typical service configurations for various environments +* Acts as a quick-start guide for customizing and deploying the relayer +* Serves as a reference for best practices in configuration and deployment + +## Development Tools + +### Pre-commit Hooks +Located in the project root: + +* Code formatting checks +* Linting rules +* Commit message validation + +### Build Configuration +Core build files: + +* `Cargo.toml`: Project dependencies and metadata +* `rustfmt.toml`: Code formatting rules +* `rust-toolchain.toml`: Rust version and components + +## Docker Support + +The project includes Docker configurations for different environments: + +* `Dockerfile.development`: Development container setup +* `Dockerfile.production`: Production-ready container + + + +For detailed information about running the relayers in containers, see the Docker deployment section in the main documentation. + diff --git a/docs/content/stellar-contracts/access/access-control.mdx b/docs/content/stellar-contracts/access/access-control.mdx new file mode 100644 index 00000000..092b8d9d --- /dev/null +++ b/docs/content/stellar-contracts/access/access-control.mdx @@ -0,0 +1,236 @@ +--- +title: Access Control +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/access/src/access-control) + +## Overview + +The Access Control module provides a comprehensive role-based access control system for Soroban contracts. It enables developers to manage permissions through a hierarchical role system, with a renounceable single overarching admin and customizable role assignments. + +## Key Concepts + +### Admin Management + +The system features a single top-level admin with privileges to call any function in the `AccessControl` trait. This admin must be set during contract initialization for the module to function properly. This overarching admin can renounce themselves for decentralization purposes. + +Admin transfers are implemented as a two-step process to prevent accidental or malicious takeovers: + +1. The current admin **initiates** the transfer by specifying the new admin and an expiration time (`live_until_ledger`). +2. The designated new admin must **explicitly accept** the transfer to complete it. + +Until the transfer is accepted, the original admin retains full control and can override or cancel the transfer by initiating a new one or using a `live_until_ledger` of `0`. + +### Role Hierarchy + +The module supports a hierarchical role system where each role can have an "admin role" assigned to it. For example: + +* Create roles `minter` and `minter_admin` +* Assign `minter_admin` as the admin role for the `minter` role +* Accounts with the `minter_admin` role can grant/revoke the `minter` role to other accounts + +This allows for creating complex organizational structures with chains of command and delegated authority. + +#### Setting Up Role Hierarchies + +Here's how to establish and use role hierarchies in practice: + +```rust +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, Symbol}; +use stellar_access::access_control::{self as access_control, AccessControl}; + +const MANAGER_ROLE: Symbol = symbol_short!("manager"); +const GUARDIAN_ROLE: Symbol = symbol_short!("guardian"); + +#[contract] +pub struct MyContract; + +#[contractimpl] +impl MyContract { + pub fn __constructor(e: &Env, admin: Address, manager: Address) { + // Set the contract admin + access_control::set_admin(e, &admin); + + // 1. Set MANAGER_ROLE as the admin role for GUARDIAN_ROLE: + // accounts with MANAGER_ROLE can manage accounts with GUARDIAN_ROLE + access_control::set_role_admin_no_auth(e, &admin, &GUARDIAN_ROLE, &MANAGER_ROLE); + + // 2. Admin grants MANAGER_ROLE to the manager account + access_control::grant_role_no_auth(e, &admin, &manager, &MANAGER_ROLE); + } + + pub fn manage_guardians(e: &Env, manager: Address, guardian1: Address, guardian2: Address) { + // Manager must be authorized + manager.require_auth(); + + // 3. Now the manager can grant GUARDIAN_ROLE to other accounts + access_control::grant_role_no_auth(e, &manager, &guardian1, &GUARDIAN_ROLE); + access_control::grant_role_no_auth(e, &manager, &guardian2, &GUARDIAN_ROLE); + + // Manager can also revoke GUARDIAN_ROLE + access_control::revoke_role_no_auth(e, &manager, &guardian1, &GUARDIAN_ROLE); + } +} +``` + +In this example: +1. The `admin` sets `MANAGER_ROLE` as the admin role for `GUARDIAN_ROLE` using `set_role_admin()` +2. The `admin` grants the `MANAGER_ROLE` role to the `manager` account +3. The `manager` can now grant/revoke the `GUARDIAN_ROLE` role to other accounts without requiring admin intervention + +### Role Enumeration + +The system tracks account-role pairs in storage with additional enumeration logic: + +* When a role is granted to an account, the pair is stored and added to enumeration storage +* When a role is revoked, the pair is removed from storage and enumeration +* If all accounts are removed from a role, the helper storage items become empty or 0 + +Roles exist only through their relationships with accounts, so a role with zero accounts is indistinguishable from a role that never existed. + +## Procedural Macros + +The module includes several procedural macros to simplify authorization checks in your contract functions. These macros are divided into two categories: + +### Authorization-Enforcing Macros + +These macros automatically call `require_auth()` on the specified account before executing the function: + +#### @only_admin + +Restricts access to the contract admin only: + +```rust +#[only_admin] +pub fn admin_function(e: &Env) { + // Only the admin can call this function + // require_auth() is automatically called +} +``` + +#### @only_role + +Restricts access to accounts with a specific role: + +```rust +#[only_role(caller, "minter")] +pub fn mint(e: &Env, caller: Address, to: Address, token_id: u32) { + // Only accounts with the "minter" role can call this + // require_auth() is automatically called on caller +} +``` + +#### @only_any_role + +Restricts access to accounts with any of the specified roles: + +```rust +#[only_any_role(caller, ["minter", "burner"])] +pub fn multi_role_action(e: &Env, caller: Address) { + // Accounts with either "minter" or "burner" role can call this + // require_auth() is automatically called on caller +} +``` + +### Role-Checking Macros + +These macros check role membership but do **not** enforce authorization. You must manually call `require_auth()` if needed: + +#### @has_role + +Checks if an account has a specific role: + +```rust +#[has_role(caller, "minter")] +pub fn conditional_mint(e: &Env, caller: Address, to: Address, token_id: u32) { + // Checks if caller has "minter" role, but doesn't call require_auth() + caller.require_auth(); // Must manually authorize if needed +} +``` + +#### @has_any_role + +Checks if an account has any of the specified roles: + +```rust +#[has_any_role(caller, ["minter", "burner"])] +pub fn multi_role_check(e: &Env, caller: Address) { + // Checks if caller has either role, but doesn't call require_auth() + caller.require_auth(); // Must manually authorize if needed +} +``` + +## Usage Example + +Here’s a simple example of using the Access Control module: + +```rust +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env}; +use stellar_access::access_control::{self as access_control, AccessControl}; +use stellar_macros::{has_role, only_admin}; + +#[contract] +pub struct MyContract; + +#[contractimpl] +impl MyContract { + pub fn __constructor(e: &Env, admin: Address) { + // Set the contract admin + access_control::set_admin(e, &admin); + + // Create a "minter" role with admin as its admin + access_control::set_role_admin_no_auth(e, &symbol_short!("minter"), &symbol_short!("admin")); + } + + + #[only_admin] + pub fn admin_restricted_function(e: &Env) -> Vec { + vec![&e, String::from_str(e, "seems sus")] + } + + + // we want `require_auth()` provided by the macro, since there is no + // `require_auth()` in `Base::mint`. + #[only_role(caller, "minter")] + pub fn mint(e: &Env, caller: Address, to: Address, token_id: u32) { + Base::mint(e, &to, token_id) + + } + + + // allows either minter or burner role, does not enforce `require_auth` in the macro + #[has_any_role(caller, ["minter", "burner"])] + pub fn multi_role_action(e: &Env, caller: Address) -> String { + caller.require_auth(); + String::from_str(e, "multi_role_action_success") + } + + + // allows either minter or burner role AND enforces `require_auth` in the macro + #[only_any_role(caller, ["minter", "burner"])] + pub fn multi_role_auth_action(e: &Env, caller: Address) -> String { + + String::from_str(e, "multi_role_auth_action_success") + } +} +``` + +## Benefits and Trade-offs + +### Benefits + +* Flexible role-based permission system +* Hierarchical role management +* Secure admin transfer process +* Admin is renounceable +* Easy integration with procedural macros + +### Trade-offs + +* More complex than single-owner models like Ownable + +## See Also + +* [Ownable](/stellar-contracts/access/ownable) +* [Fungible Token](/stellar-contracts/tokens/fungible/fungible) +* [Non-Fungible Token](/stellar-contracts/tokens/non-fungible/non-fungible) diff --git a/docs/content/stellar-contracts/access/ownable.mdx b/docs/content/stellar-contracts/access/ownable.mdx new file mode 100644 index 00000000..255a43d8 --- /dev/null +++ b/docs/content/stellar-contracts/access/ownable.mdx @@ -0,0 +1,143 @@ +--- +title: Ownable +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/access/src/ownable) + +## Overview + +The Ownable module provides a simple access control mechanism where a contract has a single account (owner) that can be granted exclusive access to specific functions. This pattern is useful for contracts that need a straightforward authorization system with a single privileged account. + +## Key Concepts + +### Ownership Management + +The system designates a single owner with exclusive access to functions marked with the `#[only_owner]` macro. The initial owner must be ideally set during contract initialization for the module to function properly. + +Like the Access Control module, ownership transfers are implemented as a two-step process to prevent accidental or malicious takeovers: + +1. The current owner **initiates** the transfer by specifying the new owner and an expiration time (`live_until_ledger`). +2. The designated new owner must **explicitly accept** the transfer to complete it. + +Until the transfer is accepted, the original owner retains full control and can override or cancel the transfer by initiating a new one or using a `live_until_ledger` of `0`. + +### Ownership Renunciation + +The Ownable module allows the owner to permanently renounce ownership of the contract. This is a one-way operation that cannot be undone. After ownership is renounced, all functions marked with `#[only_owner]` become permanently inaccessible. + +This feature is useful for contracts that need to become fully decentralized after an initial setup phase, or to make a contract **immutable and non-upgradeable** by removing administrative control entirely. + +#### Example: Making a Contract Immutable + +A common use case is renouncing ownership after initial setup to ensure the contract becomes immutable and cannot be upgraded: + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env}; +use stellar_access::ownable::{self as ownable, Ownable}; +use stellar_macros::only_owner; + +#[contract] +pub struct MyContract; + +#[contractimpl] +impl MyContract { + pub fn __constructor(e: &Env, initial_owner: Address) { + ownable::set_owner(e, &initial_owner); + } + + #[only_owner] + pub fn finalize_and_lock(e: &Env) { + // Perform any final configuration here + // ... + + // Renounce ownership to make the contract immutable + // After this call, no owner-restricted functions can ever be called again + ownable::renounce_ownership(e); + + // The contract is now permanently locked and non-upgradeable + } + + #[only_owner] + pub fn emergency_upgrade(e: &Env, new_wasm_hash: BytesN<32>) { + // This function becomes permanently inaccessible after renounce_ownership + e.deployer().update_current_contract_wasm(new_wasm_hash); + } +} +``` + +**Important Notes:** +- Once `renounce_ownership()` is called, there is no way to restore ownership +- All functions marked with `#[only_owner]` become permanently inaccessible +- This effectively makes the contract immutable and non-upgradeable +- You cannot renounce ownership while a transfer is in progress + +### Procedural Macro + +The module includes a procedural macro to simplify owner authorization checks: + +#### @only_owner + +Ensures the caller is the owner before executing the function: + +```rust +#[only_owner] +pub fn restricted_function(e: &Env, other_param: u32) { + // Function body - only accessible to owner +} +``` + +This expands to code that retrieves the owner from storage and requires authorization before executing the function body. + +## Usage Example + +Here’s a simple example of using the Ownable module: + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env}; +use stellar_access::ownable::{self as ownable, Ownable}; +use stellar_macros::only_owner; + +#[contract] +pub struct MyContract; + +#[contractimpl] +impl MyContract { + pub fn __constructor(e: &Env, initial_owner: Address) { + // Set the contract owner + ownable::set_owner(e, &initial_owner); + } + + #[only_owner] + pub fn update_config(e: &Env, new_value: u32) { + // Only the owner can call this function + // Implementation... + } + + // This function is accessible to anyone + pub fn get_config(e: &Env) -> u32 { + // Implementation... + 42 + } +} +``` + +## Benefits and Trade-offs + +### Benefits + +* Simple and straightforward ownership model +* Secure two-step ownership transfer process +* Option to permanently renounce ownership +* Easy integration with procedural macro +* Event emission for important actions + +### Trade-offs + +* Limited to a single privileged account (compared to role-based systems) +* Once ownership is renounced, privileged functions become permanently inaccessible + +## See Also + +* [Access Control](/stellar-contracts/access/access-control) +* [Fungible Token](/stellar-contracts/tokens/fungible/fungible) +* [Non-Fungible Token](/stellar-contracts/tokens/non-fungible/non-fungible) diff --git a/docs/content/stellar-contracts/accounts/authorization-flow.mdx b/docs/content/stellar-contracts/accounts/authorization-flow.mdx new file mode 100644 index 00000000..1ae843ea --- /dev/null +++ b/docs/content/stellar-contracts/accounts/authorization-flow.mdx @@ -0,0 +1,239 @@ +--- +title: Authorization Flow +--- + +Authorization in smart accounts is determined by matching the current context against the account's context rules. Rules are gathered, ordered by recency, and evaluated until one satisfies the requirements. If a matching rule is found, its policies (if any) are enforced. Otherwise, authorization fails. + +## Detailed Flow +```mermaid +sequenceDiagram + participant User + participant SmartAccount + participant ContextRule + participant DelegatedSigner + participant Verifier + participant Policy + + User->>SmartAccount: Signatures + SmartAccount->>ContextRule: Match context
(CallContract, Default, ...) + ContextRule->>ContextRule: Filter expired rules
Sort newest first + + loop Each rule until match + Note over ContextRule,DelegatedSigner: Built-in authorization
for delegated signers + ContextRule->>DelegatedSigner: require_auth_for_args() + DelegatedSigner-->>ContextRule: Authorized + + Note over ContextRule,Verifier: Signature verification for external signers + ContextRule->>Verifier: verify() + Verifier-->>ContextRule: Valid + + Note over ContextRule,Policy: Policy pre-checks + ContextRule->>Policy: can_enforce() + Policy-->>ContextRule: True/False + + alt All checks pass + ContextRule->>Policy: enforce() + Policy->>Policy: Update state + ContextRule-->>SmartAccount: ✓ Authorized + else Any check fails + ContextRule->>ContextRule: Try next rule + end + end + + SmartAccount-->>User: Success +``` + +### 1. Rule Collection + +The smart account gathers all relevant context rules for evaluation: + +- Retrieve all non-expired rules for the specific context type +- Include default rules that apply to any context +- Sort specific and default rules by creation time (newest first) + +**Context Type Matching:** +- For a `CallContract(address)` context, both specific `CallContract(address)` rules and `Default` rules are collected +- For a `CreateContract(wasm_hash)` context, both specific `CreateContract(wasm_hash)` rules and `Default` rules are collected +- For any other context, only `Default` rules are collected + +**Expiration Filtering:** +Rules with `valid_until` set to a ledger sequence that has passed are automatically filtered out during collection. + +### 2. Rule Evaluation + +For each rule in order (newest and most specific first): + +#### Step 2.1: Signer Filtering + +Extract authenticated signers from the rule's signer list. A signer is considered authenticated if: + +- **Delegated Signer**: The address has authorized the operation via `require_auth_for_args(payload)` +- **External Signer**: The verifier contract confirms the signature is valid for the public key + +Only authenticated signers proceed to the next step. + +#### Step 2.2: Policy Validation + +If the rule has attached policies, verify that all can be enforced: + +```rust +for policy in rule.policies { + if !policy.can_enforce(e, account, rule_id, signers, auth_context) { + // This rule fails, try the next rule + } +} +``` + +If any policy's `can_enforce()` returns false, the rule fails and evaluation moves to the next rule. + +#### Step 2.3: Authorization Check + +The authorization check depends on whether policies are present: + +**With Policies:** +- Success if all policies passed `can_enforce()` +- The presence of authenticated signers is verified during policy evaluation + +**Without Policies:** +- Success if all signers in the rule are authenticated +- At least one signer must be authenticated for the rule to match + +#### Step 2.4: Rule Precedence + +The first matching rule wins. Newer rules take precedence over older rules for the same context type. This allows overwriting old rules. + +### 3. Policy Enforcement + +If authorization succeeds, the smart account calls `enforce()` on all matched policies in order: + +```rust +for policy in matched_rule.policies { + policy.enforce(e, account, rule_id, signers, auth_context); +} +``` + +This triggers any necessary state changes such as updating spending counters, recording timestamps, emitting audit events, or modifying allowances. + +Policy enforcement requires the smart account's authorization, ensuring that policies can only be enforced by the account itself. + +### 4. Result + +**Success:** Authorization is granted and the transaction proceeds. All policy state changes are committed. + +**Failure:** Authorization is denied and the transaction reverts. No state changes are committed. + +## Examples + +### Specific Context with Policy + +**Configuration:** +```rust +// DEX-specific rule with session key and spending limit +ContextRule { + id: 2, + context_type: CallContract(dex_address), + valid_until: Some(current_ledger + 24_hours), + signers: [passkey], + policies: [spending_limit_policy] +} + +// Default admin rule +ContextRule { + id: 1, + context_type: Default, + signers: [ed25519_alice, ed25519_bob], + policies: [] +} +``` + +**Call Context:** `CallContract(dex_address)` + +**Authorization Entries:** `[passkey_signature]` + +**Flow:** +1. Collect: Rules 2 (specific) and 1 (default) +2. Evaluate Rule 2: + - Signer filtering: Passkey authenticated + - Policy validation: Spending limit check passes + - Authorization check: All policies enforceable → Success +3. Enforce: Update spending counters, emit events +4. Result: Authorized + +If the spending limit had been exceeded, Rule 2 would fail and evaluation would continue to Rule 1 (which would also fail since the passkey doesn't match Alice or Bob). + +### Fallback to Default + +**Configuration:** +```rust +// Session rule (expired) +ContextRule { + id: 2, + context_type: CallContract(dex_address), + valid_until: Some(current_ledger - 100), // Expired + signers: [session_key], + policies: [spending_limit_policy] +} + +// Default admin rule +ContextRule { + id: 1, + context_type: Default, + signers: [ed25519_alice, ed25519_bob], + policies: [] +} +``` + +**Call Context:** `CallContract(dex_address)` + +**Authorization Entries:** `[ed25519_alice_signature, ed25519_bob_signature]` + +**Flow:** +1. Collect: Rule 2 filtered out (expired), only Rule 1 collected +2. Evaluate Rule 1: Both Alice and Bob authenticated → Success +3. Enforce: No policies to enforce +4. Result: Authorized + +The expired session rule is automatically filtered out, and authorization falls back to the default admin rule. + +### Authorization Failure + +**Configuration:** +```rust +// Default rule requiring 2-of-3 threshold +ContextRule { + id: 1, + context_type: Default, + signers: [alice, bob, carol], + policies: [threshold_policy(2)] +} +``` + +**Call Context:** `CallContract(any_address)` + +**Authorization Entries:** `[alice_signature]` + +**Flow:** +1. Collect: Default rule retrieved +2. Evaluate: + - Signer filtering: Only Alice authenticated + - Policy validation: Threshold policy requires 2 signers, only 1 present → Fail +3. No more rules to evaluate +4. Result: Denied (transaction reverts) + +## Performance Considerations + +Protocol 23 optimizations make the authorization flow efficient: +- **Marginal storage read costs**: Reading multiple context rules has negligible cost +- **Cheaper cross-contract calls**: Calling verifiers and policies is substantially cheaper + +The framework enforces limits to maintain predictability: +- Maximum context rules per smart account: 15 +- Maximum signers per context rule: 15 +- Maximum policies per context rule: 5 + +## See Also + +- [Smart Account](/stellar-contracts/accounts/smart-account) +- [Context Rules](/stellar-contracts/accounts/context-rules) +- [Signers and Verifiers](/stellar-contracts/accounts/signers-and-verifiers) +- [Policies](/stellar-contracts/accounts/policies) diff --git a/docs/content/stellar-contracts/accounts/context-rules.mdx b/docs/content/stellar-contracts/accounts/context-rules.mdx new file mode 100644 index 00000000..6c136584 --- /dev/null +++ b/docs/content/stellar-contracts/accounts/context-rules.mdx @@ -0,0 +1,175 @@ +--- +title: Context Rules +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/smart_account) + +Context rules function like routing tables for authorization. For each context, they specify scope, lifetime, and the conditions (signers and policies) that must be satisfied before execution proceeds. + +## Structure + +A context rule contains the following components: + +#### ID +Unique identifier for the rule within the smart account. + +#### Name +Human-readable description of the rule's purpose (e.g., "Admin Access", "DeFi Session"). + +#### Context Type +Defines the scope where the rule applies: + +- `Default`: Applies to any context. Used for admin-like authorization that spans all operations. +- `CallContract(Address)`: Applies to specific contract calls. Useful for scoped permissions like session logins to a particular dApp. +- `CreateContract(BytesN<32>)`: Applies to contract deployments with a specific WASM hash. Enables control over which contracts can be deployed. + +#### Valid Until +Optional expiration defined by a ledger sequence. Rules with expiration automatically become invalid after the specified ledger, enabling time-limited permissions like 24-hour sessions. + +#### Signers +List of authorized signers (maximum 15 per rule). Signers can be either delegated (any Soroban address) or external (using verifier contracts). + +For detailed documentation on signers, see [Signers](/stellar-contracts/accounts/signers-and-verifiers). + +#### Policies +List of policy contracts (maximum 5 per rule). Policies act as enforcement modules that perform read-only prechecks and state-changing enforcement logic. + +For detailed documentation on policies, see [Policies](/stellar-contracts/accounts/policies). + +## Key Properties + +### Requirement Flexibility +Each rule must contain at least one signer OR one policy. This enables pure policy-based authorization (like a spending limit without signature checks) or pure signature-based authorization (like an n-of-n multisig). + +### Multiple Rules Per Context +Multiple rules can exist for the same context type with different signer sets and policies. This allows progressive authorization models where different combinations of credentials grant access to the same operations. + +### Rule Precedence +Rules are evaluated in reverse chronological order (newest first). The first matching rule wins. This enables seamless permission updates: adding a new rule with different requirements immediately takes precedence over older rules for the same context. + +### Automatic Expiration +Expired rules are automatically filtered out during authorization evaluation. + +## Context Rule Limits + +The framework enforces limits to keep costs predictable and encourage proactive context rule management (remove expired or non-valid rules): + +- Maximum context rules per smart account: 15 +- Maximum signers per context rule: 15 +- Maximum policies per context rule: 5 + +## Authorization Matching + +During authorization, the framework: + +1. Gathers all non-expired rules matching the context type plus default rules +2. Sorts rules by creation time (newest first) +3. Evaluates rules in order until one matches +4. Returns the first matching rule or fails if none match + +For detailed documentation on the authorization flow, see [Authorization Flow](/stellar-contracts/accounts/authorization-flow). + +## Example Configuration + +```mermaid +graph TD + SA["Smart Account"] + + CR1["Context Rule


ID: 1
Name: Sudo
Context: Default
Valid Until: None

Signers
External(bls_verifier, alice_key)
External(bls_verifier, bob_key)
Delegated(dave_addr)

Policies
2-of-3 Threshold
"] + + CR2["Context Rule


ID: 2
Name: Dapp1 Subscription
Context: CallContract(usdc)
Valid Until: 1 year

Signers
External(ed25519_verifier, dapp1_key)

Policies
Spending Limit
"] + + CR3["Context Rule


ID: 5
Name: Dapp2 Session
Context: CallContract(dapp_addr)
Valid Until: 7 days

Signers
External(ed25519_verifier, dapp2_key)

Policies
Rate Limit
Time Window
"] + + CR4["Context Rule


ID: 8
Name: AI Agent
Context: CallContract(some_addr)
Valid Until: 12 hours

Signers
External(secp256r1_verifier, agent_key)

Policies
Volume Cap
"] + + SA --- CR1 + SA --- CR2 + SA --- CR3 + SA --- CR4 + + style SA fill:#E8E8E8,stroke:#333,stroke-width:2px +``` + + +```rust +use soroban_sdk::{map, vec, Env, String} +use stellar_accounts::smart_account::{self as smart_account, ContextRuleType}; + +// This rule applies to all contexts and requires 2-of-3 signatures from Alice, Bob, or Dave. +smart_account::add_context_rule( + e, + ContextRuleType::Default, + String::from_str(e, "Sudo"), + vec![ + e, + Signer::External(bls_verifier, alice_key), + Signer::External(bls_verifier, bob_key), + Signer::Delegated(dave_addr) + ], + map![ + e, + (threshold_policy, threshold_params) // 2-of-3 Threshold + ], + None, // No expiration +); + +// This rule applies only to calls to the USDC contract, expires in 1 year, +// requires a dapp1 key signature, and enforces spending limits. +smart_account::add_context_rule( + e, + ContextRuleType::CallContract(usdc_addr), + String::from_str(e, "Dapp1 Subscription"), + vec![ + e, + Signer::External(ed25519_verifier, dapp1_key) + ], + map![ + e, + (spending_limit_policy, spending_params) + ], + Some(current_ledger + 1_year) +); + +// This rule applies only to calls to the dApp contract, expires in 7 days, +// requires a dapp2 key signature, and enforces rate limiting and time window policies. +smart_account::add_context_rule( + e, + ContextRuleType::CallContract(dapp_addr), + String::from_str(e, "Dapp2 Session"), + vec![ + e, + Signer::External(ed25519_verifier, dapp2_key) + ], + map![ + e, + (rate_limit_policy, rate_limit_params), + (time_window_policy, time_window_params) + ], + Some(current_ledger + 7_days) +); + +// This rule applies only to calls to a specific contract, expires in 12 hours, +// requires an AI agent key signature, and enforces volume caps. +smart_account::add_context_rule( + e, + ContextRuleType::CallContract(some_addr), + String::from_str(e, "AI Agent"), + vec![ + e, + Signer::External(secp256r1_verifier, agent_key) + ], + map![ + e, + (volume_cap_policy, volume_cap_params) + ], + Some(current_ledger + 12_hours) +); +``` + +## See Also + +- [Smart Account](/stellar-contracts/accounts/smart-account) +- [Signers and Verifiers](/stellar-contracts/accounts/signers-and-verifiers) +- [Policies](/stellar-contracts/accounts/policies) +- [Authorization Flow](/stellar-contracts/accounts/authorization-flow) diff --git a/docs/content/stellar-contracts/accounts/policies.mdx b/docs/content/stellar-contracts/accounts/policies.mdx new file mode 100644 index 00000000..33572eec --- /dev/null +++ b/docs/content/stellar-contracts/accounts/policies.mdx @@ -0,0 +1,240 @@ +--- +title: Policies +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/policies) + +Policies are enforcement modules that add constraints to context rules in smart accounts. While signers determine who can authorize actions, policies determine how those authorizations are enforced, enabling sophisticated patterns like multi-signature thresholds, spending limits, and time-based restrictions. + +Policies attach to context rules and execute during the authorization flow. A context rule can have up to **5 policies** attached, and policies are executed in the order they were added. If policies are present in a context rule, **all of them must be enforceable** (i.e., `can_enforce` must return `true`) for the rule to be considered matched and authorized. + +## The Policy Trait + +All policies must implement the `Policy` trait: + +```rust +pub trait Policy { + type AccountParams: FromVal; + + /// Read-only pre-check to validate conditions + /// Must be idempotent and side-effect free + /// Returns true if the policy would allow the action + fn can_enforce( + e: &Env, + context: Context, + authenticated_signers: Vec, + rule: ContextRule, + smart_account: Address, + ) -> bool; + + /// State-changing enforcement hook + /// Called when a context rule successfully matches and all can_enforce checks pass + /// Requires smart account authorization + fn enforce( + e: &Env, + context: Context, + authenticated_signers: Vec, + rule: ContextRule, + smart_account: Address, + ); + + /// Initialize policy-specific storage and configuration + /// Called when a new context rule with attached policies is created + fn install( + e: &Env, + param: Self::AccountParams, + rule: ContextRule, + smart_account: Address, + ); + + /// Clean up policy data when removed + /// Called when a context rule is removed + fn uninstall( + e: &Env, + rule: ContextRule, + smart_account: Address, + ); +} +``` + +## Policy Lifecycle + +The four trait methods form a complete lifecycle for policy management: + +### Installation + +Installation occurs when a new context rule is created with attached policies or a policy is added to an existing context rule. The smart account calls `install()` on each policy contract, passing account-specific and context-specific parameters. + +This initialization step allows policies to configure their logic. For example: +- A threshold policy might define the required number of signatures for that particular account and context rule +- A spending limit policy might set daily or per-transaction caps + +Installation ensures that each policy has the necessary state and configuration ready before authorization checks begin. + +### Pre-check Validation + +Pre-check validation happens during authorization. When the matching algorithm iterates over context rules and their associated policies, it calls `can_enforce()` on each policy as a read-only pre-check. + +This function examines the current state without modifying it, for instance: +- Verifying that a spending limit has not been exceeded +- Checking that enough signers are present +- Validating that time-based restrictions are met + +Policies that fail this check cause the algorithm to move to the next context rule. + +### Enforcement + +Enforcement is triggered when a context rule successfully matches. Once all policies in the matched rule pass their `can_enforce()` checks, the smart account calls `enforce()` on each policy. + +This state-changing hook allows policies to: +- Update counters +- Emit events +- Record timestamps +- Track authorization activity + +For example, a spending limit policy might deduct from the available balance and emit an event documenting the transaction. + +### Uninstallation + +Uninstallation occurs when a context rule is removed from the smart account. The account calls `uninstall()` on each attached policy, allowing them to clean up any stored data associated with that specific account and context rule pairing. + +This ensures that policies do not leave orphaned state in storage. + +## Stateful vs Stateless Policies + +Policies can be implemented as either stateful or stateless: + +### Stateless Policies + +Stateless policies perform validation based solely on the provided parameters without maintaining any storage: + +- No storage operations +- No `require_auth` calls needed +- Lower resource consumption +- Example: Hard-coded threshold + +### Stateful Policies + +Stateful policies maintain storage to track state across multiple authorizations: + +- **Storage Segregation**: Must segregate storage entries by **both** smart account address AND context rule ID +- **Multiple Rules Support**: The same policy contract can be installed on multiple context rules from the same smart account, with separate storage for each +- **Authorization Required**: Must call `require_auth` from the smart account in `install`, `enforce`, and `uninstall` +- **Event Emission**: Should emit events for state changes to enable tracking and auditing +- Example: Spending limit (tracks cumulative spending over time) + +## Policy Sharing Models + +Policies can be deployed and used in different ways. A single policy contract instance can be shared across multiple smart accounts, multiple context rules within the same smart account, or different combinations of both. This shared model provides lower deployment costs (deploy once, use many times) and ensures consistent behavior across accounts, but requires proper storage segregation by smart account and rule ID. Alternatively, each smart account or context rule can have their own dedicated policy contract attached. + +## Policy Management + +The `SmartAccount` trait provides functions for managing policies within context rules: + +### Adding Policies + +```rust +fn add_policy( + e: &Env, + context_rule_id: u32, + policy: Address, + account_params: Val, +); +``` + +Adds a policy to an existing context rule and calls its `install()` function. The rule must not exceed the maximum of 5 policies. + +### Removing Policies + +```rust +fn remove_policy( + e: &Env, + context_rule_id: u32, + policy: Address, +); +``` + +Removes a policy from an existing context rule and calls its `uninstall()` function. The rule must maintain at least one signer OR one policy after removal. + +### Caveats + +**Signer Set Divergence in Threshold Policies** + +Threshold policies (both simple and weighted) store authorization requirements that are validated at installation time. However, policies are not automatically notified when signers are added to or removed from their parent context rule. This creates a state divergence that can lead to operational issues. + +**Removing Signers:** If signers are removed after policy installation, the total available signatures or weight may fall below the stored threshold, making it impossible to meet the authorization requirement and permanently blocking actions governed by that policy. + +**Example:** A 5-of-5 multisig where two signers are removed leaves only three signers, making the threshold of five unreachable. + +**Adding Signers:** Conversely, if signers are added without updating the threshold, the security guarantee silently weakens. A strict 3-of-3 multisig becomes a 3-of-5 multisig after adding two signers, reducing the required approval from 100% to 60% without any explicit warning. + +**Resolution:** Administrators must manually update thresholds and weights when modifying signer sets: +1. Before removing signers, verify that the threshold remains achievable +2. After adding signers, adjust thresholds or assign weights to maintain the desired security level +3. Ideally, bundle these updates in the same transaction as the signer modifications + +## Example Policies + +The OpenZeppelin Stellar Contracts library provides the necessary utilities for three policy implementations: + +### Simple Threshold + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/policies/simple_threshold.rs) + +The `simple_threshold` policy implements N-of-M multisig authorization, requiring a minimum number of valid signatures before allowing an action. This is the most common multisig pattern, treating all signers equally. For example, a 2-of-3 multisig requires any 2 signatures from 3 allowed signers. + +The policy requires a single configuration parameter: + +- **Threshold**: The minimum number of signatures required (N) + +The total number of signers (M) is determined by the context rule's signer list. + + +When using threshold policies, be aware of signer set divergence issues. See the [Caveats](#caveats) section above for details on how adding or removing signers affects threshold policies and how to properly manage these changes. + + +### Weighted Threshold + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/policies/weighted_threshold.rs) + +The `weighted_threshold` policy implements flexible multisig authorization where each signer has an assigned weight. Authorization requires that the sum of signature weights meets or exceeds a specified threshold, enabling hierarchical and role-based authorization patterns. + +Unlike `simple_threshold` where all signers are equal, `weighted_threshold` assigns different weights to signers based on their authority level. This enables sophisticated patterns like "1 admin OR 2 managers OR 3 users" within a single rule. + +The policy requires two configuration parameters: + +- **Signer Weights**: Map of signers to their weights +- **Threshold**: The minimum total weight required + + +When using threshold policies, be aware of signer set divergence issues. See the [Caveats](#caveats) section above for details on how adding or removing signers affects threshold policies and how to properly manage these changes. + + +### Spending Limit + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/policies/spending_limit.rs) + +The `spending_limit` policy enforces spending caps over time periods, enabling budget controls, allowances, and rate limiting for smart accounts. This is particularly useful for session keys, sub-accounts, and automated operations that need spending constraints. + +The policy tracks cumulative spending within a rolling time window and rejects transactions that would exceed the configured limit. The policy maintains state to track spending and automatically resets when the time window expires. + +The policy requires two configuration parameters: + +- **Limit Amount**: Maximum spending allowed in the time window +- **Time Window**: Duration in seconds for the spending period + +## Best Practices + +1. **Order Matters**: Policies execute in order; place cheaper checks first +2. **Keep Policies Focused**: Each policy should enforce one concern +3. **Test Policy Combinations**: Ensure multiple policies work together correctly +4. **Handle Errors Gracefully**: Return clear error messages from `enforce` +5. **Clean Up Storage**: Always implement `uninstall` to free storage +6. **Document Configuration**: Clearly document policy configuration parameters + +## See Also + +- [Smart Account](/stellar-contracts/accounts/smart-account) +- [Context Rules](/stellar-contracts/accounts/context-rules) +- [Signers and Verifiers](/stellar-contracts/accounts/signers-and-verifiers) +- [Authorization Flow](/stellar-contracts/accounts/authorization-flow) diff --git a/docs/content/stellar-contracts/accounts/signers-and-verifiers.mdx b/docs/content/stellar-contracts/accounts/signers-and-verifiers.mdx new file mode 100644 index 00000000..1196cd97 --- /dev/null +++ b/docs/content/stellar-contracts/accounts/signers-and-verifiers.mdx @@ -0,0 +1,602 @@ +--- +title: Signers and Verifiers +--- + +## Signers + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/smart_account) + +Signers define who can authorize operations in a smart account. The framework supports two variants that accommodate different authorization patterns and cryptographic schemes. + +```rust +#[contracttype] +pub enum Signer { + Delegated(Address), + External(Address, Bytes), +} +``` + +### Delegated + +```rust +Signer::Delegated(Address) +``` + +Delegated signers represent any Soroban address (C-account, G-account) and delegate authorization checks to that address using the built-in `require_auth_for_args()`. This enables using traditional Stellar account address (G-accounts) as signers, but also makes possible powerful composition patterns such as nested smart accounts (one smart account authorizing on behalf of another) and contract-based signers with custom authorization logic. This variant is particularly useful for building multi-level authorization hierarchies. + +```mermaid +graph TD + G1[G-account] + G2[G-account] + C1[C-account] + SA1[Smart Account] + SA2[Smart Account] + SA3[Smart Account] + SA4[Smart Account] + + G1 --> SA1 + SA3 --> SA4 + C1 --> SA2 + G2 --> SA2 + + style G1 fill:#6B9BD1 + style G2 fill:#6B9BD1 + style C1 fill:#F4D03F + style SA1 fill:#E8E8E8,stroke:#333,stroke-width:2px + style SA2 fill:#E8E8E8,stroke:#333,stroke-width:2px + style SA3 fill:#E8E8E8,stroke:#333,stroke-width:2px + style SA4 fill:#E8E8E8,stroke:#333,stroke-width:2px +``` + +#### Transaction Simulation Behavior + +However, there is a caveat when using delegated signers: authorization entries are not automatically included in transaction simulation results and clients must manually construct these entries, which adds additional implementation complexity. This limitation may be overcome with future protocol improvements such as [CAP-71](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0071.md). + +When building transactions in Soroban, clients typically simulate them first to obtain the authorization trees and nonces needed for signing. The simulation mechanism precomputes the `SorobanAuthorizedInvocation` trees that must be authorized by the `Address`es for all `require_auth`/`require_auth_for_args` checks to pass. + +The usual flow is: simulate, collect the returned auth trees and nonces, have each `Address` sign those payloads, and then submit the final transaction that combines the simulation output with the corresponding signatures. See the [official docs](https://developers.stellar.org/docs/learn/fundamentals/contract-development/contract-interactions/transaction-simulation#authorization) for details. + +When `require_auth_for_args` is called from within `__check_auth` (as with delegated signers), the authorization entry for that signer is **not included** in the simulation output. + +```rust +#[contracttype] +pub struct Signatures(pub Map); + +#[contract] +pub struct MySmartAccount; + +#[contractimpl] +impl CustomAccountInterface for MySmartAccount { + fn __check_auth( + e: Env, + payload: Hash<32>, + signatures: Signatures, + auth_contexts: Vec, + ) -> Result<(), SmartAccountError> { + for (signer, _) in signatures.0.iter() { + match signer { + // ... + Signer::Delegated(addr) => { + let payload = (payload.clone(),).into_val(e); + addr.require_auth_for_args(payload); + } + } + } + // ... + } +} +``` + +**Example Scenario** + +Consider a scenario where a target contract `CAGCDFLG4WKPYG...` requires authorization from a smart account `CBH6XACZFDCJUHX...`. The smart account will grant authorization only if the G-account `GBDZXYMJ3SLYXCY...` has signed, meaning `Map` has to contain one element that is `Signer::Delegated("GBDZXYMJ3SLYXCY...")`. + +```mermaid +graph LR + G1[G-account:
GBDZXYMJ3SLYXCY...] + SA1[Smart Account:
CBH6XACZFDCJUHX...] + TC[Target Contract:
CAGCDFLG4WKPYG...] + + TC --> |"require_auth()"| SA1 + SA1 --> |"require_auth_for_args()"| G1 + + style G1 fill:#6B9BD1 + style SA1 fill:#E8E8E8,stroke:#333,stroke-width:2px + style TC fill:#C8D9A3 +``` + +When simulating this transaction, the following authorization entry is returned. Note that `"auth"` contains a single element and the delegated signer address (G-account) is not present at all: +```json +{ + "tx": { + // ... + "auth": [ + { + "credentials": { + "address": { + "address": "CBH6XACZFDCJUHX...", // the Smart Account + "nonce": "7346653005027720525", + "signature_expiration_ledger": 0, + "signature": "void" + } + }, + "root_invocation": { + "function": { + "contract_fn": { + "contract_address": "CAGCDFLG4WKPYG...", // the Target Contract that's initially invoked + "function_name": "some_fn", + "args": [/* fn args if any */] + } + }, + "sub_invocations": [] + } + } + ] + // ... + } +} +``` + +The client implementation requires constructing two authorization entries: + +1. Replace `"signature": "void"` with the proper `Signatures(Map)` structure +2. Create the missing authorization entry for the delegated signer's `__check_auth` call + +The following typescript code demonstrates this process: + +```typescript +async function signAndSendTx( + contract: string, + fnName: string, + fnArgs: ScVal[], + signer: Keypair +) { + const baseTx = new TransactionBuilder(...) + .addOperation( + Operation.invokeContractFunction({ contract, function: fnName, args: fnArgs }), + ) + .setTimeout(600) + .build(); + + const simRes = await server.simulateTransaction(baseTx); + // we assume only one authorization is returned + const simAuth = simRes.result.auth[0]; + + const signedAuths: SorobanAuthorizationEntry[] = []; + + // 1) Construct the 1st auth entry: `Signatures(pub Map)` with `Signer::Delegated(Address)` + const sigInnerMap = ScVal.scvMap([ + new xdr.ScMapEntry({ + key: ScVal.scvVec([ + ScVal.scvSymbol("Delegated"), + Address.fromString(signer.publicKey()).toScVal(), // "GBDZXYMJ3SLYXCY..." + ]), + val: ScVal.scvBytes(""), + }), + ]); + + simAuth.credentials().address().signature(ScVal.scvVec([sigInnerMap])); + simAuth.credentials().address().signatureExpirationLedger(validUntil); + signedAuths.push(simAuth); + + // 2) Construct the 2nd auth entry for `__check_auth` and sign the invocation + const payload = HashIdPreimage.envelopeTypeSorobanAuthorization( + new HashIdPreimageSorobanAuthorization({ + networkId, + nonce: simAuth.credentials().address().nonce(), + signatureExpirationLedger: validUntil, + invocation: simAuth.rootInvocation(), + }), + ).toXDR(); + const hashed_payload = hash(payload); + + const args = new InvokeContractArgs({ + contractAddress: Address.fromString(contract).toScAddress(), + functionName: "__check_auth", + args: [ScVal.scvBytes(hashed_payload)] + }); + const invocation = new SorobanAuthorizedInvocation({ + function: SorobanAuthorizedFunction.sorobanAuthorizedFunctionTypeContractFn(args), + subInvocations: [], + }); + const signedEntry = await authorizeInvocation( + signer, + validUntil, + invocation, + signer.publicKey(), + Networks.TESTNET, + ); + signedAuths.push(signedEntry); + + // rebuild transaction with both auth entries in signedAuths + // re-simulate +} +``` + +After including both authorization entries in `signedAuths` and re-simulating the transaction, the `"auth"` array contains now two elements: + +```json +{ + "tx": { + // ... + "auth": [ + { + "credentials": { + "address": { + "address": "CBH6XACZFDCJUHX...", // the Smart Account + "nonce": "7346653005027720525", + "signature_expiration_ledger": 1256083, + "signature": { + "vec": [ + { + "map": [ + { + "key": { + "vec": [ + { + "symbol": "Delegated" + }, + { + "address": "GBDZXYMJ3SLYXCY..." // the delegated signer (G-account) + } + ] + }, + "val": { + "bytes": "" // `Bytes` value from `Map` is empty here (it's used only for the `Signer::External`) + } + } + ] + } + ] + } + } + }, + "root_invocation": { + // ... + } + }, + { + "credentials": { + "address": { + "address": "GBDZXYMJ3SLYXCY...", // the delegated signer + "nonce": "172051086", + "signature_expiration_ledger": 1256083, + "signature": { + "vec": [ + { + "map": [ + { + "key": { + "symbol": "public_key" + }, + "val": { + "bytes": "479be189dc978b8b3b463a0c6f..." + } + }, + { + "key": { + "symbol": "signature" + }, + "val": { + "bytes": "c54aae899cc29374ef81745bf46612...." + } + } + ] + } + ] + } + } + }, + "root_invocation": { + "function": { + "contract_fn": { + "contract_address": "CBH6XACZFDCJUHX...", // the Smart Account + "function_name": "__check_auth", + "args": [ + { + "bytes": "ed0cfe2903d64e5383..." // the signature payload + } + ] + } + }, + } + } + ] + // ... + } +} +``` + +#### Future Improvements + +While delegated signers currently require manual authorization entry construction, future protocol changes like [CAP-71](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0071.md) aim to streamline this process. These improvements would eliminate the need for the additional authorization entry, making delegated signers more intuitive and reducing transaction overhead. + +Despite the current complexity, delegated signers are valuable both for simple scenarios using traditional Stellar accounts (G-accounts) as signers and for advanced use cases requiring multi-level authorization or custom contract-based signing logic. + + +### External + +Each `External` signer pairs a verifier address with a public key. + +```rust +Signer::External(Address, Bytes) +``` + +In the case of external signers verification is offloaded to specialized verifier contracts. During authorization, the smart account makes a cross-contract call to the verifier contract to validate the signature, as shown in the example below where `VerifierClient::new(e, &verifier).verify()` invokes the external verifier's verification logic. + +```rust +#[contracttype] +pub struct Signatures(pub Map); + +#[contract] +pub struct MySmartAccount; + +#[contractimpl] +impl CustomAccountInterface for MySmartAccount { + fn __check_auth( + e: Env, + payload: Hash<32>, + signatures: Signatures, + auth_contexts: Vec, + ) -> Result<(), SmartAccountError> { + for (signer, sig_data) in signatures.0.iter() { + match signer { + Signer::External(verifier, key_data) => { + let sig_payload = Bytes::from_array(e, &signature_payload.to_bytes().to_array()); + if !VerifierClient::new(e, &verifier).verify( + &sig_payload, + &key_data.into_val(e), + &sig_data.into_val(e), + ) { + panic_with_error!(e, SmartAccountError::ExternalVerificationFailed) + } + } + // ... + } + } + // ... + } +} +``` + +This design separates cryptographic logic from the smart account to facilitate support for diverse schemes (ed25519, secp256k1, secp256r1, BLS, RSA, zero-knowledge proofs). It also minimizes setup costs by allowing many accounts to reuse the same verifier contracts. + +This separation provides forward compatibility: when new cryptographic curves are added on Soroban, smart accounts can adopt them immediately by referencing the appropriate verifier contract. If signature verification were embedded directly in the account, adopting a new scheme would require either upgrading the account (if upgradeable) or migrating to an entirely new account. + +**Example Scenario** + +Consider again a target contract `CAGCDFLG4WKPYG...` that requires authorization from a smart account `CBH6XACZFDCJUHX...`. This time the smart account will grant authorization only if the Ed25519 public key `2b6bad0cfdb3d4b6f2cd...` has signed, meaning `Map` has to contain one element that is `Signer::External("CDLDYJWEZSM6IAI4...", "2b6bad0cfdb3d4b6f2cd...")` and its signature. + +```mermaid +graph LR + V[Ed25519 Verifier:
CDLDYJWEZSM6IAI4...] + SA1[Smart Account:
CBH6XACZFDCJUHX...] + TC[Target Contract:
CAGCDFLG4WKPYG...] + + TC --> |"require_auth()"| SA1 + SA1 --> |"verify()"| V + + style V fill:#16D9AA1 + style SA1 fill:#E8E8E8,stroke:#333,stroke-width:2px + style TC fill:#C8D9A3 +``` + +In contrast to `Delegated` signers, constructing the auth entry for an `External` signer is straightforward: +```json +{ + "tx": { + // ... + "auth": [ + { + "credentials": { + "address": { + "address": "CBH6XACZFDCJUHX...", // the Smart Account + "nonce": "7346653005027720525", + "signature_expiration_ledger": 1256083, + "signature": { + "vec": [ + { + "map": [ + { + "key": { + "vec": [ + { + "symbol": "External" + }, + { + "address": "CDLDYJWEZSM6IAI4..." // the Ed25519 Verifier + }, + { + "bytes": "2b6bad0cfdb3d4b6f2cd..." // Signer's public key + } + ] + }, + "val": { + "bytes": "6ead27ab6e8cab36..." // Signer's signature + } + } + ] + } + ] + } + } + }, + "root_invocation": { + // ... + } + } + ] + // ... + } +} +``` + +See the [Verifiers](#verifiers) section below for architecture details and the `Verifier` trait that external signers rely on. + +## Signer Management + +The [`SmartAccount`](https://github.com/OpenZeppelin/stellar-contracts/blob/main/packages/accounts/src/smart_account/mod.rs) trait provides functions for managing signers within context rules: + +### Adding Signers + +```rust +fn add_signer( + e: &Env, + context_rule_id: u32, + signer: Signer, +); +``` + +Adds a signer to an existing context rule. The rule must not exceed the maximum of 15 signers. + + +**Important:** +When adding signers to rules with threshold policies, administrators must manually update policy thresholds to maintain security guarantees. See the Policies documentation for details. + + +### Removing Signers + +```rust +fn remove_signer( + e: &Env, + context_rule_id: u32, + signer: Signer, +); +``` + +Removes a signer from an existing context rule. The rule must maintain at least one signer OR one policy after removal. + + +**Important:** +When removing signers from rules with threshold policies, verify that the threshold remains achievable with the remaining signers. + + +## Verifiers + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/verifiers) + +Verifiers serve as cryptographic oracles for signature validation: specialized, trusted contracts that validate signatures on behalf of smart accounts. They are the foundation for `External` signers, providing the actual signature verification logic. + +Each signer is represented as a `(verifier_address, public_key)` pair, where the verifier address points to shared verification logic and the public key identifies the specific signer. A single verifier contract can validate signatures for any number of keys. + +This architecture separates verification logic from smart accounts, enabling a clean division of concerns and promoting code reuse across the ecosystem. + +The `Verifier` trait defines the interface for verifier contracts: + +```rust +pub trait Verifier { + type KeyData: FromVal; + type SigData: FromVal; + + /// # Arguments + /// + /// * `e` - Access to the Soroban environment. + /// * `hash` - The hash of the data that was signed (typically 32 bytes). + /// * `key_data` - The public key data in the format expected by this verifier. + /// * `sig_data` - The signature data in the format expected by this verifier. + fn verify(e: &Env, hash: Bytes, key_data: Self::KeyData, sig_data: Self::SigData) -> bool; +} +``` +The trait uses associated types to allow different verifiers to define their own data structures for keys and signatures. + +### Advantages of the Verifier Pattern + +**No Setup Costs**: Once a verifier is deployed, new keys can be used immediately without any on-chain setup. Users simply reference the verifier address and provide their public key when creating signers. + +**Cryptographic Flexibility**: The verifier pattern supports diverse cryptographic schemes from standard curves to emerging authentication methods like zero-knowledge proofs and email-based signing. Developers can implement custom verifiers for specialized cryptographic schemes. + +**Address-less Keys**: Keys remain separate from account addresses, maintaining clear boundaries between accounts (which hold assets) and the keys that control them. This separation improves security and flexibility. + +**Shared Security**: Verification logic is centralized in well-audited, immutable contracts. The entire network benefits from shared security guarantees, reduced deployment overhead, community-reviewed implementations, and consistent verification behavior. + +**Ecosystem Trust**: Well-known verifier addresses build trust as they are used across many accounts. A small set of thoroughly audited verifiers can serve the entire ecosystem. + +### Implementation Recommendations + +Verifiers logic should be implemented as pure verification functions with the following characteristics: + +- **Stateless**: No internal state that could be manipulated +- **Immutable**: Not upgradeable once deployed, minimizing the trust +- **Deterministic**: Same inputs always produce the same output +- **Efficient**: Optimized for gas costs and performance + +## Example Verifiers + +The "accounts" package provides utility functions for implementing **Ed25519** and **WebAuthn** verifiers: + +### Ed25519 + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts/src/verifiers/ed25519.rs) + +The `ed25519` verifier utilities package provides standard Ed25519 signature verification for external signers in smart accounts. This enables using Ed25519 keys that are not native Soroban addresses, such as keys generated by external systems or hardware wallets. + +Ed25519 public keys must be exactly **32 bytes**: + +```rust +let public_key = Bytes::from_array(e, &[ + 0x12, 0x34, 0x56, 0x78, // ... 32 bytes total +]); +``` + +Ed25519 signatures must be exactly **64 bytes**: + +```rust +let signature = Bytes::from_array(e, &[ + 0xab, 0xcd, 0xef, 0x01, // ... 64 bytes total +]); +``` + +### WebAuthn (Passkeys) + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/blob/main/packages/accounts/src/verifiers/webauthn.rs) + +[WebAuthn (Web Authentication)](https://www.w3.org/TR/webauthn-2/) is a web standard for passwordless authentication that allows users to authenticate using: + +- **Passkeys**: Biometric authentication (Face ID, Touch ID, Windows Hello) +- **Hardware Keys**: Physical security keys (YubiKey, Titan Key) +- **Platform Authenticators**: Built-in device authenticators + +WebAuthn uses secp256r1 (P-256) public keys, which must be exactly **65 bytes** in uncompressed format: + +```rust +let public_key = Bytes::from_array(e, &[ + 0x04, // Uncompressed point indicator + // ... 32 bytes for X coordinate + // ... 32 bytes for Y coordinate +]); +``` + +Unlike simpler signature schemes, WebAuthn signature data is a complex structure that must be XDR-encoded. The signature data contains three components: + +```rust +#[contracttype] +pub struct WebAuthnSigData { + /// The cryptographic signature (64 bytes for secp256r1) + pub signature: BytesN<64>, + /// Raw authenticator data from the WebAuthn response + pub authenticator_data: Bytes, + /// Raw client data JSON from the WebAuthn response + pub client_data: Bytes, +} +``` + +Components: + +- **`signature`**: The secp256r1 signature (64 bytes) +- **`authenticator_data`**: Binary data from the authenticator (minimum 37 bytes), containing: + - RP ID hash (32 bytes) + - Flags byte (1 byte) - indicates user presence, user verification, and backup state + - Signature counter (4 bytes) +- **`client_data`**: JSON string from the browser/platform (max 1024 bytes), containing: + - `type`: Must be `"webauthn.get"` for authentication + - `challenge`: Base64url-encoded signature payload + - `origin`: The origin where authentication occurred + +The `sig_data` parameter passed to the verifier must be the XDR-encoded representation of this structure to ensure proper serialization and deserialization. + +## See Also + +- [Smart Account](/stellar-contracts/accounts/smart-account) +- [Context Rules](/stellar-contracts/accounts/context-rules) +- [Policies](/stellar-contracts/accounts/policies) +- [Authorization Flow](/stellar-contracts/accounts/authorization-flow) diff --git a/docs/content/stellar-contracts/accounts/smart-account.mdx b/docs/content/stellar-contracts/accounts/smart-account.mdx new file mode 100644 index 00000000..e236be6f --- /dev/null +++ b/docs/content/stellar-contracts/accounts/smart-account.mdx @@ -0,0 +1,89 @@ +--- +title: Smart Accounts +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts) + +Smart accounts are contracts that manage the composition of authorization intents coming from multiple sources, such as policies, signing keys from different cryptographic curves or other Soroban accounts. This lays the ground for flexible combinations where multiple authorization mechanisms work together seamlessly. + +## Overview + +The [accounts](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/accounts) package provides a comprehensive smart account framework, enabling programmable authorization. Rather than hard-coding signature checks directly into the account contract, this framework organizes authorization as a composition of three core elements: context rules, signers, and policies. + +To achieve this composability, smart accounts implement the `CustomAccountInterface` and define authorization as data and behavior that can evolve over time. The framework takes a context-centric approach, separating three distinct concerns: who is allowed to act (signers), what they are allowed to do (scope or context rules), and how those permissions are enforced (policies). + +This separation is made practical by externalizing parts of the logic and state to dedicated contracts. Specifically, policies are external contracts that enforce constraints and can maintain their own state, while verifiers are external contracts that handle signature validation logic. This modular architecture enables flexibility and allows multiple smart accounts to share well-audited verification and policy logic. The Protocol 23 improvements make this design efficient, with substantially cheaper cross-contract calls enabling practical composition of multiple external contracts. + +## Core Components + +The framework separates three distinct concerns: + +- **What** (Context Rules): Defines the scope (e.g. call `transfer()` on some token, call any function of a specific contract, deploy contracts, or just any call to any contract) +- **Who** (Signers): Identifies the authorized entities (e.g. cryptographic keys, G-accounts, or other C-accounts) +- **How** (Policies): Enforces business logic and constraints (e.g. amount < 500, every month, or 2-of-3 threshold) + +```mermaid +graph LR + P1((Policy)) --- CR1 + P2((Policy)) --- CR2 + P2((Policy)) --- CR3 + P3((Policy)) --- CR3 + + subgraph SA[Smart Account] + CR1[Context Rule] + CR2[Context Rule] + CR3[Context Rule] + end + + S1["Delegated Signer
G-account"] + S2["Delegated Signer
G-account"] + S3["Delegated Signer
C-account"] + S4["External Signer
Verifier Contract || Pubkey"] + S5["External Signer
Verifier Contract || Pubkey"] + + CR1 --- S1 + CR1 --- S2 + CR2 --- S3 + CR3 --- S4 + CR3 --- S5 + + style SA fill:#E8E8E8,stroke:#333,stroke-width:2px + style S1 fill:#6B9BD1 + style S2 fill:#6B9BD1 + style S3 fill:#F4D03F + style S4 fill:#A9DA9B + style S5 fill:#A9DA9B +``` + +### Context Rules +Context rules function like routing tables for authorization. For each context, they specify scope, lifetime, and the conditions (signers and policies) that must be satisfied before execution proceeds. + +#### Examples +1. Subscription: A dapp public key (signer) can withdraw 100 USDC every month (policy) for one year (lifetime) +2. Deployment: 2-of-3 (policy) Ed25519 keys (signers) can deploy a new contract +3. Vote: 3-of-3 P256 keys (signers) can cast a vote at a specific voting contract + +For detailed documentation, see [Context Rules](/stellar-contracts/accounts/context-rules). + +### Signers and Verifiers +Signers define who can authorize operations. The framework supports both **delegated** signers (any Soroban address) and **external** signers that use specialized verifier contracts for signature validation. Verifiers can be seen as some sort of cryptographic oracles that validate signatures for external signers. + +For detailed documentation, see [Signers and Verifiers](/stellar-contracts/accounts/signers-and-verifiers). + +### Policies +Policies act as enforcement modules attached to context rules. They perform read-only prechecks and can update state to enforce limits or workflows. + +For detailed documentation, see [Policies](/stellar-contracts/accounts/policies). + +This separation allows for clean composition of authorization requirements while maintaining auditability and flexibility. + +## Authorization Flow + +Authorization is determined by matching the current call context against the account's context rules: + +1. **Rule Collection**: Retrieve all non-expired rules for the specific context type and default rules +2. **Rule Evaluation**: For each rule (newest first), authenticate signers and validate policies +3. **Policy Enforcement**: If enough signers are authenticated and policy prechecks succeed, trigger policy state changes +4. **Result**: Grant or deny authorization + +For detailed documentation, see [Authorization Flow](/stellar-contracts/accounts/authorization-flow). diff --git a/docs/content/stellar-contracts/changelog.mdx b/docs/content/stellar-contracts/changelog.mdx new file mode 100644 index 00000000..4dc87544 --- /dev/null +++ b/docs/content/stellar-contracts/changelog.mdx @@ -0,0 +1,109 @@ +--- +title: Changelog +--- + + +# [v0.4.1](https://github.com/OpenZeppelin/stellar-contracts/releases/tag/v0.4.1) - 2025-07-22 + +Added `readme.md` for each new package category: +- access +- contract-utils +- macros +- tokens + +[Changes][v0.4.1] + + + +# [v0.4.0](https://github.com/OpenZeppelin/stellar-contracts/releases/tag/v0.4.0) - 2025-07-22 + +Restructures the crates to group them under: +- `access` +- `tokens` +- `macros` +- `contract-utils` + +None of the audited code has changed. This release consists only of moving things around and restructuring + +[Changes][v0.4.0] + + + +# [v0.3.0](https://github.com/OpenZeppelin/stellar-contracts/releases/tag/v0.3.0) - 2025-07-03 + +This release is audited, you can find the audit report [here](https://github.com/OpenZeppelin/stellar-contracts/blob/main/audits/Stellar%20Contracts%20Library%20v0.3.0-rc.2%20Audit.pdf). + +## Breaking Changes +- Fungible module got reworked, see: [#234](https://github.com/OpenZeppelin/stellar-contracts/pull/234) + +## What's Changed +* Access control by [@ozgunozerk](https://github.com/ozgunozerk) in [#214](https://github.com/OpenZeppelin/stellar-contracts/pull/214) +* Ownable by [@ozgunozerk](https://github.com/ozgunozerk) in [#216](https://github.com/OpenZeppelin/stellar-contracts/pull/216) +* Error numbering by [@ozgunozerk](https://github.com/ozgunozerk) in [#226](https://github.com/OpenZeppelin/stellar-contracts/pull/226) +* Merkle Proofs by [@brozorec](https://github.com/brozorec) in [#222](https://github.com/OpenZeppelin/stellar-contracts/pull/222) +* SacAdmin by [@brozorec](https://github.com/brozorec) in [#215](https://github.com/OpenZeppelin/stellar-contracts/pull/215) +* Merkle Distributor by [@brozorec](https://github.com/brozorec) in [#229](https://github.com/OpenZeppelin/stellar-contracts/pull/229) +* Royalty by [@ozgunozerk](https://github.com/ozgunozerk) in [#221](https://github.com/OpenZeppelin/stellar-contracts/pull/221) +* SAC Admin Generic by [@brozorec](https://github.com/brozorec) in [#232](https://github.com/OpenZeppelin/stellar-contracts/pull/232) +* Fungible refactor by [@ozgunozerk](https://github.com/ozgunozerk) in [#234](https://github.com/OpenZeppelin/stellar-contracts/pull/234) +* Fix binver by [@brozorec](https://github.com/brozorec) in [#224](https://github.com/OpenZeppelin/stellar-contracts/pull/224) +* Allowlist and blocklist by [@ozgunozerk](https://github.com/ozgunozerk) in [#237](https://github.com/OpenZeppelin/stellar-contracts/pull/237) + +## New Contributors +* [@orangelash](https://github.com/orangelash) made their first contribution in [#331](https://github.com/OpenZeppelin/stellar-contracts/pull/331) + +**Full Changelog**: https://github.com/OpenZeppelin/stellar-contracts/compare/v0.2.0...v0.3.0 + +[Changes][v0.3.0] + + + +# [v0.2.0](https://github.com/OpenZeppelin/stellar-contracts/releases/tag/v0.2.0) - 2025-05-12 + +This release is audited, you can find the audit report [here](https://github.com/OpenZeppelin/stellar-contracts/blob/main/audits/2025-05-v0.2.0.pdf). + +In this release, you can find: + +- Non-Fungible Token base implementation with the following extensions: + - enumerable + - consecutive + - burnable +- Upgradeable and Migrations utilities +- Capped extension for Fungible Token +- Showcase examples: + - nft-consecutive + - nft-enumerable + - nft-sequential-minting + - fungible-capped + - upgradeable + +[Changes][v0.2.0] + + + +# [v0.1.0](https://github.com/OpenZeppelin/stellar-contracts/releases/tag/v0.1.0) - 2025-02-21 + +The first release of OpenZeppelin Contracts for Stellar Soroban. + +This release is audited, you can find the audit report [here](https://github.com/OpenZeppelin/stellar-contracts/blob/main/audits/2025-02-v0.1.0-rc.pdf) + +In this release, you can find: +- Fungible Token standard (similar to ERC20) implemented for Stellar Soroban, compliant with SEP-41 +- The following extensions for the Fungible Token standard: + - Mintable + - Burnable + - Metadata +- `Pausable` utility for your contracts. +- Examples folder to showcase what's possible: + - fungible-pausable + - fungible-token-interface + - pausable + +[Changes][v0.1.0] + + +[v0.4.1]: https://github.com/OpenZeppelin/stellar-contracts/compare/v0.4.0...v0.4.1 +[v0.4.0]: https://github.com/OpenZeppelin/stellar-contracts/compare/v0.3.0...v0.4.0 +[v0.3.0]: https://github.com/OpenZeppelin/stellar-contracts/compare/v0.2.0...v0.3.0 +[v0.2.0]: https://github.com/OpenZeppelin/stellar-contracts/compare/v0.1.0...v0.2.0 +[v0.1.0]: https://github.com/OpenZeppelin/stellar-contracts/tree/v0.1.0 diff --git a/docs/content/stellar-contracts/get-started.mdx b/docs/content/stellar-contracts/get-started.mdx new file mode 100644 index 00000000..477661de --- /dev/null +++ b/docs/content/stellar-contracts/get-started.mdx @@ -0,0 +1,7 @@ +--- +title: Get Started +--- + +Not sure where to start? Use the interactive generator below to bootstrap your contract and find about the components offered in OpenZeppelin Smart Contracts Suite for Stellar. You can also access the code generator from [here](https://wizard.openzeppelin.com/stellar). + + diff --git a/docs/content/stellar-contracts/helpers/default-impl-macro.mdx b/docs/content/stellar-contracts/helpers/default-impl-macro.mdx new file mode 100644 index 00000000..0733f90a --- /dev/null +++ b/docs/content/stellar-contracts/helpers/default-impl-macro.mdx @@ -0,0 +1,95 @@ +--- +title: Default Implementation Macro +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/default-impl-macro) + +## Overview + +The `#[default_impl]` macro is a utility that simplifies the implementation of OpenZeppelin Stellar +contract traits by automatically generating default implementations for trait methods. This allows developers +to focus only on overriding the methods they need to customize, while the macro handles the rest. + +## Background + +When using Soroban’s `#[contractimpl]` macro, all methods (including default implementations) must be explicitly +included in the implementation block for them to be accessible to the generated client. This is due to how +Rust macros work - they cannot access default implementations of trait methods that are not in the scope of the macro. + +The `#[default_impl]` macro solves this problem by automatically generating the missing default implementations +for OpenZeppelin Stellar traits. + +## Supported Traits + +The `#[default_impl]` macro supports the following OpenZeppelin Stellar traits: + +* `FungibleToken` +* `FungibleBurnable` +* `NonFungibleToken` +* `NonFungibleBurnable` +* `NonFungibleEnumerable` +* `AccessControl` +* `Ownable` + +The `#[default_impl]` macro intentionally does not support the following traits: + +* `FungibleAllowlist` +* `FungibleBlocklist` +* `NonFungibleRoyalties` + +This limitation is by design: authorization configurations require specific implementation tailored to +each project’s security requirements. By requiring manual implementation of these traits, we ensure +developers carefully consider and explicitly define their authorization logic rather than relying on generic defaults. + +## Usage + +To use the `#[default_impl]` macro, place it above the `#[contractimpl]` macro when implementing one of the supported traits: + +```rust +#[default_impl] // IMPORTANT: place this above `#[contractimpl]` +#[contractimpl] +impl NonFungibleToken for MyContract { + type ContractType = Base; + + // Only override the methods you need to customize + // All other methods will be automatically implemented with their default behavior +} +``` + +## How It Works + +The `#[default_impl]` macro: + +1. Identifies which trait is being implemented +2. Determines which methods are explicitly defined by the user +3. Uses the user defined methods to overwrite the default implementations +4. Fills the rest of the methods (not defined by the user) with default implementations +5. Adds any necessary imports for the trait + +This process ensures that all trait methods are available to the client generated by `#[contractimpl]`, while allowing developers to only write the code they need to customize. + +## Examples + +### Fungible Token Example + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env}; +use stellar_tokens::fungible::FungibleToken; +use stellar_macros::default_impl; + +#[contract] +pub struct MyToken; + +#[default_impl] +#[contractimpl] +impl FungibleToken for MyToken { + type ContractType = Base; + + // Only override methods that need custom behavior + fn transfer(e: &Env, from: Address, to: Address, amount: i128) { + // custom transfer logic here + } + + // All other FungibleToken methods will be automatically implemented +} +``` diff --git a/docs/content/stellar-contracts/index.mdx b/docs/content/stellar-contracts/index.mdx new file mode 100644 index 00000000..2ed24270 --- /dev/null +++ b/docs/content/stellar-contracts/index.mdx @@ -0,0 +1,69 @@ +--- +title: Stellar Smart Contracts Suite +--- + +Explore our comprehensive suite of secure and scalable smart contract utilities for Stellar Soroban. +Our libraries provide robust implementations for fungible and non-fungible tokens, along with powerful tools +for access control and contract management. + +## Accounts + +* **[Smart Account](/stellar-contracts/accounts/smart-account)**: Context-centric framework for Soroban smart accounts composing authorization intents through signers and policies. + +## Tokens + +* **[Fungible Tokens](/stellar-contracts/tokens/fungible/fungible)**: Digital assets representing a fixed or dynamic supply of identical units. +* **[Non-Fungible Tokens (NFTs)](/stellar-contracts/tokens/non-fungible/non-fungible)**: Unique digital assets with verifiable ownership. +* **[Real World Assets (RWAs)](/stellar-contracts/tokens/rwa/rwa)**: Digital assets representing real-world assets. +* **[Vault](/stellar-contracts/tokens/vault/vault)**: Digital assets representing a fixed or dynamic supply of identical units. + +## Access Control + +* **[Ownable](/stellar-contracts/access/ownable)**: A simple mechanism with a single account authorized for all privileged actions. +* **[Role-Based Access Control](/stellar-contracts/access/access-control)**: A flexible mechanism with distinct roles for each privileged action. + +## Utilities + +* **[Pausable](/stellar-contracts/utils/pausable)**: Pause and unpause contract functions, useful for emergency response. +* **[Upgradeable](/stellar-contracts/utils/upgradeable)**: Manage contract upgrades and data migrations seamlessly. +* **[Cryptography](/stellar-contracts/utils/crypto/crypto)**: A set of cryptographic primitives and utilities for Soroban contracts. + +## Security and Audits + +Our contracts are built with security as a top priority. You can find our audit reports [here](https://github.com/OpenZeppelin/stellar-contracts/tree/main/audits). + +## Error Codes +In Stellar Soroban, each error variant is assigned an integer. To prevent duplication of error codes, +we use the following convention: + +* Fungible: `1XX` +* Non-Fungible: `2XX` +* RWA: `3XX` +* Vault: `4XX` + +Any future tokens will continue from `5XX`, `6XX`, and so on. + +Similarly, utilities and other modules have their own error codes: + +* Utilities: `1XXX` + * Pausable: `10XX` + * Upgradeable: `11XX` + * Merkle Distributor: `13XX` + * Crypto: `14XX` + * Math: `15XX` +* Access: `2XXX` + * Access Control: `20XX` + * Ownable: `21XX` + * Role Transfer (internal common module for 2-step role transfer): `22XX` +* Accounts: `3XXX` + +## Important Notes +As a deliberate design choice, this library manages the TTL for temporary and persistent storage items. +To provide flexibility to the owner of the contract, this library deliberately does not manage the TTL for instance storage items. +It is the responsibility of the developer to manage the TTL for instance storage items. + +## Audits +You can find our audit reports [here](https://github.com/OpenZeppelin/stellar-contracts/tree/main/audits). + +## Get Started +Get started [here](/stellar-contracts/get-started). diff --git a/docs/content/stellar-contracts/tokens/fungible/fungible.mdx b/docs/content/stellar-contracts/tokens/fungible/fungible.mdx new file mode 100644 index 00000000..372a4991 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/fungible/fungible.mdx @@ -0,0 +1,154 @@ +--- +title: Fungible Token +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible) + +Fungible tokens represent assets where each unit is identical and interchangeable, such as currencies, +commodities, or utility tokens. On Stellar, you can create fungible tokens where each token has the +same value and properties, with balances and ownership tracked through Soroban smart contracts. + +## Overview + +The [fungible](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible) +module provides three different Fungible Token variants that differ in how certain features like +token transfers and approvals are handled: + +The module provides several implementation options to suit different use cases: + +1. **Base implementation** (`FungibleToken` with `Base` contract type): Suitable for most standard token use cases. +2. **AllowList extension** (`FungibleToken` with `AllowList` contract type): For tokens that require an allowlist mechanism to control who can transfer tokens. +3. **BlockList extension** (`FungibleToken` with `BlockList` contract type): For tokens that need to block specific addresses from transferring tokens. + +These implementations share core functionality and a common interface, exposing identical contract functions as entry-points. However, the extensions provide specialized behavior by overriding certain functions to implement their specific requirements. + +## Usage + +We’ll create a simple token for a game’s in-game currency. Players can earn tokens by completing tasks, +and they can spend tokens on in-game items. The contract owner can mint new tokens as needed, +and players can transfer tokens between accounts. + +Here’s what a basic fungible token contract might look like: + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env, String}; +use stellar_tokens::fungible::{burnable::FungibleBurnable, Base, ContractOverrides, FungibleToken}; +use stellar_access::ownable::{self as ownable, Ownable}; +use stellar_macros::{default_impl, only_owner}; + +#[contract] +pub struct GameCurrency; + +#[contractimpl] +impl GameCurrency { + pub fn __constructor(e: &Env, initial_owner: Address) { + // Set token metadata + Base::set_metadata( + e, + 8, // 8 decimals + String::from_str(e, "Game Currency"), + String::from_str(e, "GCUR"), + ); + + // Set the contract owner + ownable::set_owner(e, &initial_owner); + } + + #[only_owner] + pub fn mint_tokens(e: &Env, to: Address, amount: i128) { + // Mint tokens to the recipient + Base::mint(e, &to, amount); + } +} + +#[default_impl] +#[contractimpl] +impl FungibleToken for GameCurrency { + type ContractType = Base; +} + +#[default_impl] +#[contractimpl] +impl FungibleBurnable for GameCurrency {} +``` + +## Extensions + +The following optional extensions are provided: + +### - Burnable +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/extensions/burnable) + +The `FungibleBurnable` trait extends the `FungibleToken` trait to provide the capability to burn tokens. +To fully comply with the SEP-41 specification, a contract must implement both the `FungibleToken` +and `FungibleBurnable` traits. + +### - Capped +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/extensions/capped) + +Unlike other extensions, the capped extension does not expose a separate trait. Instead, +it offers helper functions designed to assist in implementing the mint function, enforcing a supply cap. + +### - AllowList +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/extensions/allowlist) + +The `FungibleAllowList` trait extends the `FungibleToken` trait to provide an allowlist mechanism that +can be managed by an authorized account. This extension ensures that only allowed accounts can +transfer/receive tokens or approve token transfers. + +### - BlockList +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/extensions/blocklist) + +The `FungibleBlockList` trait extends the `FungibleToken` trait to provide a blocklist mechanism that +can be managed by an authorized account. This extension ensures that blocked accounts cannot transfer/receive +tokens, or approve token transfers. + +## Stellar Asset Contract (SAC) + +The Stellar Asset Contract (SAC) is a special built-in implementation of [CAP-46-6 Smart Contract Standardized Asset](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-06.md) and [SEP-41 Token Interface](https://developers.stellar.org/docs/tokens/token-interface). The SAC acts as a bridge between traditional Stellar assets and Soroban smart contracts. + +SAC automatically wraps existing Stellar assets (like USDC, XLM, or any issued asset) so they can be used in smart contracts. Think of it as the "smart contract version" of any Stellar asset. + +Key points to know: +- Every Stellar asset gets its own SAC instance with a deterministic contract address +- No bridging or wrapping tokens needed - it's the same asset, just accessible via smart contracts +- Anyone can deploy a SAC for any asset (the original issuer doesn't need to be involved) +- When you transfer between Stellar accounts and contracts, the balances are stored differently but represent the same underlying asset +- SAC implements the same SEP-41 interface as OpenZeppelin fungible tokens + +When to use SAC vs OpenZeppelin tokens: +- Use SAC: When you want to interact with existing Stellar assets (USDC, XLM, etc.) in your smart contracts +- Use OpenZeppelin: When creating new custom tokens with specialized logic, access controls, or unique tokenomics + +For example, if you want to build a DeFi protocol that uses USDC, you'd deploy the SAC for USDC rather than creating a new token. Users can then interact with the same USDC they hold in their Stellar wallets directly through your smart contract. + +Every SAC has an admin interface for privileged operations like minting or clawback. The OpenZeppelin fungible token module provides utilities that enable you to set a separate contract as the admin for these operations: + +### - SAC Admin Generic +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/utils/sac_admin_generic) + +Provides generic admin functionality similar to the Stellar Asset Contract (SAC). This approach leverages the `__check_auth` function to handle authentication and authorization logic while maintaining a unified interface. + +For detailed documentation, see [SAC Admin Generic](/stellar-contracts/tokens/fungible/sac-admin-generic). + +### - SAC Admin Wrapper +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/utils/sac_admin_wrapper) + +Provides a wrapper around the SAC admin functionality for easier integration. This approach defines specific entry points for each admin function and forwards calls to the corresponding SAC functions. + +For detailed documentation, see [SAC Admin Wrapper](/stellar-contracts/tokens/fungible/sac-admin-wrapper). + +## Compatibility and Compliance + +The module is designed to ensure full compatibility with SEP-0041. It also closely mirrors the Ethereum ERC-20 +standard, facilitating cross-ecosystem familiarity and ease of use. + +To comply with the SEP-41 specification, a contract must implement both the `FungibleToken` and +`FungibleBurnable` traits. These traits together provide all the necessary methods to conform to +`soroban_sdk::token::TokenInterface`. + +## TTL Management + +The library handles the TTL (Time-To-Live) of only `temporary` and `persistent` storage entries declared by the library. The `instance` TTL management is left to the implementor due to flexibility. The library exposes default values for extending the TTL: `INSTANCE_TTL_THRESHOLD` and `INSTANCE_EXTEND_AMOUNT`. + +For a comprehensive understanding of Soroban's three storage types (`temporary`, `persistent`, and `instance`) and their archival behavior, see the [State Archival documentation](https://developers.stellar.org/docs/learn/fundamentals/contract-development/storage/state-archival#contract-data-type-descriptions). diff --git a/docs/content/stellar-contracts/tokens/fungible/sac-admin-generic.mdx b/docs/content/stellar-contracts/tokens/fungible/sac-admin-generic.mdx new file mode 100644 index 00000000..f423c6e3 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/fungible/sac-admin-generic.mdx @@ -0,0 +1,197 @@ +--- +title: SAC Admin Generic +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/utils/sac_admin_generic) + +## Overview + +The Stellar Asset Contract (SAC) Admin Generic module provides a way to implement custom administrative +functionality for Stellar Asset Contracts (SACs) using the generic approach. This approach leverages the +`__check_auth` function to handle authentication and authorization logic while maintaining a unified +interface for both user-facing and admin functions. + +## Key Concepts + +When a classic Stellar asset is ported to Soroban, it is represented by a SAC - a smart contract that provides +both user-facing and administrative functions for asset management. SACs expose standard functions for handling +fungible tokens, such as `transfer`, `approve`, `burn`, etc. Additionally, they include administrative functions +(`mint`, `clawback`, `set_admin`, `set_authorized`) that are initially restricted to the issuer (a G-account). + +The `set_admin` function enables transferring administrative control to a custom contract, allowing for more +complex authorization logic. This flexibility opens up possibilities for implementing custom rules, such as +role-based access control, two-step admin transfers, mint rate limits, and upgradeability. + +## Generic Approach + +The Generic approach to SAC Admin implementation: + +* Leverages the `__check_auth` function to handle authentication and authorization logic +* Maintains a unified interface for both user-facing and admin functions +* Allows for injecting any custom authorization logic +* Requires a more sophisticated authorization mechanism + +### Example Implementation + +Here’s a simplified example of a SAC Admin Generic contract: + +```rust +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum SACAdminGenericError { + Unauthorized = 1, + InvalidContext = 2, + MintingLimitExceeded = 3, +} + +#[contracttype] +#[derive(Clone)] +pub struct Signature { + pub public_key: BytesN<32>, + pub signature: BytesN<64>, +} + +#[contracttype] +pub enum SacDataKey { + Chief, + Operator(BytesN<32>), // -> true/false + MintingLimit(BytesN<32>), // -> (max_limit, curr) +} + +#[contract] +pub struct SacAdminExampleContract; + +#[contractimpl] +impl SacAdminExampleContract { + pub fn __constructor(e: Env, sac: Address, chief: BytesN<32>, operator: BytesN<32>) { + set_sac_address(&e, &sac); + e.storage().instance().set(&SacDataKey::Chief, &chief); + e.storage().instance().set(&SacDataKey::Operator(operator.clone()), &true); + e.storage() + .instance() + .set(&SacDataKey::MintingLimit(operator), &(1_000_000_000i128, 0i128)); + } + + pub fn get_sac_address(e: &Env) -> Address { + get_sac_address(e) + } +} +``` + +### Custom Authorization Logic + +The key feature of the Generic approach is the ability to implement custom authorization logic in the `__check_auth` +function: + +```rust +use soroban_sdk::{ + auth::Context, CustomAccountInterface, + contract, contracterror, contractimpl, contracttype, + crypto::Hash, + Address, BytesN, Env, IntoVal, Val, Vec, +}; + +#[contractimpl] +impl CustomAccountInterface for SacAdminExampleContract { + type Error = SACAdminGenericError; + type Signature = Signature; + + fn __check_auth( + e: Env, + payload: Hash<32>, + signature: Self::Signature, + auth_context: Vec, + ) -> Result<(), SACAdminGenericError> { + // authenticate + e.crypto().ed25519_verify( + &signature.public_key, + &payload.clone().into(), + &signature.signature, + ); + let caller = signature.public_key.clone(); + + // extract from context and check required permissions for every function + for ctx in auth_context.iter() { + let context = match ctx { + Context::Contract(c) => c, + _ => return Err(SACAdminGenericError::InvalidContext), + }; + + match extract_sac_contract_context(&e, &context) { + SacFn::Mint(amount) => { + // ensure caller has required permissions + ensure_caller_operator(&e, &SacDataKey::Operator(caller.clone()))?; + // ensure operator has minting limit + ensure_minting_limit(&e, &caller, amount)?; + } + SacFn::Clawback(_amount) => { + // ensure caller has required permissions + ensure_caller_operator(&e, &SacDataKey::Operator(caller.clone()))?; + } + SacFn::SetAuthorized(_) => { + // ensure caller has required permissions + ensure_caller_operator(&e, &SacDataKey::Operator(caller.clone()))?; + } + SacFn::SetAdmin => { + // ensure caller has required permissions + ensure_caller_chief(&e, &caller, &SacDataKey::Chief)?; + } + SacFn::Unknown => { + // ensure only chief can call other functions + ensure_caller_chief(&e, &caller, &SacDataKey::Chief)? + } + } + } + + Ok(()) + } +} + +// Helper functions +fn ensure_caller_chief>( + e: &Env, + caller: &BytesN<32>, + key: &K, +) -> Result<(), SACAdminGenericError> { + let operator: BytesN<32> = e.storage().instance().get(key).expect("chief or operator not set"); + if *caller != operator { + return Err(SACAdminGenericError::Unauthorized); + } + Ok(()) +} + +fn ensure_caller_operator>( + e: &Env, + key: &K, +) -> Result<(), SACAdminGenericError> { + match e.storage().instance().get::<_, bool>(key) { + Some(is_op) if is_op => Ok(()), + _ => Err(SACAdminGenericError::Unauthorized), + } +} +``` + +## Benefits and Trade-offs + +### Benefits + +* Maintains a unified interface for both user-facing and admin functions +* Allows for complex authorization logic +* Provides flexibility in implementing custom rules + +### Trade-offs + +* Requires a more sophisticated authorization mechanism +* More complex to implement compared to the wrapper approach +* Requires understanding of the Soroban authorization system + +## Full Example + +A complete example implementation can be found in the +[sac-admin-generic example](https://github.com/OpenZeppelin/stellar-contracts/tree/main/examples/sac-admin-generic). + +## See Also + +* [SAC Admin Wrapper](/stellar-contracts/tokens/fungible/sac-admin-wrapper) +* [Fungible Token](/stellar-contracts/tokens/fungible/fungible) diff --git a/docs/content/stellar-contracts/tokens/fungible/sac-admin-wrapper.mdx b/docs/content/stellar-contracts/tokens/fungible/sac-admin-wrapper.mdx new file mode 100644 index 00000000..9bec9a6f --- /dev/null +++ b/docs/content/stellar-contracts/tokens/fungible/sac-admin-wrapper.mdx @@ -0,0 +1,122 @@ +--- +title: SAC Admin Wrapper +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/fungible/utils/sac_admin_wrapper) + +## Overview + +The Stellar Asset Contract (SAC) Admin Wrapper module provides a way to implement custom administrative functionality for Stellar Asset Contracts (SACs) using the wrapper approach. This approach defines specific entry points for each admin function and forwards calls to the corresponding SAC functions, providing a straightforward and modular design. + +## Key Concepts + +When a classic Stellar asset is ported to Soroban, it is represented by a SAC - a smart contract that provides both user-facing and administrative functions for asset management. SACs expose standard functions for handling fungible tokens, such as `transfer`, `approve`, `burn`, etc. Additionally, they include administrative functions (`mint`, `clawback`, `set_admin`, `set_authorized`) that are initially restricted to the issuer (a G-account). + +The `set_admin` function enables transferring administrative control to a custom contract, allowing for more complex authorization logic. This flexibility opens up possibilities for implementing custom rules, such as role-based access control, two-step admin transfers, mint rate limits, and upgradeability. + +## Wrapper Approach + +The Wrapper approach to SAC Admin implementation: + +* Acts as a middleware, defining specific entry points for each admin function +* Forwards calls to the corresponding SAC functions +* Applies custom logic before forwarding the call +* Provides a straightforward and modular design +* Separates user-facing and admin interfaces + +### SACAdminWrapper Trait + +The `SACAdminWrapper` trait defines the interface for the wrapper approach: + +```rust +pub trait SACAdminWrapper { + fn set_admin(e: Env, new_admin: Address, operator: Address); + fn set_authorized(e: Env, id: Address, authorize: bool, operator: Address); + fn mint(e: Env, to: Address, amount: i128, operator: Address); + fn clawback(e: Env, from: Address, amount: i128, operator: Address); +} +``` + +### Example Implementation + +Here’s a simplified example of a SAC Admin Wrapper contract using the OpenZeppelin access control library: + +```rust +#[contract] +pub struct ExampleContract; + +#[contractimpl] +impl ExampleContract { + pub fn __constructor( + e: &Env, + default_admin: Address, + manager1: Address, + manager2: Address, + sac: Address, + ) { + access_control::set_admin(e, &default_admin); + + // create a role "manager" and grant it to `manager1` + access_control::grant_role_no_auth(e, &default_admin, &manager1, &symbol_short!("manager")); + + // grant it to `manager2` + access_control::grant_role_no_auth(e, &default_admin, &manager2, &symbol_short!("manager")); + + fungible::sac_admin_wrapper::set_sac_address(e, &sac); + } +} + +#[contractimpl] +impl SACAdminWrapper for ExampleContract { + #[only_admin] + fn set_admin(e: Env, new_admin: Address, _operator: Address) { + fungible::sac_admin_wrapper::set_admin(&e, &new_admin); + } + + #[only_role(operator, "manager")] + fn set_authorized(e: Env, id: Address, authorize: bool, operator: Address) { + fungible::sac_admin_wrapper::set_authorized(&e, &id, authorize); + } + + #[only_role(operator, "manager")] + fn mint(e: Env, to: Address, amount: i128, operator: Address) { + fungible::sac_admin_wrapper::mint(&e, &to, amount); + } + + #[only_role(operator, "manager")] + fn clawback(e: Env, from: Address, amount: i128, operator: Address) { + fungible::sac_admin_wrapper::clawback(&e, &from, amount); + } +} +``` + +### Integration with Access Control + +The wrapper approach works particularly well with the OpenZeppelin access control library, allowing for role-based access control to be applied to each admin function: + +* `#[only_admin]`: Restricts the function to be called only by the admin +* `#[only_role(operator, "manager")]`: Restricts the function to be called only by addresses with the "manager" role + +## Benefits and Trade-offs + +### Benefits + +* Simpler to implement compared to the generic approach +* More flexible in terms of function-specific authorization +* Works well with role-based access control +* Clear separation of concerns + +### Trade-offs + +* Requires additional entry points for each admin function +* Splits user-facing and admin interfaces +* May require more code for complex authorization scenarios + +## Full Example + +A complete example implementation can be found in the [sac-admin-wrapper example](https://github.com/OpenZeppelin/stellar-contracts/tree/main/examples/sac-admin-wrapper). + +## See Also + +* [SAC Admin Generic](/stellar-contracts/tokens/fungible/sac-admin-generic) +* [Fungible Token](/stellar-contracts/tokens/fungible/fungible) diff --git a/docs/content/stellar-contracts/tokens/non-fungible/nft-consecutive.mdx b/docs/content/stellar-contracts/tokens/non-fungible/nft-consecutive.mdx new file mode 100644 index 00000000..38b7a856 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/non-fungible/nft-consecutive.mdx @@ -0,0 +1,58 @@ +--- +title: Non-Fungible Consecutive +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible/extensions/consecutive) + +Consecutive extension for [Non-Fungible Token](/stellar-contracts/tokens/non-fungible/non-fungible) is useful +for efficiently minting multiple tokens in a single transaction. This can significantly +reduce costs and improve performance when creating a large number of tokens at once. + +## Usage + +We’ll continue with the [example](/stellar-contracts/tokens/non-fungible/non-fungible#usage) from **Non-Fungible Token** +and modify the contract so that now batches of tokens can be minted with each call +to `award_items`. Please note any account can call `award_items` and we might want to +implement access control to restrict who can mint. + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env, String}; +use stellar_macros::default_impl; +use stellar_tokens::non_fungible::{ + consecutive::{Consecutive, NonFungibleConsecutive}, + Base, ContractOverrides, NonFungibleToken, +}; + +#[contract] +pub struct GameItem; + +#[contractimpl] +impl GameItem { + pub fn __constructor(e: &Env) { + Base::set_metadata( + e, + String::from_str(e, "www.mygame.com"), + String::from_str(e, "My Game Items Collection"), + String::from_str(e, "MGMC"), + ); + } + + pub fn award_items(e: &Env, to: Address, amount: u32) -> u32 { + // access control might be needed + Consecutive::batch_mint(e, &to, amount) + } + + pub fn burn(e: &Env, from: Address, token_id: u32) { + Consecutive::burn(e, &from, token_id); + } +} + +#[default_impl] +#[contractimpl] +impl NonFungibleToken for GameItem { + type ContractType = Consecutive; +} + +// no entry-point functions required, marker impl +impl NonFungibleConsecutive for GameItem {} +``` diff --git a/docs/content/stellar-contracts/tokens/non-fungible/nft-enumerable.mdx b/docs/content/stellar-contracts/tokens/non-fungible/nft-enumerable.mdx new file mode 100644 index 00000000..a37a85e1 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/non-fungible/nft-enumerable.mdx @@ -0,0 +1,66 @@ +--- +title: Non-Fungible Enumerable +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible/extensions/enumerable) + +Enumerable extension for [Non-Fungible Token](/stellar-contracts/tokens/non-fungible/non-fungible) allows for enumeration +of all the token IDs in the contract as well as all the token IDs owned by each account. This is +useful for applications that need to list or iterate over tokens, such as marketplaces or wallets. + +## Usage + +We’ll build on the [example](/stellar-contracts/tokens/non-fungible/non-fungible#usage) from **Non-Fungible Token** +and modify the contract so that all tokens an address own can be listed. Please note any account +can call `award_item` and we might want to implement access control to restrict who can mint. + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env, String}; +use stellar_macros::default_impl; +use stellar_tokens::non_fungible::{ + enumerable::{Enumerable, NonFungibleEnumerable}, + Base, ContractOverrides, NonFungibleToken, +}; + +#[contract] +pub struct GameItem; + +#[contractimpl] +impl GameItem { + pub fn __constructor(e: &Env) { + Base::set_metadata( + e, + String::from_str(e, "www.mygame.com"), + String::from_str(e, "My Game Items Collection"), + String::from_str(e, "MGMC"), + ); + } + + pub fn award_item(e: &Env, to: Address) -> u32 { + // access control might be needed + Enumerable::sequential_mint(e, &to) + } + + pub fn burn(e: &Env, from: Address, token_id: u32) { + Enumerable::sequential_burn(e, &from, token_id); + } +} + +#[default_impl] +#[contractimpl] +impl NonFungibleToken for GameItem { + type ContractType = Enumerable; +} + +#[default_impl] +#[contractimpl] +impl NonFungibleEnumerable for GameItem {} +``` + +The extension exposes additionally the following entry-point functions, automatically implemented by `#[default_impl]`: + +```rust +fn total_supply(e: &Env) -> u32; +fn get_owner_token_id(e: &Env, owner: Address, index: u32) -> u32; +fn get_token_id(e: &Env, index: u32) -> u32; +``` diff --git a/docs/content/stellar-contracts/tokens/non-fungible/non-fungible.mdx b/docs/content/stellar-contracts/tokens/non-fungible/non-fungible.mdx new file mode 100644 index 00000000..415eac81 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/non-fungible/non-fungible.mdx @@ -0,0 +1,108 @@ +--- +title: Non-Fungible Token +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible) + +In the world of digital assets, not all tokens are alike. This becomes important in situations +like **real estate**, **voting rights**, or **collectibles**, where some items are valued more than +others due to their usefulness, rarity, etc. +On Stellar, you can create non-fungible tokens (NFTs), where each token is unique and +represents something distinct, with ownership tracked through Soroban smart contracts. + +## Overview + +The [non-fungible](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible) module +provides three different NFT variants that differ in how certain features like ownership tracking, +token creation and destruction are handled: + +1. **Base**: Contract variant that implements the base logic for the NonFungibleToken interface. Suitable for most use cases. +2. **Consecutive**: Contract variant for optimized minting of batches of tokens. Builds on top of the base variant, and overrides the necessary functions from the `Base` variant. +3. **Enumerable**: Contract variant that allows enumerating the tokens on-chain. Builds on top of the base variant, and overrides the necessary functions from the `Base` variant. + +These three variants share core functionality and a common interface, exposing identical contract functions as +entry-points. However, composing custom flows must be handled with extra caution. That is required because of the +incompatible nature between the business logic of the different NFT variants or the need to wrap the base +functionality with additional logic. + +## Usage + +We’ll use an NFT to track game items, each having 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 or burn their token or +trade it with other people as they see fit. Please note any account can call `award_item` and we might +want to implement access control to restrict who can mint. + +Here’s what a contract for tokenized items might look like: + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env, String}; +use stellar_macros::default_impl; +use stellar_tokens::non_fungible::{ + burnable::NonFungibleBurnable, + Base, ContractOverrides, NonFungibleToken, +}; + +#[contract] +pub struct GameItem; + +#[contractimpl] +impl GameItem { + pub fn __constructor(e: &Env) { + Base::set_metadata( + e, + String::from_str(e, "www.mygame.com"), + String::from_str(e, "My Game Items Collection"), + String::from_str(e, "MGMC"), + ); + } + + pub fn award_item(e: &Env, to: Address) -> u32 { + // access control might be needed + Base::sequential_mint(e, &to) + } +} + +#[default_impl] +#[contractimpl] +impl NonFungibleToken for GameItem { + type ContractType = Base; +} + +#[default_impl] +#[contractimpl] +impl NonFungibleBurnable for GameItem {} +``` + +## Extensions + +The following optional extensions are provided to enhance capabilities: + +### - Burnable +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible/extensions/burnable) + +The `NonFungibleBurnable` trait extends the `NonFungibleToken` trait to provide the capability to burn tokens. + +### - Consecutive +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible/extensions/consecutive) + +The `NonFungibleConsecutive` extension is optimized for batch minting of tokens with consecutive IDs. This approach drastically reduces storage writes during minting by storing ownership only at boundaries and inferring ownership for other tokens. See [Non-Fungible Consecutive](/stellar-contracts/tokens/non-fungible/nft-consecutive) for detailed documentation. + +This extension is build around the contract variant `Consecutive`. Here is an example usage: + +* [Non-Fungible Consecutive](/stellar-contracts/tokens/non-fungible/nft-consecutive) + +### - Enumerable +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible/extensions/enumerable) + +The `NonFungibleEnumerable` extension enables on-chain enumeration of tokens owned by an address. See [Non-Fungible Enumerable](/stellar-contracts/tokens/non-fungible/nft-enumerable) for detailed documentation. + +This extension is build around the contract variant `Enumerable`. Here is an example usage: + +* [Non-Fungible Enumerable](/stellar-contracts/tokens/non-fungible/nft-enumerable) + +### - Royalties +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/non-fungible/extensions/royalties) + +The `NonFungibleRoyalties` trait extends the `NonFungibleToken` trait to provide royalty information for tokens, similar to ERC-2981 standard. This allows marketplaces to query royalty information and pay appropriate fees to creators. + +Note: The royalties extension allows both collection-wide default royalties and per-token royalty settings. diff --git a/docs/content/stellar-contracts/tokens/rwa/rwa.mdx b/docs/content/stellar-contracts/tokens/rwa/rwa.mdx new file mode 100644 index 00000000..4e6e48d2 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/rwa/rwa.mdx @@ -0,0 +1,540 @@ +--- +title: Real World Asset (RWA) Token +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa) + +Real World Asset (RWA) tokens are security tokens that represent ownership or rights to real-world assets such as +real estate, commodities, securities, or other regulated financial instruments. These tokens must comply with various +regulatory requirements including KYC/AML verification, transfer restrictions, and compliance rules. The RWA suite +provides a comprehensive framework based on the T-REX (Token for Regulated Exchanges) standard, +which implements the [ERC-3643](https://docs.erc3643.org/erc-3643) specification. + +## Overview + +The [RWA](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa) suite +provides a complete implementation of regulated security tokens with built-in compliance, +identity verification, and administrative controls. The suite is designed to be flexible and extensible, +allowing integration with various identity registry and compliance frameworks. + +The RWA token extends the standard fungible token functionality with regulatory features required for +security tokens, including: + +- **Identity Management**: Integration with identity registries for KYC/AML compliance +- **Compliance Framework**: Modular compliance rules and validation for transfers and minting +- **Transfer Controls**: Sophisticated transfer restrictions and validations +- **Freezing Mechanisms**: Address-level and partial token freezing capabilities +- **Recovery System**: Lost/old account recovery for verified investors +- **Pausable Operations**: Emergency pause functionality for the entire token +- **Role-Based Access Control (RBAC)**: Flexible privilege management for administrative functions + +## Architecture + +The RWA suite follows a modular architecture that separates concerns and allows for flexible integration + +```mermaid +graph TB + + RWA[RWA Token] + COMP[Compliance] + IDV[Identity Verifier] + + subgraph "Optional Modules" + CM1[Transfer Limit Module] + CM2[Country Restriction Module] + CM3[Other Custom Modules...] + end + + subgraph "Optional Extensions" + DM[Document Manager] + end + + IDV --> IVS + + subgraph "Identity Stack" + IVS[Custom Identity Suite] + end + + RWA -->|"can_transfer()
can_create()
transferred()
created()
destroyed()"| COMP + RWA -->|"verify_identity()
recovery_target()"| IDV + RWA -.-> DM + + COMP --> CM1 + COMP --> CM2 + COMP --> CM3 +``` + + + +### Core Components + +1. **RWA Token Contract**: The main token contract implementing the `RWAToken` trait, which extends both +`FungibleToken` and `Pausable` traits. + +2. **Identity Verifier**: A separate contract responsible for verifying user identities. +The RWA token expects the following function to be available: `fn verify_identity(e: &Env, account: &Address);`. + +3. **Compliance Contract**: A separate contract that validates transfers, minting, and burning operations. +The RWA token expects the following functions to be available: + - `fn can_transfer(e: &Env, from: Address, to: Address, amount: i128, token: Address) -> bool;` + - `fn can_create(e: &Env, to: Address, amount: i128, token: Address) -> bool;` + - `fn created(e: &Env, to: Address, amount: i128, token: Address);` + - `fn destroyed(e: &Env, from: Address, amount: i128, token: Address);` + - `fn transferred(e: &Env, from: Address, to: Address, amount: i128, token: Address);` + +This loose coupling between the RWA token and the modules allows the following: +- Share identity verifier and compliance contracts across multiple RWA tokens +- Implement custom identity verification approaches (Merkle trees, zero-knowledge proofs, claim-based, etc.) +- Create modular compliance rules that can be composed and reused + +### Supporting Modules + +The module provides default implementations for common use cases: + +- **Claim Topics and Issuers**: Manages trusted claim issuers and claim types (e.g., KYC=1, AML=2) +- **Identity Claims**: Integration with identity registries for cryptographic claim validation +- **Identity Registry Storage**: Registry for storing identity information and country relations +- **Claim Issuer**: Validates cryptographic claims with multiple signature schemes (Ed25519, Secp256k1, Secp256r1) +- **Compliance**: Modular compliance framework with hook-based architecture + +## Usage + +We'll create a regulated security token for tokenized real estate shares. The token requires KYC verification, +implements transfer restrictions, and provides administrative controls for compliance. + +Here's what a basic RWA token contract might look like (only the base token contract, not the modules): + +```rust +use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env, String}; +use stellar_access::access_control::{self as access_control, AccessControl}; +use stellar_macros::default_impl; +use stellar_tokens::{ + fungible::{Base, FungibleToken}, + rwa::{RWAToken, RWA}, +}; + +#[contract] +pub struct RealEstateToken; + +#[contractimpl] +impl RealEstateToken { + pub fn __constructor( + e: &Env, + admin: Address, + manager: Address, + compliance: Address, + identity_verifier: Address, + initial_supply: i128, + ) { + // Set token metadata + Base::set_metadata( + e, + 18, // 18 decimals + String::from_str(e, "Real Estate Token"), + String::from_str(e, "REST"), + ); + + // Set compliance and identity verifier contracts + RWA::set_compliance(e, &compliance); + RWA::set_identity_verifier(e, &identity_verifier); + + // Set up access control + access_control::set_admin(e, &admin); + + // Create a "manager" role and grant it to the manager address + access_control::grant_role_no_auth(e, &admin, &manager, &symbol_short!("manager")); + + // Mint initial supply to the admin (must be a verified identity) + RWA::mint(e, &admin, initial_supply); + } +} + +// Implement the FungibleToken trait with RWA contract type +#[default_impl] +#[contractimpl] +impl FungibleToken for RealEstateToken { + type ContractType = RWA; +} + +// Implement the RWAToken trait for regulatory features +#[default_impl] +#[contractimpl] +impl RWAToken for RealEstateToken {} + +// Implement AccessControl for role-based permissions +#[default_impl] +#[contractimpl] +impl AccessControl for RealEstateToken {} +``` + +## Key Features + +### Identity Verification + +Identity Verification is handled on a separate contract as a module. + +All token recipients must have verified identities before receiving tokens. The RWA token integrates +with an identity verifier contract that validates user identities against cryptographic claims. + +Every token operation that involves receiving tokens (mint, transfer, recovery) automatically calls the +identity verifier contract to ensure the recipient has a valid identity with the required claims (KYC, AML, etc.). + +```rust +// Example: Minting tokens +// Internally calls: identity_verifier.verify_identity(&recipient) +// Then calls: compliance.can_create(&recipient, &amount) +// Finally calls: compliance.created(&recipient, &amount) after minting +RWA::mint(e, &recipient, amount); + +// Example: Transferring tokens +// Internally calls: identity_verifier.verify_identity(&from) +// Then calls: identity_verifier.verify_identity(&to) +// Then calls: compliance.can_transfer(&from, &to, &amount) +// Finally calls: compliance.transferred(&from, &to, &amount) after transfer +RWA::transfer(e, &from, &to, amount); + +// Example: Wallet recovery for lost/old accounts +// Internally calls: identity_verifier.verify_identity(&new_account) +// Then calls: identity_verifier.recovery_target(&old_account) to verify the new account +// is the authorized recovery target for the old account +// Transfers all tokens and preserves frozen status +RWA::recover_balance(e, &old_account, &new_account, &operator); +``` + +**Identity Verification Flow:** +1. The RWA token calls `identity_verifier.verify_identity(&address)` +2. The identity verifier checks if the address has a registered identity +3. The identity verifier validates that the identity has all required claims from trusted issuers +4. If verification fails, the entire operation reverts with `IdentityVerificationFailed` error + +### Compliance Validation + +Compliance Validation is handled on a separate contract as a module. + +The compliance framework allows you to implement custom transfer and minting rules through a modular hook system: + +- **CanTransfer**: Validates if a transfer should be allowed +- **CanCreate**: Validates if a mint operation should be allowed +- **Transferred**: Updates state after a successful transfer +- **Created**: Updates state after a successful mint +- **Destroyed**: Updates state after a successful burn + +### Freezing Mechanisms + +Freezing Mechanisms are handled on the RWA token contract. + +RWA tokens support two types of freezing: + +1. **Address-level freezing**: Completely freeze an address from sending or receiving tokens +2. **Partial token freezing**: Freeze a specific amount of tokens for an address + +```rust +// Freeze an entire address +RWA::set_address_frozen(e, &user_address, bool, &operator); + +// Freeze a specific amount of tokens +RWA::freeze_partial_tokens(e, &user_address, amount, &operator); + +// Unfreeze tokens +RWA::unfreeze_partial_tokens(e, &user_address, amount, &operator); +``` + +### Recovery System + +The recovery system allows authorized operators to transfer tokens from a lost/old account to a +new account for the same verified investor: + +Balance Recovery System is handled on the RWA token contract, whereas Identity Recovery System is handled +on a separate contract as a module (may be a part of the Identity Registry contract). + +```rust +// Recover tokens from old account to new account +RWA::recover_balance(e, &old_account, &new_account, &operator); +``` + +### Forced Transfers + +Forced Transfers are handled on the RWA token contract. + +Authorized operators can force transfers between verified wallets for regulatory compliance: + +```rust +// Force a transfer for regulatory reasons +RWA::forced_transfer(e, &from, &to, amount, &operator); +``` + +## Modules + +The RWA package includes several supporting modules that work together to provide comprehensive regulatory compliance: + +### - Compliance +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/compliance) + +This is a mandatory module, since RWA token contract expects the compliance checks and hooks to be available. + +Provides a modular framework for implementing custom compliance rules through a hook-based architecture where multiple +[compliance modules](https://github.com/OpenZeppelin/stellar-contracts/blob/main/packages/tokens/src/rwa/compliance/mod.rs#L327) +can be registered to the [Compliance Contract](https://github.com/OpenZeppelin/stellar-contracts/blob/main/packages/tokens/src/rwa/compliance/mod.rs#L67). + +**Compliance Validation Flow:** + +```mermaid +graph TD + A[RWA Token Transfer/Mint] --> B[Compliance Contract] + B --> C{CanTransfer
or
CanCreate} + C --> D[Registered Modules 1..N] + D --> E[Transfer Limit Module] + D --> F[Country Restriction Module] + D --> G[Investor Count Module] + D --> H[Custom Module X] + E --> I{All modules
return true?} + F --> I + G --> I + H --> I + I -->|Yes| J[Operation Proceeds] + I -->|No| K[Operation Reverts] +``` + +The compliance contract is designed to be shared across multiple RWA tokens, with each hook function accepting a +`token` parameter to identify the calling token. + +### - Identity Verifier +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/identity_verifier) + +This is a mandatory module, since RWA token contract expects `verify_identity(e: &Env, address: Address)` +function to be available. + +The **Identity Verifier Module** provides the interface for verifying user identities. +It can also support possible custom implementation approaches: + +- **Claim-based**: Cryptographic claims from trusted issuers (provided default implementation) +- **Merkle Tree**: Efficient verification using merkle proofs +- **Zero-Knowledge**: Privacy-preserving verification with custom ZK circuits +- **Other custom approaches**: Any implementation that satisfies the `verify_identity` interface + +The diagram below illustrates how identity verification works in the claim-based identity stack: + +```mermaid +graph TB + + RWA[RWA Token] + IDV[Identity Verifier] + + subgraph " " + CTI[Claim Topics & Issuers] + IRS[Identity Registry Storage] + CI[Claim Issuer] + IC[Identity Claims] + + end + + RWA -->|"verify_identity()"| IDV + + IDV -->|"get_claim_topics_and_issuers()"| CTI + IDV -->|"stored_identity()"| IRS + IDV -->|"is_claim_valid()"| CI + IDV -->|"get_claim_ids_by_topic()
get_claim()"| IC +``` + +### - Claim Topics and Issuers +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/claim_topics_and_issuers) + +This module is an implementation detail. It is provided as the suggested implementation for the **Claim-based** approach. + +Acts as the trust registry that defines which claim topics are required and which issuers are authorized to provide those claims. + +Key features/responsibilities of the **Claim Topics and Issuers Module** are: + - **Claim Topic Registry**: Defines which types of claims are required for token participation (e.g., KYC=1, AML=2, Accredited Investor=3) + - **Trusted Issuer Registry**: Maintains a list of authorized claim issuers (e.g., KYC providers, compliance firms) + - **Authorization Mapping**: Maps each trusted issuer to the specific claim topics they are authorized to issue + - Example: Issuer A can issue KYC and AML claims, but not Accreditation claims + - Example: Issuer B can only issue Accreditation claims + - **Multi-Token Support**: Can be shared across multiple RWA tokens, reducing deployment costs + +**Configuration Example:** +```rust +// Add claim topics +add_claim_topic(e, 1, operator); // KYC +add_claim_topic(e, 2, operator); // AML +add_claim_topic(e, 3, operator); // Accredited Investor + +// Add trusted issuer with authorized topics +add_trusted_issuer(e, issuer_a, vec![1, 2], operator); // Can issue KYC and AML +add_trusted_issuer(e, issuer_b, vec![3], operator); // Can only issue Accreditation +``` + +#### Understanding Claims + +**What is a Claim?** + +A claim is a cryptographically signed attestation made by a trusted authority (claim issuer) about a specific aspect +of an identity. Think of it as a digital certificate that proves something about an investor. + +**Claim Structure:** + +Each claim contains: +- **Topic**: A numeric identifier for what the claim attests to (e.g., KYC=1, AML=2, Accredited Investor=3) +- **Issuer**: The address of the trusted authority that issued the claim +- **Signature**: Cryptographic proof that the issuer created this claim +- **Data**: The actual claim information (can include expiration dates, metadata) +- **Scheme**: The signature algorithm used (Ed25519, Secp256k1, Secp256r1) +- **Uri**: Optional URI for additional information + +**How Claims Work:** + +1. **Off-chain Verification**: KYC Provider verifies investor's identity documents off-chain +2. **Off-chain Signing**: KYC Provider signs the claim with their private key off-chain +3. **On-chain Storage**: Signed claim is stored in the investor's Identity Contract +4. **Token Validation**: When investor attempts to receive tokens, the RWA Token: + - Retrieves claims from the Identity Contract + - Calls the Claim Issuer Contract to validate signatures + - Checks for revocations (stored in Claim Issuer Contract) + - Verifies the issuer is trusted (via Claim Topics and Issuers registry) + +**Note:** The KYC Provider and Claim Issuer are the same entity. Signing happens off-chain; only the signed claim and revocation data are stored on-chain. + + +### - Identity Registry Storage +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/identity_registry_storage) + +This module is an implementation detail. It is provided as the suggested implementation for the **Claim-based** approach. + +Stores identity information for verified investors, including: + +- Mapping of wallet addresses to onchain identity contracts +- Country information for regulatory compliance +- Support for both individual and organizational identities +- Recovery account mappings for lost wallet scenarios + +### - Identity Claims +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/identity_claims) + +This module is an implementation detail. It is provided as the suggested implementation for the **Claim-based** approach. + +Manages on-chain identity claims with cryptographic signatures. + +This module can be used to extend or be embedded in identity systems. + + +This contract is under the control of the investors themselves, who are responsible for storing their claims and corresponding signatures. This gives investors ownership of their identity data. + + +Claims are issued by trusted authorities and contain: + +- Claim topic (e.g., KYC, AML) +- Claim data (encrypted or hashed information) +- Cryptographic signature +- Issuer information + +### - Claim Issuer +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/claim_issuer) + +This module is an implementation detail. It is provided as the suggested implementation for the **Claim-based** approach. + +Validates cryptographic claims and provides comprehensive claim lifecycle management. The module includes: + +**Signature Verification:** +- Ed25519 (Stellar native) +- Secp256k1 (Ethereum compatible) +- Secp256r1 (Enterprise PKI compatible) + +**Key Management:** +- Topic-specific key authorization with registry tracking +- Each public key is tied to a signature scheme +- A signing key (public key + scheme) can be authorized to sign claims for specific topic and registry combinations +- The same signing key can be authorized across multiple topics and registries independently + +**Claim Invalidation Mechanisms:** +- **Passive Expiration**: Helper functions to encode/decode expiration metadata (`created_at` and `valid_until` timestamps) within claim data, allowing claims to automatically expire without on-chain action +- **Per-claim Revocation**: Fine-grained revocation of individual claims for precise control +- **Signature Invalidation**: Efficient bulk invalidation via nonce increment for revoking all claims signed by a specific key + +## Extensions + +The following optional extensions are provided: + +### - Document Manager +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/extensions/doc_manager) + +This module is not mandatory. + +The `DocumentManager` trait extends the `RWAToken` trait to provide document management capabilities following the +ERC-1643 standard. This extension allows contracts to: + +- Attach documents with URI, hash, and timestamp +- Update existing document metadata +- Remove documents from the contract +- Retrieve individual or all documents + +This is useful for attaching legal documents, prospectuses, or other regulatory disclosures to the token contract. + +## Utility Modules + +### - Token Binder +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/rwa/utils/token_binder) + +The `TokenBinder` trait provides a standardized interface for linking tokens to periphery contracts such as identity +registries and compliance contracts. This allows a single periphery contract to serve multiple RWA tokens. + +Features: +- Bind/unbind tokens to periphery services +- Query all linked tokens +- Efficient storage with bucket-based architecture (supports up to 10,000 tokens) +- Swap-remove pattern for compact storage + +## Architecture Overview + +Now that we've covered each module individually, let's see how they work together. We'll walk through a `transfer()` operation to illustrate how the components interact: + +```mermaid +sequenceDiagram + participant InvestorA + participant Token + participant IdentityVerifier + participant IdentityRegistryStorage + participant ClaimTopicsAndIssuers + participant TrustedIssuer + participant InvestorB IdentityClaims + participant ComplianceContract + + Note over Token,ComplianceContract: 1. Eligibility + + InvestorA->>Token: transfer(B, amount) + Token->>IdentityVerifier: is_verified(B) + + IdentityVerifier->>IdentityRegistryStorage: Fetch identity: stored_identity(B) + IdentityVerifier->>ClaimTopicsAndIssuers: Fetch required claims and issuers: get_claim_topics_and_issuers() + + loop every pair of claim topic and issuer + IdentityVerifier->>InvestorB IdentityClaims: Fetch claim ids: get_claim_ids_by_topic() + loop every claim + IdentityVerifier->>InvestorB IdentityClaims: Fetch claim: get_claim() + IdentityVerifier->>TrustedIssuer: Check validity: is_claim_valid() + end + end + + IdentityVerifier-->>Token: B eligible + + Note over Token,ComplianceContract: 2. Compliance + + Token->>ComplianceContract: can_transfer(A, B, amount) + ComplianceContract->>ComplianceContract: checks all compliance modules + ComplianceContract-->>Token: transfer compliant + + Token->>Token: process transfer + Token->>InvestorA: notification hook: transferred() +``` + +## Security Considerations + +When implementing RWA tokens, consider the following security aspects: + +1. **Authorization**: All administrative functions expect RBAC checks to be enforced on the `operator` parameter +2. **Identity Verification**: Always verify identities before allowing token operations +3. **Compliance Validation**: Ensure compliance rules are properly configured before enabling transfers +4. **Freezing**: Use freezing mechanisms carefully as they lock user funds +5. **Recovery**: There are two distinct recovery flows, both requiring strict authorization: + - **Identity Recovery**: Managed by the Identity Stack (Identity Registry Storage in Claim-Based approach), transfers the identity contract reference and profile (including country data) from old wallet to new wallet, and creates a recovery mapping + - **Balance Recovery**: Managed by the RWA Token, transfers tokens from old to new account after verifying the identity recovery mapping exists (via `recovery_target()`) +6. **Pausability**: The pause mechanism should only be accessible to authorized administrators +7. **Contract Upgrades**: Consider following a secure upgrade strategy for your contracts. See: [Contract Upgrades](/stellar-contracts/utils/upgradeable) diff --git a/docs/content/stellar-contracts/tokens/vault/vault.mdx b/docs/content/stellar-contracts/tokens/vault/vault.mdx new file mode 100644 index 00000000..89345dd7 --- /dev/null +++ b/docs/content/stellar-contracts/tokens/vault/vault.mdx @@ -0,0 +1,375 @@ +--- +title: Fungible Token Vault +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/vault) + +The Fungible Token Vault extends the [Fungible Token](/stellar-contracts/tokens/fungible/fungible) and implements the ERC-4626 tokenized vault standard, +enabling fungible tokens to represent shares in an underlying asset pool. The tokenized vault standard +is the formalized interface for yield-bearing vaults that hold underlying assets. Vault shares enable +hyperfungible collaterals in DeFi and remain fully compatible with standard fungible token operations. + +This module allows users to deposit underlying assets in exchange for vault shares, and later redeem +those shares for the underlying assets. The vault maintains a dynamic conversion rate between shares and +assets based on the total supply of shares and total assets held by the vault contract. + +## Overview + +The [Vault](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/tokens/src/vault) module provides a complete implementation of tokenized vaults following the ERC-4626 standard. Vaults are useful for: + +- **Yield-bearing tokens**: Represent shares in a yield-generating strategy +- **Liquidity pools**: Pool assets together with automatic share calculation +- **Asset management**: Manage a pool of assets with proportional ownership +- **Wrapped tokens**: Create wrapped versions of tokens with additional features + +The vault automatically handles: +- Share-to-asset conversion with configurable precision +- Deposit and withdrawal operations +- Minting and redemption of shares +- Preview functions for simulating operations + +## Key Concepts + +### Shares vs Assets + +- **Assets**: The underlying token that the vault manages (e.g., USDC, XLM) +- **Shares**: The Token Vaults that represent proportional ownership of the assets + +When assets are deposited into a vault, shares are minted to the depositor. +The number of shares minted depends on the current exchange rate, which is determined by: + +``` +shares = (assets × totalSupply) / totalAssets +``` + +When withdrawing or redeeming, the reverse calculation applies: + +``` +assets = (shares × totalAssets) / totalSupply +``` + +### Virtual Decimals Offset + +The vault uses a "virtual decimals offset" to add extra precision to share calculations. +This helps prevent rounding errors and improves the accuracy of share-to-asset conversions, +especially when the vault has few assets or shares. It's also a key defense mechanism against +[inflation attacks](#inflation-precision-attacks). + +The offset adds virtual shares and assets to the conversion formula: + +``` +shares = (assets × (totalSupply + 10^offset)) / (totalAssets + 1) +``` + +The offset is bounded to a maximum of 10 with both security and UX taken into account. +Values higher than 10 provide minimal practical benefits and may cause overflow errors. + +## Rounding Behavior + +The vault implements specific rounding behavior to protect against being drained through repeated rounding exploits. +Without proper rounding, an attacker could exploit precision loss to extract more assets than they deposited by +performing many small operations where rounding errors accumulate in their favor. + +To prevent this: + +- **Deposit/Redeem**: Rounds **down** (depositor receives slightly fewer shares/assets) +- **Mint/Withdraw**: Rounds **up** (depositor provides slightly more assets/shares) + +This ensures the vault always retains a slight advantage in conversions, making such attacks unprofitable. + +| Operation | Input | Output | Rounding Direction | +| ---------- | ------ | ------ | ---------------------------- | +| `deposit` | assets | shares | Down (fewer shares) | +| `mint` | shares | assets | Up (more assets required) | +| `withdraw` | assets | shares | Up (more shares burned) | +| `redeem` | shares | assets | Down (fewer assets received) | + +## Security Considerations + +### Initialization + +The vault **MUST** be properly initialized before use: + +1. Call `Vault::set_asset(e, asset)` to set the underlying asset +2. Call `Vault::set_decimals_offset(e, offset)` to set the decimals offset +3. Initialize metadata with `Base::set_metadata()` + +These should typically be done in the constructor. Once set, the asset address and decimals offset are **immutable**. + +### Decimal Offset Limits + +The decimals offset is limited to a maximum of 10 to prevent: +- Overflow errors in calculations +- Excessive precision that provides no practical benefit +- Poor user experience with unnecessarily large numbers + +If a higher offset is required, a custom version of `set_decimals_offset()` must be implemented. + +### Inflation (Precision) Attacks + +The virtual decimals offset helps protect against inflation attacks where an attacker: +1. Deposits 1 stroop to get the first share (becoming the sole shareholder) +2. **Donates** (not deposits) an enormous amount of assets directly to the vault contract via a direct transfer, without receiving any shares in return. This inflates the vault's total assets while keeping total shares at 1, making that single share worth an enormous amount +3. When a legitimate user tries to deposit (e.g., 1000 stroops), the share calculation rounds down to 0 shares because their deposit is negligible compared to the inflated vault balance. The user loses their deposit while receiving nothing + +For example: If the attacker donates 1,000,000 stroops after their initial 1 stroop deposit, the vault has 1,000,001 total assets and 1 total share. A user depositing 1000 stroops would receive `(1000 × 1) / 1,000,001 = 0.000999` shares, which rounds down to 0. + +The offset adds virtual shares and assets to the conversion formula, making such attacks economically infeasible by ensuring the denominator is never so small that legitimate deposits round to zero. + +For more details about the mechanics of this attack, see the [OpenZeppelin ERC-4626 security documentation](https://docs.openzeppelin.com/contracts/5.x/erc4626#security-concern-inflation-attack). + +### Custom Authorization + +Custom authorization logic can be implemented as needed: + +```rust +fn deposit( + e: &Env, + assets: i128, + receiver: Address, + from: Address, + operator: Address, +) -> i128 { + // Custom authorization: only allow deposits from whitelisted addresses + if !is_whitelisted(e, &from) { + panic_with_error!(e, Error::NotWhitelisted); + } + + operator.require_auth(); + Vault::deposit(e, assets, receiver, from, operator) +} +``` + +## Compatibility and Compliance + +The vault module implements the ERC-4626 tokenized vault standard with one minor deviation (see Security Considerations). + +### ERC-4626 Deviation + + +The `query_asset()` function will **panic if the asset address is not set**, whereas ERC-4626 requires it to never revert. + +**Rationale**: Soroban doesn't have a "zero address" concept like EVM. Returning `Option
` would break ERC-4626 compatibility. + +**Mitigation**: Always initialize the vault properly in the constructor. Once initialized, `query_asset()` will never panic during normal operations. + + +Aside from this deviation, the vault implementation for Soroban provides: + +- **Cross-ecosystem familiarity**: Ethereum developers will recognize the interface +- **Standard compliance**: Compatible with ERC-4626 tooling and integrations + +## Usage + +### Basic Implementation + +To create a vault contract, implement both the `FungibleToken` and `FungibleVault` traits: + +```rust +use soroban_sdk::{contract, contractimpl, Address, Env, String}; +use stellar_macros::default_impl; +use stellar_tokens::{ + fungible::{Base, FungibleToken}, + vault::{FungibleVault, Vault}, +}; + +#[contract] +pub struct VaultContract; + +#[contractimpl] +impl VaultContract { + pub fn __constructor(e: &Env, asset: Address, decimals_offset: u32) { + // Set the underlying asset address (immutable after initialization) + Vault::set_asset(e, asset); + + // Set the decimals offset for precision (immutable after initialization) + Vault::set_decimals_offset(e, decimals_offset); + + // Initialize token metadata + // Note: Vault overrides the decimals function, so set offset first + Base::set_metadata( + e, + Vault::decimals(e), + String::from_str(e, "Vault Token"), + String::from_str(e, "VLT"), + ); + } +} + +#[default_impl] +#[contractimpl] +impl FungibleToken for VaultContract { + type ContractType = Vault; + + fn decimals(e: &Env) -> u32 { + Vault::decimals(e) + } +} + +#[contractimpl] +impl FungibleVault for VaultContract { + fn deposit( + e: &Env, + assets: i128, + receiver: Address, + from: Address, + operator: Address, + ) -> i128 { + operator.require_auth(); + Vault::deposit(e, assets, receiver, from, operator) + } + + fn withdraw( + e: &Env, + assets: i128, + receiver: Address, + owner: Address, + operator: Address, + ) -> i128 { + operator.require_auth(); + Vault::withdraw(e, assets, receiver, owner, operator) + } + + // Implement other required methods... +} +``` + +### Initialization + +The vault **must** be properly initialized in the constructor: + +1. **Set the underlying asset**: Call `Vault::set_asset(e, asset)` with the address of the token contract that the vault will manage +2. **Set the decimals offset**: Call `Vault::set_decimals_offset(e, offset)` to configure precision (0-10 recommended) +3. **Initialize metadata**: Call `Base::set_metadata()` with appropriate token information + +**Important**: The asset address and decimals offset are immutable once set and cannot be changed. + +### Core Operations + +#### Depositing Assets + +Users can deposit underlying assets to receive vault shares: + +```rust +// Deposit 1000 assets and receive shares +let shares_received = vault_client.deposit( + &1000, // Amount of assets to deposit + &user_address, // Address to receive shares + &user_address, // Address providing assets + &user_address, // Operator (requires auth) +); +``` + +Alternatively, mint a specific amount of shares: + +```rust +// Mint exactly 500 shares by depositing required assets +let assets_required = vault_client.mint( + &500, // Amount of shares to mint + &user_address, // Address to receive shares + &user_address, // Address providing assets + &user_address, // Operator (requires auth) +); +``` + +#### Withdrawing Assets + +Users can withdraw assets by burning their shares: + +```rust +// Withdraw 500 assets by burning required shares +let shares_burned = vault_client.withdraw( + &500, // Amount of assets to withdraw + &user_address, // Address to receive assets + &user_address, // Owner of shares + &user_address, // Operator (requires auth) +); +``` + +Or redeem a specific amount of shares: + +```rust +// Redeem 200 shares for underlying assets +let assets_received = vault_client.redeem( + &200, // Amount of shares to redeem + &user_address, // Address to receive assets + &user_address, // Owner of shares + &user_address, // Operator (requires auth) +); +``` + +### Preview Functions + +Preview functions allow you to simulate operations without executing them: + +```rust +let expected_shares = vault_client.preview_deposit(&1000); + +let required_assets = vault_client.preview_mint(&500); + +let shares_to_burn = vault_client.preview_withdraw(&500); + +let expected_assets = vault_client.preview_redeem(&200); +``` + +### Conversion Functions + +Convert between assets and shares at the current exchange rate: + +```rust +// Convert assets to shares +let shares = vault_client.convert_to_shares(&1000); + +// Convert shares to assets +let assets = vault_client.convert_to_assets(&500); +``` + +### Query Functions + +Check vault state and limits: + +```rust +// Get the underlying asset address +let asset_address = vault_client.query_asset(); + +// Get total assets held by the vault +let total_assets = vault_client.total_assets(); + +// Check maximum amounts for operations +let max_deposit = vault_client.max_deposit(&user_address); +let max_mint = vault_client.max_mint(&user_address); +let max_withdraw = vault_client.max_withdraw(&user_address); +let max_redeem = vault_client.max_redeem(&user_address); +``` + +### Operator Pattern + +The vault supports an operator pattern where one address can perform operations on behalf of another: + +```rust +// User approves operator to spend their assets on the underlying token +asset_client.approve(&user, &operator, &1000, &expiration_ledger); + +// Operator deposits user's assets to a receiver +vault_client.deposit( + &1000, + &receiver, // Receives the shares + &user, // Provides the assets + &operator, // Performs the operation (requires auth) +); +``` + +For withdrawals, the operator must have allowance on the **vault shares**: + +```rust +// User approves operator to spend their vault shares +vault_client.approve(&user, &operator, &500, &expiration_ledger); + +// Operator withdraws on behalf of user +vault_client.withdraw( + &500, + &receiver, // Receives the assets + &user, // Owns the shares + &operator, // Performs the operation (requires auth) +); +``` diff --git a/docs/content/stellar-contracts/utils/crypto/crypto.mdx b/docs/content/stellar-contracts/utils/crypto/crypto.mdx new file mode 100644 index 00000000..bf3cf746 --- /dev/null +++ b/docs/content/stellar-contracts/utils/crypto/crypto.mdx @@ -0,0 +1,109 @@ +--- +title: Cryptography Utilities +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/crypto) + +## Overview + +The Cryptography Utilities provide a set of cryptographic tools for Soroban smart contracts, +including hash functions, Merkle tree verification, and Merkle-based distribution systems. +These utilities enable secure data verification and efficient token distribution mechanisms. +The Cryptography Utilities consist of two main packages: + +* Crypto: A set of cryptographic primitives and utilities for Soroban contracts. +* [Merkle Distributor](./merkle-distributor): A system for distributing tokens or other assets using Merkle proofs for verification. + +## Crypto Package + +The crypto package provides fundamental cryptographic primitives and utilities for Soroban contracts, +with a focus on hashing and Merkle tree operations. + +### Key Components + +#### Hashers + +Provides a generic `Hasher` trait and implementations for common hash functions: + +* `Sha256`: Implementation of the SHA-256 hash function +* `Keccak256`: Implementation of the Keccak-256 hash function (used in Ethereum) + +Each hasher follows the same interface: + +```rust +pub trait Hasher { + type Output; + + fn new(e: &Env) -> Self; + fn update(&mut self, input: Bytes); + fn finalize(self) -> Self::Output; +} +``` + +#### Hashable + +The `Hashable` trait allows types to be hashed with any `Hasher` implementation: + +```rust +pub trait Hashable { + fn hash(&self, hasher: &mut H); +} +``` + +Built-in implementations are provided for `BytesN<32>` and `Bytes`. + +#### Utility Functions + +* `hash_pair`: Hashes two values together +* `commutative_hash_pair`: Hashes two values in a deterministic order (important for Merkle trees) + +#### Merkle Tree Verification + +The `Verifier` struct provides functionality to verify Merkle proofs: + +```rust +impl Verifier +where + H: Hasher, +{ + pub fn verify(e: &Env, proof: Vec, root: Bytes32, leaf: Bytes32) -> bool { + // Implementation verifies that the leaf is part of the tree defined by root + } +} +``` + +### Usage Examples + +#### Hashing Data + +```rust +use soroban_sdk::{Bytes, Env}; +use stellar_contract_utils::crypto::keccak::Keccak256; +use stellar_contract_utils::crypto::hasher::Hasher; + +// Hash some data with Keccak256 +let e = Env::default(); +let data = Bytes::from_slice(&e, "Hello, world!".as_bytes()); + +let mut hasher = Keccak256::new(&e); +hasher.update(data); +let hash = hasher.finalize(); +``` + +#### Verifying a Merkle Proof + +```rust +use soroban_sdk::{BytesN, Env, Vec}; +use stellar_crypto::keccak::Keccak256; +use stellar_crypto::merkle::Verifier; + +// Verify that a leaf is part of a Merkle tree +let e = Env::default(); +let root = /* merkle root as BytesN<32> */; +let leaf = /* leaf to verify as BytesN<32> */; +let proof = /* proof as Vec> */; + +let is_valid = Verifier::::verify(&e, proof, root, leaf); +``` + + diff --git a/docs/content/stellar-contracts/utils/crypto/merkle-distributor.mdx b/docs/content/stellar-contracts/utils/crypto/merkle-distributor.mdx new file mode 100644 index 00000000..75a28e7d --- /dev/null +++ b/docs/content/stellar-contracts/utils/crypto/merkle-distributor.mdx @@ -0,0 +1,109 @@ +--- +title: Merkle Distributor +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/merkle-distributor) + +The Merkle Distributor package builds on the [crypto package](./crypto) to provide a system for distributing tokens or other assets using Merkle proofs for verification. + +## Key Components + +### IndexableLeaf + +The `IndexableLeaf` trait defines the interface that leaf nodes must implement to be used with the Merkle Distributor: + +```rust +pub trait IndexableLeaf { + fn index(&self) -> u32; +} +``` + +Each leaf in the merkle tree requires a unique index to track whether it has been claimed to prevent double-claiming. The custom leaf struct should implement this trait by returning a unique `u32` value for each recipient. This index serves as the key for storing claim status on-chain. + +### MerkleDistributor + +The `MerkleDistributor` struct is a generic component that manages the verification and claiming process. It is parameterized by a hash function (e.g. `Keccak256`) and provides the following core functionality: + +#### Setting the Merkle Root + +`set_root(env: &Env, root: BytesN<32>)` stores the Merkle tree root on-chain. This is typically called once during contract initialization or when updating the distribution list. + +#### Checking Claim Status + +`is_claimed(env: &Env, index: u32) -> bool` queries whether a specific index has already been claimed. This allows you to check claim status before attempting verification. + +#### Verifying and Claiming + +`verify_and_set_claimed(env: &Env, leaf: impl IndexableLeaf, proof: Vec>)` performs two operations atomically: + +1. **Verification**: Validates that the provided leaf and proof correctly reconstruct the stored Merkle root +2. **Claiming**: Marks the leaf's index as claimed to prevent duplicate claims + +This function will panic if the proof is invalid or if the index has already been claimed, ensuring the integrity of the distribution process. + +## Usage Example + +```rust +use soroban_sdk::{contract, contractimpl, contracttype, Address, BytesN, Env, Vec}; +use stellar_contract_utils::crypto::keccak::Keccak256; +use stellar_contract_utils::merkle_distributor::{IndexableLeaf, MerkleDistributor}; + +// Define a leaf node structure +#[contracttype] +struct LeafData { + pub index: u32, + pub address: Address, + pub amount: i128, +} + +// Implement IndexableLeaf for the leaf structure +impl IndexableLeaf for LeafData { + fn index(&self) -> u32 { + self.index + } +} + +#[contract] +pub struct TokenDistributor; + +#[contractimpl] +impl TokenDistributor { + // Initialize the distributor with a Merkle root + pub fn initialize(e: &Env, root: BytesN<32>) { + MerkleDistributor::::set_root(e, root); + } + + // Claim tokens by providing a proof + pub fn claim(e: &Env, leaf: LeafData, proof: Vec>) { + // Verify the proof and mark as claimed + MerkleDistributor::::verify_and_set_claimed(e, leaf.clone(), proof); + + // Transfer tokens or perform other actions based on leaf data + // ... + } + + // Check if an index has been claimed + pub fn is_claimed(e: &Env, index: u32) -> bool { + MerkleDistributor::::is_claimed(e, index) + } +} +``` + +## Use Cases + +### Token Airdrops + +Efficiently distribute tokens to a large number of recipients without requiring individual transactions for each recipient. + +### NFT Distributions + +Distribute NFTs to a whitelist of addresses, with each address potentially receiving different NFTs. + +### Off-chain Allowlists + +Maintain a list of eligible addresses off-chain and allow them to claim tokens or other assets on-chain. + +### Snapshot-based Voting + +Create a snapshot of token holders at a specific block and allow them to vote based on their holdings. + diff --git a/docs/content/stellar-contracts/utils/pausable.mdx b/docs/content/stellar-contracts/utils/pausable.mdx new file mode 100644 index 00000000..fc741f70 --- /dev/null +++ b/docs/content/stellar-contracts/utils/pausable.mdx @@ -0,0 +1,56 @@ +--- +title: Pausable +--- + +[Source Code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/pausable) + +## Purpose + +Allows contracts to be paused and unpaused by authorized accounts. + +This utility contract can be used with any token standard (fungible, non-fungible, multi-token). + +## Design + +To make it easier to spot when inspecting the code, we turned this simple functionality into a macro that can annotate your smart contract functions. + +An example: +```rust +#[when_paused] +pub fn emergency_reset(e: &Env) { + e.storage().instance().set(&DataKey::Counter, &0); +} +``` + +Which will expand into the below code: + +```rust +pub fn emergency_reset(e: &Env) { + when_paused(e); + + e.storage().instance().set(&DataKey::Counter, &0); +} +``` + +## Implementing Pausable Trait + +```rust +#[contractimpl] +impl Pausable for ExampleContract { + fn paused(e: &Env) -> bool { + pausable::paused(e) + } + + #[only_owner] + fn pause(e: &Env, _caller: Address) { + pausable::pause(e); + } + + #[only_owner] + fn unpause(e: &Env, _caller: Address) { + pausable::unpause(e); + } +} +``` + + diff --git a/docs/content/stellar-contracts/utils/upgradeable.mdx b/docs/content/stellar-contracts/utils/upgradeable.mdx new file mode 100644 index 00000000..5a8407af --- /dev/null +++ b/docs/content/stellar-contracts/utils/upgradeable.mdx @@ -0,0 +1,186 @@ +--- +title: Upgrades and Migrations +--- + +[Source code](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/upgradeable) + +Soroban contracts are mutable by default. Mutability in the context of Stellar Soroban refers to the ability of a smart +contract to modify its WASM bytecode, thereby altering its function interface, execution logic, or metadata. + +Soroban provides a built-in, protocol-level defined mechanism for contract upgrades, allowing contracts to upgrade +themselves if they are explicitly designed to do so. One of the advantages of it is the flexibility it offers to +contract developers who can choose to make the contract immutable by simply not provisioning upgradability mechanics. On +the other hand, providing upgradability on a protocol level significantly reduces the risk surface, compared to other +smart contract platforms, which lack native support for upgradability. + +While Soroban’s built-in upgradability eliminates many of the challenges, related to managing smart contract upgrades +and migrations, certain caveats must still be considered. + +## Overview + +The [upgradeable](https://github.com/OpenZeppelin/stellar-contracts/tree/main/packages/contract-utils/src/upgradeable) module +provides a lightweight upgradeability framework with additional support for structured and safe migrations. + +It consists of two main components: + +1. ***[`Upgradeable`](#upgrade-only)*** for cases where only the WASM binary needs to be updated. +2. ***[`UpgradeableMigratable`](#upgrade-and-migrate)*** for more advanced scenarios where, in addition to the WASM binary, specific storage entries +must be modified (migrated) during the upgrade process. + +The recommended way to use this module is through the `#[derive(Upgradeable)]` and `#[derive(UpgradeableMigratable)]` +macros. + +They handle the implementation of the necessary functions, allowing developers to focus solely on managing authorizations +and access control. These derive macros also leverage the crate version from the contract’s `Cargo.toml` and set it as +the binary version in the WASM metadata, aligning with the guidelines outlined in +[SEP-49](https://github.com/stellar/stellar-protocol/blob/master/ecosystem%2Fsep-0049.md). + + + +While the framework structures the upgrade flow, it does NOT perform deeper checks and verifications such as: + +* Ensuring that the new contract does not include a constructor, as it will not be invoked. +* Verifying that the new contract includes an upgradability mechanism, preventing an unintended loss of further + upgradability capacity. +* Checking for storage consistency, ensuring that the new contract does not inadvertently introduce storage mismatches. + + +## Usage + +### Upgrade Only +#### `Upgradeable` + +When only the WASM binary needs to be upgraded and no additional migration logic is required, developers should implement +the `UpgradeableInternal` trait. This trait is where authorization and custom access control logic are defined, +specifying who can perform the upgrade. This minimal implementation keeps the focus solely on controlling upgrade +permissions. + +```rust +use soroban_sdk::{ + contract, contracterror, contractimpl, panic_with_error, symbol_short, Address, Env, +}; +use stellar_contract_utils::upgradeable::UpgradeableInternal; +use stellar_macros::Upgradeable; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum ExampleContractError { + Unauthorized = 1, +} + + +#[derive(Upgradeable)] +#[contract] +pub struct ExampleContract; + +#[contractimpl] +impl ExampleContract { + pub fn __constructor(e: &Env, admin: Address) { + e.storage().instance().set(&symbol_short!("OWNER"), &admin); + } +} + +impl UpgradeableInternal for ExampleContract { + fn _require_auth(e: &Env, operator: &Address) { + operator.require_auth(); + // `operator` is the invoker of the upgrade function and can be used + // to perform a role-based access control if implemented + let owner: Address = e.storage().instance().get(&symbol_short!("OWNER")).unwrap(); + if *operator != owner { + panic_with_error!(e, ExampleContractError::Unauthorized) + } + } +} +``` + +### Upgrade and Migrate +#### `UpgradeableMigratable` + +When both the WASM binary and specific storage entries need to be modified as part of the upgrade process, the +`UpgradeableMigratableInternal` trait should be implemented. In addition to defining access control and migration +logic, the developer must specify an associated type that represents the data required for the migration. + +The `#[derive(UpgradeableMigratable)]` macro manages the sequencing of operations, ensuring that the migration can +only be invoked after a successful upgrade, preventing potential state inconsistencies and storage corruption. + +```rust +use soroban_sdk::{ + contract, contracterror, contracttype, panic_with_error, symbol_short, Address, Env, +}; +use stellar_contract_utils::upgradeable::UpgradeableMigratableInternal; +use stellar_macros::UpgradeableMigratable; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum ExampleContractError { + Unauthorized = 1, +} + +#[contracttype] +pub struct Data { + pub num1: u32, + pub num2: u32, +} + +#[derive(UpgradeableMigratable)] +#[contract] +pub struct ExampleContract; + +impl UpgradeableMigratableInternal for ExampleContract { + type MigrationData = Data; + + fn _require_auth(e: &Env, operator: &Address) { + operator.require_auth(); + let owner: Address = e.storage().instance().get(&symbol_short!("OWNER")).unwrap(); + if *operator != owner { + panic_with_error!(e, ExampleContractError::Unauthorized) + } + } + + fn _migrate(e: &Env, data: &Self::MigrationData) { + e.storage().instance().set(&symbol_short!("DATA_KEY"), data); + } +} +``` + + +If a rollback is required, the contract can be upgraded to a newer version where the rollback-specific logic +is defined and performed as a migration. + + +#### Atomic upgrade and migration + +When performing an upgrade, the new implementation only becomes effective after the current invocation completes. +This means that if migration logic is included in the new implementation, it cannot be executed within the same +call. To address this, an auxiliary contract called `Upgrader` can be used to wrap both invocations, enabling an +atomic upgrade-and-migrate process. This approach ensures that the migration logic is executed immediately after the +upgrade without requiring a separate transaction. + +```rust +use soroban_sdk::{contract, contractimpl, symbol_short, Address, BytesN, Env, Val}; +use stellar_contract_utils::upgradeable::UpgradeableClient; + +#[contract] +pub struct Upgrader; + +#[contractimpl] +impl Upgrader { + pub fn upgrade_and_migrate( + env: Env, + contract_address: Address, + operator: Address, + wasm_hash: BytesN<32>, + migration_data: soroban_sdk::Vec, + ) { + operator.require_auth(); + let contract_client = UpgradeableClient::new(&env, &contract_address); + + contract_client.upgrade(&wasm_hash, &operator); + // The types of the arguments to the migrate function are unknown to this + // contract, so we need to call it with invoke_contract. + env.invoke_contract::<()>(&contract_address, &symbol_short!("migrate"), migration_data); + } +} +``` diff --git a/docs/content/substrate-runtimes/2.0.2/glossary.mdx b/docs/content/substrate-runtimes/2.0.2/glossary.mdx new file mode 100644 index 00000000..ee01b857 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/glossary.mdx @@ -0,0 +1,185 @@ +--- +title: Glossary +--- + +## A + +### Announcement + +Announcement is a statement of call hash for the [proxy](#proxy) to execute in some future block. Required for some proxies where delay is specified. + +### Asset Teleportation + +Asset Teleportation is a movement of an asset by destroying it on one side and creating a clone on the other side + +## B + +### Barrier + +A generic filter which returns whether or not XCM may pass and be executed. If they do not pass the barrier, they are not executed. The barrier is the final filter before XCM execution. It implements the `ShouldExecute` trait and therefore takes as input an XCM and returns a bool indicating whether it should be executed. + +### Burning + +The process of destroying existing assets. + +## C + +### Candidacy Bond + +Candidacy bond is fixed amount to deposit to become a [candidate](#candidate). + +### Candidate + +Candidate is a self-promoted [collator](#collator), who deposited a candidacy bond to participate in collation process + +### Collator + +Collator is a node that gathers collation information and communicates it to relay chain to make a block. + +## D + +### Delay + +Delay is number of block that should pass from [announcement](#announcement) before call execution. + +### Delegatee + +Delegatee is an account that was granted call execution rights with [proxy](#proxy) creation. + +### Delegator + +Delegator is an account that granted call execution rights with [proxy](#proxy) creation. + +## F + +### Freeze + +`Freeze` is reserving some amount from the account. For example, to make a transaction, the amount to be sent might be first frozen, then the checks for the transaction will be made. If the checks pass, the amount will be deducted from the sender’s account and added to the recipient’s. `Freeze` should be preferred if the reserved money can be lost/sent due to external reasons. + +### Fungible Asset + +An asset where any unit of it worth the same. Opposite of [nonfungible_asset](#nonfungible_asset) + +### NonFungible Asset + +An asset meaning that it is not interchangeable with other assets of the same type. For example, a non-fungible asset could be an NFT, while a fungible asset could be a currency token. + +## H + +### Hold +`Hold` means reserving/holding an amount of balance from an account. The `Hold` amount cannot be spent, but still belongs to the account. For example, depositing modifying the state, and occupying space. This deposit will be repaid once the occupied space is freed. + +## I + +### Invulnerable + +Invulnerable is a permissioned [collator](#collator), that will always be part of the collation process + +## J + +### Junction + +Junction is a single item in a path to describe a relative location (think of `dir` or `dir/$file_path` as a junction). + +### Junctions + +Junctions is multiple `Junction`s, formed only through [multilocation](#multilocation) construction. + +## L + +### Length Fee + +A fee proportional to the encoded length of the transaction. + +### Lock + +Nearly the same as `[Hold](#hold)`, with 2 major differences: + +* Locks can store the reason for the locking operation. +* Locks are overlapping, whereas Reserve/Hold’s are additive. Example: + * Apply `Hold` to account A for 20 units, then apply another `Hold` to account A for 10 units -> a total of 30 units will be reserved/hold + * Apply `Lock` to account A for 20 units, then apply another `Lock` to account A for 10 units -> a total of 20 units will be locked, since 10 is smaller than 20. + +## M + +### Minting + +The process of creating new assets. + +### MultiAsset + +Either an amount of a single fungible asset, or one non-fungible asset. + +### MultiLocation + +A path described by junctions leading to the location. + +## N + +### NonFungible Asset + +An asset where each unit is worth a different amount and/or is unique in some way. Opposite of [fungible_asset](#fungible_asset). + +## P + +### Pot + +Pot is a stake that will reward block authors. Block author will get half of the current stake. + +### Proxy + +Proxy is a statement of call execution rights transfer from [delegator](#delegator) to [delegatee](#delegatee). Specified by [proxy_type](#proxy_type) and [delay](#delay). + +### Proxy type + +Proxy type is a type of calls that can be executed using this [proxy](#proxy). + +### Pure account + +Pure account is an account that was spawned only to be a [delegatee](#delegatee) for some [proxy](#proxy). + +## R + +### Reserve + +Deprecated, use `[Hold](#hold)`. + +### Reserve Asset Transfer + +When consensus systems do not have a established layer of trust over which they can transfer assets, they can opt for a trusted 3rd entity to store the assets + +## T + +### Thawing + +The process of unfreezing an asset after being frozen. + +### Tip + +An optional tip. Tip increases the priority of the transaction, giving it a higher chance to be included by the transaction queue. + +## U + +### Unincluded Segment + +A sequence of blocks that were not yet included into the relay chain state transition + +## V + +### Validation Code + +Validation Code is the runtime binary that runs in the parachain + +### Validation Data + +Validation Data is the information passed from the relay chain to validate the next block + +## W + +### Weight + +The time it takes to execute runtime logic. By controlling the execution time that a block can consume, weight bounds the storage changes and computation per block. + +### Weight Fee + +A fee proportional to amount of [weight](#weight) a transaction consumes. diff --git a/docs/content/substrate-runtimes/2.0.2/guides/async_backing.mdx b/docs/content/substrate-runtimes/2.0.2/guides/async_backing.mdx new file mode 100644 index 00000000..9248a1ec --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/async_backing.mdx @@ -0,0 +1,34 @@ +--- +title: Async Backing +--- + +Async backing is a feature of parachains that allow to shorten the block time, get more execution time and speed up transaction inclusion in general. You can read in details what async backing is in the [general guide](https://wiki.polkadot.network/docs/learn-async-backing#candidate-receipt) and in the [update guide](https://wiki.polkadot.network/docs/maintain-guides-async-backing). In our generic template we have included async backing under a feature. This page describes how can you build and test the template with async backing enabled. + +## How to build + +Async backing is enabled by the feature `async-backing`. You can build the template with it like this: + +```bash +cargo build --release --features="async-backing" +``` + +## How to test + +You can always test it against Paseo (it should already have enabled async backing), but the fastest way is to test against a local relay chain. In our repository you can find a script, a config and a [manual](https://github.com/OpenZeppelin/polkadot-runtime-templates/tree/main/generic-template/zombienet-config) that will run both relay chain and a parachain. To launch a parachain with a relay chain, you will need to run these commands: + +* Get the Polkadot binaries: + * If you are using some Linux distro, you can download the binaries: + + ``` + ./scripts/zombienet.sh init + ``` + * Otherwise, you have to build it from scratch. It takes + + ``` + ./scripts/zombienet.sh build + ``` +* After that, launch the network: + + ``` + ./scripts/zombienet.sh devnet + ``` diff --git a/docs/content/substrate-runtimes/2.0.2/guides/contract_migration.mdx b/docs/content/substrate-runtimes/2.0.2/guides/contract_migration.mdx new file mode 100644 index 00000000..2678d98a --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/contract_migration.mdx @@ -0,0 +1,46 @@ +--- +title: Migrate a Contract from EVM chain +--- + +This EVM template is powered by Frontier. It allows you to deploy almost any Solidity contract you have following these steps. + +## Step 1: Prepare and run the chain + +This step is basic, you can use our [guide for generic template]. It is recommended to use [async backing](/substrate-runtimes/2.0.2/guides/async_backing) for better experience. +The only additional thing that you will need to specify is the EVM Chain Id in your chainspec. For that, you need: + +* During the "Edit the chainspec" step execute one additional step: + * Edit the `evmChainId.chainId` to the value of your choice. Try to select a value that has no existing EVM chain assigned to it. + +## Step 2: Set up the development environment + +For Solidity development you can use [Foundry](https://book.getfoundry.sh/getting-started/installation). Feel free to use any other toolkit like ethers.js or web3.py. + +With the chain running, to start the development you need only two values: a private key from the account with some tokens on it and a url from EVM RPC. + +### Private Key. +If you have formed your own chainspec, you should have some keys ready. Alternatively, you can generate the key using any wallet of your choice (e.g. Metamask) and get some tokens to it. By default, we have these keys for prefunded accounts in the chainspec: + +* Private: `0x94834ac72c525a539097542a738816e8d2d18a60d6460c25e82b306441635dc4` + Public: `0x33c7c88f2B2Fcb83975fCDB08d2B5bf7eA29FDCE` +* Private: `0x3c959c97d3b687af91c27b93bbe8deb2bb2148f01bd48126ae757e202ab625a0` + Public: `0xc02db867898f227416BCB6d97190126A6b04988A` + + +You must not use these keys for any production purpose + + +### Url for the EVM RPC. + +This part is simple. You can use the same url that you would use with the polkadot.js wallet -- it serves the EVM JSON-RPC as well. If you have followed our guide, you can use http://localhost:8844. + +## Step 3: Deploy your contract + +Optional: If you want to test any contract, you can use our [guide about the standard contracts](https://docs.openzeppelin.com/contracts/5.x/#foundry_git). + +When you are ready with the contract just use `forge` that you have installed in a previous step: +``` +forge create --rpc-url --private-key +``` + +Output of this command will contain the contract address. That’s it! diff --git a/docs/content/substrate-runtimes/2.0.2/guides/hrmp_channels.mdx b/docs/content/substrate-runtimes/2.0.2/guides/hrmp_channels.mdx new file mode 100644 index 00000000..3727c478 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/hrmp_channels.mdx @@ -0,0 +1,145 @@ +--- +title: Sending Cross-Chain Messages between Parachains +--- + +The supported way to exchange cross-chain messages (XCM) between parachains is to use Horizontal Relay-routed Message Passing (HRMP) channels. + +Each HRMP channel is unidirectional. In order to enable full connectivity between two parachains, two HRMP channels must be opened: one for sending outgoing XCM and the other for receiving incoming XCM. + +## Opening an HRMP Channel + +Opening a channel between two parachains A and B takes 2 steps: +1. parachain A initiates a channel request +2. parachain B accepts the channel request + +For step (1), parachain A calls `hrmp > hrmpInitOpenChannel(recipient, proposedMaxCapacity, proposedMaxMessageSize)` to create a channel request with the input configuration. + +For step (2), parachain B calls `hrmp > hrmpAcceptOpenChannel(sender)` to accept the channel open request from the input sender. + +In order to dispatch a call from its sovereign origin, a parachain may use governance to send the encoded call in a Transact instruction to the Relay Chain, but it may also execute this logic autonomously (e.g. on the notification that a channel was requested). + +## Connecting to Ecosystem Parachains + +The examples in this section include steps to connect to existing parachains in the Polkadot ecosystem. Examples include the following Polkadot parachains: +1. AssetHub supports managing asset balances as well as the execution of cross-chain transfers. +2. Snowbridge supports sending ERC20 tokens between Polkadot parachains and Ethereum in both directions. +3. HydraDX supports DeFi functionality to provide liquidity to Polkadot. + +For all examples: +. `paraId` for the user’s parachain is set to `43211234`. +. `proposedMaxCapacity` and `proposedMaxMessageSize` are set to the values of Polkadot `config.hrmpChannelMaxCapacity = 1000` and `config.hrmpChannelMaxMessageSize = 102400`, respectively. + +### AssetHub + +AssetHub supports managing asset balances as well as the execution of cross-chain transfers. + +AssetHub is a common-good, system parachain and is therefore controlled by relay chain governance. This means it is sufficient to propose opening both channels at once through the relay chain’s `GeneralAdmin` track. The proposal should set its call (proposed to be executed) to `utility.batchAll` with the input including 2 calls to `hrmp.forceOpenHrmpChannel` to open a channel in each direction between the user parachain and AssetHub. + +The first call to `hrmp.forceOpenHrmpChannel` proposes opening a unidirectional channel to send XCM from the user parachain to AssetHub. If AssetHub’s paraId is set to `1000`, here are the inputs: +``` +hrmp.forceOpenChannel( + sender = 43211234, + recipient = 1000, + max_capacity = 1000, + max_message_size = 102400, +) +``` +Here is the second call to open a unidirectional channel to send XCM from AssetHub to the user parachain: +``` +hrmp.forceOpenChannel( + sender = 1000, + recipient = 43211234, + max_capacity = 1000, + max_message_size = 102400, +) +``` + +[Here](https://polkadot.subsquare.io/referenda/438) is a successful example of this proposal which passed to open 2 HRMP channels between Unique Network and AssetHub. [Here](https://polkadot.polkassembly.io/referenda/594) is another example of a proposal executed to open HRMP Channels between AssetHub and Mythos. + +### Snowbridge + +Snowbridge supports sending ERC20 tokens between Polkadot parachains and Ethereum in both directions. + +Snowbridge leverages AssetHub to mint ERC20 tokens received from Ethereum and send them to parachains. This implies that a prerequisite step for receiving ERC20 tokens via Snowbridge is opening HRMP channels with AssetHub by following the previous section. + +The standard way of interacting with Snowbridge is to make calls to the [Gateway](https://github.com/Snowfork/snowbridge/blob/main/contracts/src/interfaces/IGateway.sol) contract deployed on Ethereum at [this address](https://etherscan.io/address/0x27ca963C279c93801941e1eB8799c23f407d68e7). + +If an ERC20 token has already been bridged before, the user may send the following transaction to the Gateway to send ERC20 tokens to parachain `destinationChain` from Ethereum and deposit into account `destinationAddress` on that parachain. +```solidity, ignore +function sendToken(address token, ParaID destinationChain, MultiAddress destinationAddress, uint128 destinationFee, uint128 amount) + external + payable; +``` + +If the ERC20 tokens has not been bridged before, there is a prerequisite step to register the ERC20 token on AssetHub via the `ForeignAssets` pallet. To register ERC20 tokens for the first time, the user may send the following transaction to the Gateway: +```solidity, ignore +function registerToken(address token) external payable; +``` +The above function sends a message to the AssetHub parachain to register a new fungible asset in the `ForeignAssets` pallet. To account for delivery costs, the above function charges a fee in Ether which may be retrieved beforehand by calling `quoteRegisterFee`. + +For more information, see the [Snowbridge Docs](https://docs.snowbridge.network). For more information on the Snowbridge deployment to Polkadot, see the [governance proposal which initialized Snowbridge on BridgeHub and AssetHub](https://polkadot.polkassembly.io/referenda/680). + +### HydraDX + +HydraDX supports DeFi functionality to provide liquidity to Polkadot. + +The `Opening an HRMP Channel` section shows the general steps for opening two HRMP channels between any two parachains. + +To propose opening a channel to send XCM to HydraDX, the sending parachain may call: +``` +hrmp.hrmpInitOpenChannel( + recipient = 2034, + proposedMaxCapacity = 1000, + proposedMaxMessageSize = 102400, +) +``` + +HydraDX may accept the channel open request from the sending parachain with paraID 43211234 by calling: +``` +hrmpAcceptOpenChannel( + sender = 43211234 +) +``` + +HydraDX may call the following to propose opening a channel to send XCM from HydraDX to the parachain with paraID 43211234: +``` +hrmp.hrmpInitOpenChannel( + recipient = 43211234, + proposedMaxCapacity = 1000, + proposedMaxMessageSize = 102400, +) +``` + +Assuming the HydraDX has paraID 2034, the receiving parachain may accept the channel open request by calling: +``` +hrmpAcceptOpenChannel( + sender = 2034 +) +``` + +Note that in order to dispatch a call from its sovereign origin, a parachain may use governance to send the encoded call in a Transact instruction to the Relay Chain, but it may also execute this logic autonomously (e.g. on the notification that a channel was requested). HRMP extrinsics often must be called from the parachain’s sovereign account as origin, often via a democracy proposal. + +[Here](https://moonbeam.polkassembly.network/referendum/93) is an example of a proposal on Moonbeam to Open/Accept HRMP channels with HydraDX. + +## Bonus: HRMP Channel Notification Handlers + +There are 3 handlers that may be configured as hooks to implement automated logic for when a `HRMP` notification is received: +. `HrmpChannelAcceptedHandler` +. `HrmpChannelClosingHandler` +. `HrmpNewChannelOpenRequestHandler` + +Each follows a similar interface: +```rust +pub trait HandleHrmpNewChannelOpenRequest { + fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult; +} + +pub trait HandleHrmpChannelAccepted { + fn handle(recipient: u32) -> XcmResult; +} + +pub trait HandleHrmpChannelClosing { + fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult; +} +``` +The default implementation `()` returns `Ok(())` without executing any effects. Read more in the [Polkadot documentation](https://wiki.polkadot.network/docs/build-hrmp-channels). diff --git a/docs/content/substrate-runtimes/2.0.2/guides/predeployed_contracts.mdx b/docs/content/substrate-runtimes/2.0.2/guides/predeployed_contracts.mdx new file mode 100644 index 00000000..caa13e3d --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/predeployed_contracts.mdx @@ -0,0 +1,49 @@ +--- +title: Predeployed Contracts +--- + +To enable some bleeding-edge features that EVM provides (like pay gas with any tokens), we have developed an ability to deploy contracts in genesis config. Initially it was done to deploy Entrypoint contract only, but we have supported this functionality for any contract with some limitations. + +## How to use: + +### Step 1: Compiling your contracts + +Currently we are supporting contracts that are compiled with Forge or Hardhat. If you want any other tool to be supported, consider creating an issue and attach and example of compiled contract. + +Here are compilation guides: + +* [Hardhat](https://hardhat.org/hardhat-runner/docs/guides/compile-contracts) +* [Forge](https://book.getfoundry.sh/reference/forge/forge-build) + +When you have compiled the contracts, take the resulting JSON for your contract and save into a single directory for the future deployment. + +### Step 2: Creating the configuration + +In the directory where you have saved the contracts, create a file name "contracts.json". In this file you should store an array of contract metadata, that contains filename of the artifact and the address where you want this contract to be deployed to. So, it should like like this: + +```json +[ + { + "address": "0x81ead4918134AE386dbd04346216E20AB8F822C4", + "filename": "Entrypoint.json" + } +] +``` + +You can take as an example a file in our EVM template in path `contracts/contracts.json`. + +### Step 3: Building the chainspec + +During the step when you generate a chainspec pass the parameter `--predeployed-contracts` with a path to the directory where you have stored the contract artifacts and the configuration: + +```bash +./target/release/parachain-template-node build-spec --disable-default-bootnode --predeployed-contracts= > plain-parachain-chainspec.json +``` + +## Exclude any contracts from genesis config + +If you do not want any contract to be predeployed, you can use the `--no-predeployed-contracts` option when you are generating a plain chainspec. With this flag set you will receive a pristine chainspec without any additional smartcontracts deployed. + +## Limitations + +* Contructors are not executed at the moment. So if your contract needs any initialization, consider deploying it as usual. diff --git a/docs/content/substrate-runtimes/2.0.2/guides/quick_start.mdx b/docs/content/substrate-runtimes/2.0.2/guides/quick_start.mdx new file mode 100644 index 00000000..081ca351 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/quick_start.mdx @@ -0,0 +1,130 @@ +--- +title: Quick start +--- + +This guide will help you deploy your own parachain on the [Paseo](https://github.com/paseo-network) testnet. + +## Prerequisites +* Begin by visiting our [repository](https://github.com/OpenZeppelin/polkadot-runtime-templates). You can fork it, or simply clone it to your local directory. +```bash +git clone git@github.com:OpenZeppelin/polkadot-runtime-templates.git +``` +* Receive some `PSO` from the [Paseo faucet](https://paritytech.github.io/polkadot-testnet-faucet/) +* Reserve a ParaId on Paseo: + * Go to [PolkadotJS](https://polkadot.js.org/apps). Check that it points to Paseo testnet. + * Go to `Network` > `Parachains` + * Go to `Parathreads` tab + * Click the `+ ParaId` button + * Save the `parachain id` for the further usage. + * Click `Submit` and `Sign and Submit`. + +## Creating the chain spec +The chain spec is the collection of information that describes a Substrate-based blockchain network. + +* Move to the directory of the template you want to use. We will use the `generic runtime template` for this tutorial,but the same applies to the [EVM Runtime Template](/substrate-runtimes/2.0.2/runtimes/evm). +```bash +cd generic-template +``` +* Build a release version of the runtime and node: +```bash +cargo build --release +``` +* Generate and customize a chainspec: + + + + +We use the `generic-template-node` executable throughout all the commands since we are using the `generic-template`, but make sure to update the name of the executable if you are using any of the other runtime template. + + + +* Generate a plain chainspec with this command: + + ```bash + ./target/release/generic-template-node build-spec --disable-default-bootnode > plain-parachain-chainspec.json + ``` +* Edit the chainspec: + * Update `name`, `id` and `protocolId` to unique values. + * Change `relay_chain` from `rococo-local` to `paseo`. + * Change `para_id` and `parachainInfo.parachainId` from `1000` to the parachain id you reserved. +* Generate a raw chainspec with this command: + + ```bash + ./target/release/generic-template-node build-spec --chain plain-parachain-chainspec.json --disable-default-bootnode --raw > raw-parachain-chainspec.json + ``` + +## Running the nodes +Now it’s time to run the collator nodes. + +* Run two nodes and wait until it syncs with the Paseo relay chain. This can take a fairly long time(up to 2 days), so we can use the `fast-unsafe` flag to make the process faster since we are on a testnet(~ 3 hours). `fast` downloads the blocks without executing the transactions, and `unsafe` skips downloading the state proofs(which we are ok with since it is a testnet). + + ```bash + ./target/release/generic-template-node \ + --alice \ + --collator \ + --force-authoring \ + --chain raw-parachain-chainspec.json \ + --base-path ./data \ + --port 40333 \ + --rpc-port 8844 \ + -- \ + --execution wasm \ + --chain \ + --port 30343 \ + --rpc-port 9977 \ + --sync fast-unsafe + ``` + + ```bash + ./target/release/generic-template-node \ + --bob \ + --collator \ + --force-authoring \ + --chain raw-parachain-chainspec.json \ + --base-path ./data \ + --port 40333 \ + --rpc-port 8845 \ + -- \ + --execution wasm \ + --chain \ + --port 30343 \ + --rpc-port 9977 \ + --sync fast-unsafe + ``` + * `` is where your Paseo chainspec is stored. You can download this file from the [official Polkadot sdk repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/polkadot/node/service/chain-specs/paseo.json). +* Register a parathread: + * Generate a genesis state: + + ```bash + ./target/release/generic-template-node export-genesis-state --chain raw-parachain-chainspec.json para--genesis-state + ``` + * Generate a genesis wasm: + + ```bash + ./target/release/generic-template-node export-genesis-wasm --chain raw-parachain-chainspec.json para--wasm + ``` + * Go to [PolkadotJS](https://polkadot.js.org/apps). Check that it points to Paseo testnet. + * Go to `Network` > `Parachains`. + * Go to `Parathreads` tab. + * Click the `+ ParaThread` button. + * Insert `para--wasm` to `code` field. + * Insert `para--genesis-state` to `initial state` field. + * Click `Submit` and `Sign and Submit`. + +## Testing your parachain + +When a parachain gets synced with the relaychain, you may start producing blocks as a parathread: + +* Create some transaction with PolkadotJS pointing to your parachain setup. +* With PolkadotJS pointing to Paseo go to `Developer` > `Extrinsics`. +* Submit an extrinsic `onDemandAssignmentProvider.placeOrderAllowDeath` or `onDemandAssignmentProvider.placeOrderKeepAlive`: + * `maxAmount` should be not less than 10_000_000. + * `paraId` should be set to your parachain id. + * Click `Submit` and `Sign and Submit`. + After a short period, your parathread will produce a block. This block will then be included in one of the subsequent Paseo relay chain blocks. + +## What's next? + +* Read our general guides to understand more about the concepts of runtime development. +* Learn more about the runtime configuration. Currently, we have two runtime templates: [Generic Runtime Template](/substrate-runtimes/2.0.2/runtimes/generic) and [EVM Runtime Template](/substrate-runtimes/2.0.2/runtimes/evm). +* Explore the documentation for pallets. It may be useful if you are considering building a frontend for your parachain. diff --git a/docs/content/substrate-runtimes/2.0.2/guides/rpc_differences.mdx b/docs/content/substrate-runtimes/2.0.2/guides/rpc_differences.mdx new file mode 100644 index 00000000..954c974b --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/rpc_differences.mdx @@ -0,0 +1,12 @@ +--- +title: RPC Differences +--- + +The EVM in a Substrate node has almost the same RPC calls common to all nodes. But some of them are not applicable for Frontier. + +These are the calls that behave differently than would be expected by regular Ethereum nodes: + +* `eth_sign` / `eth_signTransaction` -- these methods do not exist in the Frontier and this template because it, unlike Ethereum nodes, does not hold any keys. +* `eth_syncing` -- it can have additional fields `warp_chunks_amount` and `warp_chunks_processed` that describe warp syncing process. + +Other calls comply to the same scheme as Ethereum nodes provide. diff --git a/docs/content/substrate-runtimes/2.0.2/guides/testing_with_zombienet.mdx b/docs/content/substrate-runtimes/2.0.2/guides/testing_with_zombienet.mdx new file mode 100644 index 00000000..43b607c0 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/testing_with_zombienet.mdx @@ -0,0 +1,290 @@ +--- +title: Testing Solidity Smart Contracts with Zombienet +--- + +In this tutorial, we will demonstrate how to deploy your parachain using Zombienet, and test the functionalities of your parachain. + +Below are the main steps of this demo: +1. Deploy our parachain against the locally simulated Paseo testnet by Zombienet. +2. Deploy a Solidity smart contract on our parachain. +3. Successfully invoke the Solidity smart contract deployed on our parachain. + +## Step 1: Deploy The Parachain + +1. Git clone the repository: + ```bash + git clone https://github.com/OpenZeppelin/polkadot-runtime-templates + ``` + +2. Move to evm template directory: + ```bash + cd evm-template + ``` + +3. Replace the content of `zombinet-config/devnet.toml` with: + ```toml + [relaychain] + chain = "rococo-local" + default_command = "./bin-v1.6.0/polkadot" + + [[relaychain.nodes]] + name = "alice" + validator = true + + [[relaychain.nodes]] + name = "bob" + validator = true + + [relaychain.genesis.runtime.runtime_genesis_config.configuration.config] + scheduling_lookahead = 2 + + [relaychain.genesis.runtime.runtime_genesis_config.configuration.config.async_backing_params] + max_candidate_depth = 3 + allowed_ancestry_len = 2 + ``` + +4. Build the zombienet: + ```bash + ./scripts/zombienet.sh build + ``` + + If you came across this error (click to expand): +
+ Details + + ```bash + error[E0635]: unknown feature `stdsimd` + --> /Users/ozgunozerk/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ahash-0.7.6/src/lib.rs:33:42 + | + 33 | #![cfg_attr(feature = "stdsimd", feature(stdsimd))] + ``` + + `cd` into the `polkadot-sdk` directory (the path should be visible on the error message), and run the below command to update `ahash`: + + ```bash + cargo update -p ahash@0.7.6 + ``` +
+ + Or if you came across this error (click to expand): +
+ Details + + ```bash + assertion failed [block != nullptr]: BasicBlock requested for unrecognized address + ``` + + Just re-run 🙂 +
+ +5. Run the zombienet: + ```bash + ./scripts/zombienet.sh devnet + ``` + +6. Build it with `async-backing` feature: + ```bash + cargo build --release --features="async-backing" + ``` + +7. Copy the `Direct Link` from `Alice's` tab from Zombienet TUI: + ![Alice Direct Link](/alice-direct-link.png) + +8. Open the link with Chrome: [PolkadotJS](https://polkadot.js.org/apps). As of 2024 July, it doesn't work on Safari and Brave, and it is buggy on Firefox. + +9. Reserve a `ParaId` on Zombienet: + 1. Go to `Network` > `Parachains` + 2. Go to `Parathreads` tab + 3. Click the `+ ParaId` button + 4. Save a `parachain id` for the further usage. + 5. Click `Submit` and `Sign and Submit` (you can use `Alice` as the account). + +10. Preparing necessary files to become a Parachain: + 1. Generate a plain chainspec: + ```bash + ./target/release/evm-template-node build-spec --disable-default-bootnode > plain-parachain-chainspec.json + ``` + 2. Edit the chainspec: + 1. Update `name`, `id` and `protocolId` to unique values (optional). + 2. Change `para_id` and `parachainInfo.parachainId` from `1000` to the previously saved parachain id (probably 2000 if that's your first time ;) ). + 3. Edit the `evmChainId.chainId` to the value of your choice. Try to select a value that has no existing EVM chain assigned to it (should be ok to leave as is for the most cases). + 3. Generate a raw chainspec: + ```bash + ./target/release/evm-template-node build-spec --chain plain-parachain-chainspec.json --disable-default-bootnode --raw > raw-parachain-chainspec.json + ``` + 4. Generate the genesis state: + ```bash + ./target/release/evm-template-node export-genesis-state --chain raw-parachain-chainspec.json > genesis-state + ``` + 5. Generate the validation code: + ```bash + ./target/release/evm-template-node export-genesis-wasm --chain raw-parachain-chainspec.json > genesis-wasm + ``` + +11. Registering the Parachain: + 1. Go back to `polkadot.js.org/apps` (remember Chrome). Go to `Developer/Sudo`. + 2. Select `pasasSudoWrapper` and `sudoScheduleParaInitialize(id, genesis)` + 3. Enter the reserved id (2000) to `id` field + 4. Enable `file upload` for both `genesisHead` and `validationCode` → because we will upload files for these. + 5. Select `Yes` for `paraKind` → meaning when we register our parachain, it will be a parachain rather than a parathread. + 6. Drag & drop your `genesis-state` file generated in step `10.d` into `genesisHead` field (good luck with the aiming) + 7. Drag & drop your `genesis-wasm` file generated in `10.e` into `validationCode` field + 8. It should look like below: + ![Register Parachain](/register-parachain.png) + 9. `Submit Sudo`! + +12. Copy the path to `chain-spec` from zombienet terminal from `Bob` (beware, this file is changing every time you spin up a new zombienet): + ![Zombie Chain Spec](/zombie-chain-spec.png) + +13. Run the node, and provide the `chain-spec` you copied from the last step into `--chain` part: + - Be sure to clear your storage if you were running a node before + ```bash + ./target/release/evm-template-node \ + --alice \ + --collator \ + --force-authoring \ + --chain raw-parachain-chainspec.json \ + --base-path storage/alice \ + --port 40333 \ + --rpc-port 8844 \ + -- \ + --execution wasm \ + --chain /var/folders/...\{redacted\}.../rococo-local.json \ + --port 30343 \ + --rpc-port 9977 + ``` + +14. Your node should be running without any problem, and should see block production in your node terminal! + ![Node Success](/node-success.png) + +## Step 2: Deploy a Solidity Smart Contract + +1. Install Foundry with: `curl -L https://foundry.paradigm.xyz | bash` + +2. Have a smart contract file ready, any smart contract of your choice! We will go with a cute `HelloWorld.sol` smart contract for this tutorial: + ```solidity + pragma solidity ^0.8.0; + + contract HelloWorld { + string public greeting = "Hello, World!"; + + function getGreeting() public view returns (string memory) { + return greeting; + } + } + ``` + +3. Create a new javascript project with the below files: + 1. `package.json`: + ```json + { + "name": "ts-wallet", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "exec": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "web3": "^4.8.0" + } + } + ``` + + 2. `sanity_check.js`: + ```js + import { Web3 } from "web3"; + + const web3 = new Web3("ws://127.0.0.1:8844"); + + console.log("Balance:"); + web3.eth.getBalance("0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac").then(console.log); + + let raw = await web3.eth.accounts.signTransaction({ + gas: 21000, + gasPrice: 10000000000, + from: "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac", // Alith's address + to: "0x7c98a1801f0B28dF559bCd828fc67Bd6ab558074", // Baltathar's address + value: '100000000000000000' + }, "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"); // Alith's private key + + let res = await web3.eth.sendSignedTransaction(raw.rawTransaction); + console.log("Transaction details:"); + console.log(res); + ``` + + 3. `invoke_smart_contract.js`: + ```js + import { Web3 } from "web3"; + import { MyAbi } from "./abi.js"; + + const web3 = new Web3("ws://127.0.0.1:8844"); + + let contract = new web3.eth.Contract(MyAbi, "0x4045F03B68919da2c440F023Fd7cE2982BfD3C03"); + let s = await contract.methods.getGreeting().call(); + + console.log(s); + ``` + + 4. `abi.js`: + ```js + export var MyAbi = [ + { + "type": "function", + "name": "getGreeting", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "greeting", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + } + ]; + ``` + +4. Run the below command, and you should see the balance, and then the transaction details printed, proving everything works so far! + ```bash + node sanity_check.js + ``` + +5. Open a terminal instance where the current directory has the `HelloWorld.sol` file, and run the below command to deploy the contract with Alith's private key: + ```bash + forge create --rpc-url http://localhost:9933 --private-key 0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133 HelloWorld.sol:HelloWorld + ``` + - Don't forget to copy the address this contract deployed to (shown in the output of the command)! + +## Step 3: Invoke The Solidity Smart Contract + +1. Replace the contract address in `invoke_smart_contract.js` with the address you copied! + +2. Build the `abi` of the smart contract with: + ```bash + forge build --silent && jq '.abi' ./out/HelloWorld.sol/HelloWorld.json + ``` + +3. Surprise! We already give you the abi of this in `abi.js` file in step `3`. If you used another contract than `HelloWorld`, replace that `abi.js` file's content with your contracts `abi`. + +4. Run the below command, and you should see your smart contract in action: + ```bash + node invoke_smart_contract.js + ``` diff --git a/docs/content/substrate-runtimes/2.0.2/guides/weights_fees.mdx b/docs/content/substrate-runtimes/2.0.2/guides/weights_fees.mdx new file mode 100644 index 00000000..e510e888 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/guides/weights_fees.mdx @@ -0,0 +1,58 @@ +--- +title: Weights & Fees +--- + +Weight is a metric to estimate the time it takes to execute code. + +In Substrate, every transaction has a weight. The block production algorithm selects the set of transactions from the transaction pool that achieve block saturation without exceeding `MaximumBlockWeight`. The `AvailableBlockRatio` ensures only a fraction of `MaximumBlockWeight` is used for regular transactions while system-critical operations (operational transactions) may use all remaining block weight. + +Substrate charges every transaction’s caller with fees in proportion to the cost of execution. It does so by converting each transaction’s weight to fees via `WeightToFee`. + +Weights which underestimate cost of execution expose a DDOS vulnerability. If a transaction takes up less space than it should, too many transactions may be included in the block and the block’s execution time may exceed expected block time. For parachains, this DDOS vulnerability can be critical; it may prevent future block production by bricking the chain until the parachain state is reset on the relay chain. + +It is imperative for runtime developers to be conservative when approximating weight. Moreover, runtime complexity that is non-linear must be limited and bounded. + +## Runtime-Specific Weights + +Production runtimes should never set `WeightInfo = ()` in production because this configuration underestimates weight. Instead, every production runtime should generate and set its own weights specific to its runtime. + +1. Ensure passing status for benchmarking compilation by running this command: +``` +cargo build --features runtime-benchmarks +``` +2. Run the benchmarking command and write its output to the runtime. Here is the command via bash script: +``` +#!/bin/sh + +# Build release runtime benchmarks +cargo build --release --features=runtime-benchmarks + +# Collect all pallets needed for benchmarking +# Makes the assumption all pallets are present at: /pallets/$PALLET_NAME +pallets=$(ls pallets/) + +# Generate weights +for pallet_name in $pallets; do + ./target/release/node benchmark pallet \ + --pallet pallet_$pallet_name \ + --extrinsic "*" \ + --steps 50 \ + --repeat 20 \ + --output ./runtime/src/weights/$pallet_name.rs +done +``` +1. Automate the benchmarking pipeline. The first step may be automated by enforcing the command in the CI. The second step may be automated by integrating a benchmarking github bot. For an example, see [Parity’s Command Bot](https://github.com/paritytech/command-bot). + +## More Reading + +Technically, **Weight** represents the time it takes to execute code on **production hardware** used by nodes actively participating in the network’s consensus. + +``` +1 unit of weight = 1 picosecond of execution time on target reference hardware +``` + +It is important to ONLY generate weights on **production hardware**. [Polkadot Reference Hardware Docs](https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#:~:text=Reference%20Hardware%E2%80%8B,instance%20on%20GCP%20and%20c6i) provides more information on Polkadot validator hardware requirements. + +### References + +[Substrate Weight & Fees](https://www.shawntabrizi.com/blog/substrate/substrate-weight-and-fees/) by [Shawn Tabrizi](https://github.com/shawntabrizi/) diff --git a/docs/content/substrate-runtimes/2.0.2/index.mdx b/docs/content/substrate-runtimes/2.0.2/index.mdx new file mode 100644 index 00000000..3cbd7d6f --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/index.mdx @@ -0,0 +1,13 @@ +--- +title: Polkadot Parachain Runtimes +--- + +A collection of secure runtime templates to build parachains more easily on Polkadot. + +## Runtimes +* [Generic Runtime Template](/substrate-runtimes/2.0.2/runtimes/generic) +* [EVM Runtime Template](/substrate-runtimes/2.0.2/runtimes/evm) + +## Where to get started +* [Quick Start](./guides/quick_start): a generic parachain runtime that works out of the box. It has all the must have features, and allows further customization based on your project’s needs. Generic Runtime Template also serves as the base for our other runtime templates. +* [Testing with Zombienet](./guides/testing_with_zombienet): a more opinionated parachain runtime template that maximizes Ethereum compatibility by using `AccountId20` and configures a local EVM instance. You can easily migrate/deploy Solidity Smart Contracts to this one. diff --git a/docs/content/substrate-runtimes/2.0.2/misc/multisig-accounts.mdx b/docs/content/substrate-runtimes/2.0.2/misc/multisig-accounts.mdx new file mode 100644 index 00000000..ce1026df --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/misc/multisig-accounts.mdx @@ -0,0 +1,15 @@ +--- +title: Multisig Accounts +--- + +* Multisig accounts do not have a private key. They are managed by their signatories. + * Q: how can it be secure if it does not have a private key + * A: substrate uses `origin` for accounts. Each account signs their transaction. Someone who does not belong to the signatories of a multisig account, cannot approve/dispatch calls in the name of the multisig account, even if they wanted to. Knowing the public key of the account is not enough, since the approval message needs to belong to signatories, and each message is signed by the private key. So, effectively, a multisig account indirectly has private keys as many as its signatory count, and thus, a multisig account does not need a separate private key. +* Creating a multisig account is basically just deriving the public key of it. Just like the creation of a new personal account, we are just deriving the public key. We don’t store this public key on-chain for the creation of the account. The only difference between personal accounts and multisig accounts is, the public key derivation process: + * for a single account, the process is roughly: + * generating a private key, + * deriving the public key from the private key. + * for multisig accounts, the process is roughly: + * supplying the public keys of the accounts that will belong to the multisig account + * supplying the threshold for this multisig account (same signatories with different threshold qualifies as a different multisig account) + * deriving the public key from combining and hashing threshold and signatories’ public keys diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/assets.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/assets.mdx new file mode 100644 index 00000000..3c37957f --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/assets.mdx @@ -0,0 +1,768 @@ +--- +title: pallet_assets +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/assets/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +The `pallet_assets` module is designed to manage and manipulate custom fungible asset types. It provides functionality for asset creation, management, and various operations, allowing for a flexible and scalable approach to asset handling. + +## Pallet specific terms + +* `Asset ID` - A unique identifier for an asset type. +* `Asset Owner` - The account that owns an asset. +* `Admin` - The account that has administrative privileges over an asset, such as: `minting`, `burning`, `freezing`, and `thawing`. +* `Sufficiency` - The amount of an asset that is required to be held in an account in order to perform certain operations, such as: `transfers`, `freezing`, and `thawing`. + +## Config + +* Pallet-specific configs + * `Balance` -- The balance unit type. + * `RemoveItemsLimit` -- The maximum number of items that can be removed in a single `destroy_accounts` and `destroy_approvals` call. + * `AssetId` -- The unique asset identifiers. + * `AssetIdParameter` -- The parameter type for asset identifiers. + * `Currency` -- The currency type. + * `CreateOrigin` -- The origin type that can create an asset. + * `ForceOrigin` -- The origin type that can force an asset action. + * `AssetDeposit` -- The deposit required for reservation to create an asset. + * `AssetAccountDeposit` -- The deposit required for a non-provider asset account for reservation to create an asset account. + * `MetadataDepositBase` -- The base deposit required to reserve metadata. + * `MetadataDepositPerByte` -- The deposit required to reserve metadata per byte. + * `ApprovalDeposit` -- The deposit required to reserve an approval. + * `StringLimit` -- The maximum length of a name or symbol. + * `Freezer` -- A hook to allow a per-asset, per-account minimum balance to be enforced. + * `Extra` -- The extra data to be stored with an account’s asset balance. + * `CallbackHandle` -- An asset created or destroyed callback functions. + * `BenchmarkHelper` -- A helper type to benchmark the pallet’s weight. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- [Weight](/substrate-runtimes/2.0.2/glossary#weight) information for extrinsics in this pallet. + +## Events + +* `Created(asset_id, creator, owner)` -- An asset was created. +* `Issued(asset_id, owner, amount)` -- An asset was issued. +* `Transferred(asset_id, from, to, amount)` -- An asset was transferred. +* `Burned(asset_id, owner, balance)` -- An asset was burned. +* `TeamChanged(asset_id, issuer, admin, freezer)` -- The management team of an asset was changed. +* `OwnerChanged(asset_id, owner)` -- The owner of an asset was changed. +* `Frozen(asset_id, who)` -- An account `who` was frozen. +* `Thawed(asset_id, who)` -- An account `who` was thawed. +* `AssetFrozen(asset_id)` -- An asset was frozen. +* `AssetThawed(asset_id)` -- An asset was thawed. +* `AccountsDestroyed(asset_id, accounts_destroyed, accounts_remaining)` -- A number `accounts_destroyed` accounts were destroyed for an asset. +* `ApprovalsDestroyed(asset_id, approvals_destroyed, approvals_remaining)` -- A number `approvals_destroyed` approvals were destroyed for an asset. +* `DestructionStarted(asset_id)` -- The destruction of an asset class was started. +* `Destroyed(asset_id)` -- An asset class was destroyed. +* `ForceCreated(asset_id, owner)` -- An asset was force created. +* `MetadataSet(asset_id, name, symbol, decimals, is_frozen)` -- The metadata of an asset was set. +* `MetadataCleared(asset_id)` -- The metadata of an asset was cleared. +* `ApprovedTransfer(asset_id, source, delegate, amount)` -- An approval was made for a delegate to transfer a specific amount of an asset on behalf of the owner. +* `ApprovalCancelled(asset_id, owner, delegate)` -- An approval was cancelled for a delegate to transfer a specific amount of an asset on behalf of the owner. +* `TransferredApproved(asset_id, owner, delegate, destination, amount)` -- A delegate transferred a specific amount of an asset on behalf of the owner. +* `AssetStatusChanged(asset_id)` -- The attributes of an asset was forcely changed. +* `AssetMinBalanceChanged(asset_id, new_min_balance)` -- The minimum balance of an asset was changed by an owner. +* `Touched(asset_id, who, depositor)` -- An account `who` was created with a deposit from `depositor`. +* `Blocked(asset_id, who)` -- An account `who` was blocked. + +## Errors + +* `BalanceLow` -- Account balance must be greater than or equal to the transfer amount. +* `NoAccount` -- The account to alter does not exist. +* `NoPermission` -- The signing account has no permission to do the operation. +* `Unknown` -- The given asset ID is unknown. +* `Frozen` -- The origin account is frozen. +* `InUse` -- The asset ID is already taken. +* `BadWitness` -- Invalid witness data given. +* `MinBalanceZero` -- Minimum balance should be non-zero. +* `UnavailableConsumer` -- Unable to increment the consumer reference counters on the account. Either no provider reference exists to allow a non-zero balance of a non-self-sufficient asset, or one fewer then the maximum number of consumers has been reached. +* `BadMetadata` -- Invalid metadata given. +* `Unapproved` -- No approval exists that would allow the transfer. +* `WouldDie` -- The source account would not survive the transfer and it needs to stay alive. +* `AlreadyExists` -- The asset-account already exists. +* `NoDeposit` -- The asset-account doesn’t have an associated deposit. +* `WouldBurn` -- The operation would result in funds being burned. +* `LiveAsset` -- The asset is a live asset and is actively being used. Usually emit for operations such as `start_destroy` which require the asset to be in a destroying state. +* `AssetNotLive` -- The asset is not live, and likely being destroyed. +* `IncorrectStatus` -- The asset status is not the expected status. +* `NotFrozen` -- The asset should be frozen before the given operation. +* `CallbackFailed` -- Callback action resulted in error. + +## Dispatchables + +#### `[.contract-item-name]#`create`#` +```rust +pub fn create( + origin: OriginFor, + id: T::AssetIdParameter, + admin: AccountIdLookupOf, + min_balance: T::Balance, +) -> DispatchResult +``` +Issues a new class of fungible assets from a public origin. If an asset was not created, raises `CallbackFailed`. + + +The origin must conform to the configured `CreateOrigin` and have sufficient funds free. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the identifier of the asset to be created. +* `admin: AccountIdLookupOf` -- the admin of this asset, who can execute all privileged functions. +* `min_balance: T::Balance` -- the minimum balance of this new asset that any single account must have. + +#### `[.contract-item-name]#`force_create`#` +```rust +pub fn force_create( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + is_sufficient: bool, + #[pallet::compact] min_balance: T::Balance, +) -> DispatchResult +``` +Issues a new class of fungible assets from a privileged origin. + + +The origin must conform to `ForceOrigin`. Unlike `create`, no funds are reserved. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the unique identifier of the asset to be created. +* `owner: AccountIdLookupOf` -- the owner of this asset, who can mint/burn tokens. +* `is_sufficient: bool` -- whether the owner account should be checked for minimum balance. + +#### `[.contract-item-name]#`start_destroy`#` +```rust +pub fn start_destroy( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Start the process of destroying a fungible asset class. + + +`start_destroy` is the first in a series of extrinsics that should be called, to allow destruction of an asset class. The origin must conform to `ForceOrigin` or must be `Signed` by the asset’s `owner`. The asset class must be frozen before calling `start_destroy`. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the identifier of the existing asset to be destroyed. + +#### `[.contract-item-name]#`destroy_accounts`#` +```rust +pub fn destroy_accounts( + origin: OriginFor, + id: T::AssetIdParameter, +) -> DispatchResultWithPostInfo +``` + +Destroys accounts associated with a given asset. + + +`destroy_accounts` is the second in a series of extrinsics that should only be called after `start_destroy`, to allow destruction of an asset class. The origin must conform to `ForceOrigin` or must be `Signed` by the asset’s `owner`. It will call `T::RemoveItemsLimit::get()` so if the number of accounts exceeds this limit, this function need to be called several times. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the identifier of the existing asset to be destroyed. + +#### `[.contract-item-name]#`destroy_approvals`#` +```rust +pub fn destroy_approvals( + origin: OriginFor, + id: T::AssetIdParameter, +) -> DispatchResult +``` +Clears approvals associated with a given asset. + + +It should be called after `start_destroy`. It will call `T::RemoveItemsLimit::get()` so if the number of approvals exceeds this limit, this function need to be called several times. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, root) account. +* `id: T::AssetIdParameter` -- the identifier of the asset for which all approvals will be destroyed. + +#### `[.contract-item-name]#`finish_destroy`#` +```rust +pub fn finish_destroy( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Completes the process of asset destruction initiated by `start_destroy`. + + +This function is the final step in the asset destruction process. It should only be called after all checks and balances have been done, typically after `start_destroy` and `destroy_accounts` are successfully called. The origin must conform to `ForceOrigin` or be `Signed` by the asset’s owner, ensuring that this sensitive action is adequately authorized. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, root) account. +* `id: T::AssetIdParameter` -- the identifier of the asset to be destroyed. + +#### `[.contract-item-name]#`mint`#` +```rust +pub fn mint( + origin: OriginFor, + id: T::AssetIdParameter, + beneficiary: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Mints new units of a specific asset and assigns them to a beneficiary account. + + +The `mint` function allows authorized accounts to increase the supply of an asset. The origin must be authorized to mint assets, typically configured via `MintOrigin`. The `#[pallet::compact]` attribute is used to optimize the storage of the `amount` parameter. This function ensures that the total supply doesn’t overflow and that the beneficiary is capable of holding the asset. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which units are to be minted. +* `beneficiary: AccountIdLookupOf` -- the account that will receive the newly minted units. +* `amount: T::Balance` -- the amount of new units to be minted and credited to the beneficiary. + +#### `[.contract-item-name]#`burn`#` +```rust +pub fn burn( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Destroys units of a specific asset from the specified account. + + +The `burn` function decreases the supply of an asset by removing a specified amount from a particular account. The origin must be authorized to burn assets, typically configured via `BurnOrigin`. The `#[pallet::compact]` attribute optimizes the storage of the `amount` parameter. This function is used for asset management, such as reducing supply or removing assets from circulation for regulatory compliance. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset from which units are to be burned. +* `who: AccountIdLookupOf` -- the account from which the units will be destroyed. +* `amount: T::Balance` -- the amount of units to be burned from the specified account. + +#### `[.contract-item-name]#`transfer`#` +```rust +pub fn transfer( + origin: OriginFor, + id: T::AssetIdParameter, + target: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Transfers a specified amount of a specific asset to a target account. + + +The `transfer` function allows assets to be moved between accounts. The origin must be signed by the account wishing to transfer assets and must have sufficient balance. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. This function checks for liquidity, asset validity, and the receiving account’s ability to accept the asset, ensuring secure and accurate transactions. + + +***Params:*** + +* `origin: OriginFor` -- the caller account initiating the transfer. +* `id: T::AssetIdParameter` -- the identifier of the asset to be transferred. +* `target: AccountIdLookupOf` -- the recipient account of the asset units. +* `amount: T::Balance` -- the amount of units to transfer from the caller to the target account. The amount actually transferred may be slightly greater in the case that the transfer would otherwise take the sender balance above zero but below the minimum balance. Must be greater than zero. + +#### `[.contract-item-name]#`transfer_keep_alive`#` +```rust +pub fn transfer_keep_alive( + origin: OriginFor, + id: T::AssetIdParameter, + target: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Transfers a specified amount of a specific asset to a target account, ensuring that the transfer does not result in the sender’s total demise. + + +The `transfer_keep_alive` function is similar to `transfer` but includes an additional check that prevents the transfer if it would cause the origin account to be reaped. This is critical for ensuring the account’s continued existence, particularly for accounts with minimum balance requirements. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. Like `transfer`, it ensures secure and precise asset movement between accounts. + + +***Params:*** + +* `origin: OriginFor` -- the caller account initiating the transfer. +* `id: T::AssetIdParameter` -- the identifier of the asset to be transferred. +* `target: AccountIdLookupOf` -- the recipient account of the asset units. +* `amount: T::Balance` -- the amount of units to transfer from the caller to the target account, while ensuring the sender’s account remains active. The amount actually transferred may be slightly greater in the case that the transfer would otherwise take the sender balance above zero but below the minimum balance. Must be greater than zero. + +#### `[.contract-item-name]#`force_transfer`#` +```rust +pub fn force_transfer( + origin: OriginFor, + id: T::AssetIdParameter, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Forces the transfer of a specified amount of a specific asset from a source account to a destination account. + + +The `force_transfer` function is an administrative tool that allows a privileged origin, typically configured via `ForceOrigin`, to move assets between accounts without consent from the source. This might be used in exceptional scenarios, such as legal or administrative resolutions. The `#[pallet::compact]` attribute optimizes storage of the `amount` parameter. This function must be used with caution due to its power and potential impact on account balances. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be forcibly transferred. +* `source: AccountIdLookupOf` -- the account from which the asset units will be debited. +* `dest: AccountIdLookupOf` -- the account to which the asset units will be credited. +* `amount: T::Balance` -- the amount of units to forcibly transfer from the source to the destination account. The amount actually transferred may be slightly greater in the case that the transfer would otherwise take the sender balance above zero but below the minimum balance. Must be greater than zero. + +#### `[.contract-item-name]#`freeze`#` +```rust +pub fn freeze( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Freezes the specified asset in a particular account, preventing further unprivileged transfers of the asset from the frozen account. + + +The `freeze` function is used to halt all operations for a specified asset in a given account, typically for regulatory or compliance reasons. This action can only be performed by an authorized origin, usually the asset’s admin or another account with special privileges. Once an account is frozen, it cannot transact the specified asset until an `thaw` operation is performed. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be frozen. +* `who: AccountIdLookupOf` -- the account in which the asset will be frozen. Must already exist as an entry in `Account`s of the asset. + +#### `[.contract-item-name]#`thaw`#` +```rust +pub fn thaw( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Unfreezes the specified asset in a particular account, allowing the resumption of unprivileged transfers of the asset. + + +The `thaw` function reverses the effect of `freeze`, re-enabling the account to transact with the specified asset. This action is typically restricted to authorized origins, such as the asset’s admin or other accounts with special privileges. It is used in scenarios where the conditions that led to the initial freezing have been resolved or are no longer applicable. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be thawed. +* `who: AccountIdLookupOf` -- the account in which the asset will be thawed. + +#### `[.contract-item-name]#`freeze_asset`#` +```rust +pub fn freeze_asset( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Freezes all operations for a specified asset across all accounts, halting any transfer, minting, or other asset-related activities. + + +The `freeze_asset` function is a comprehensive freeze operation affecting all accounts holding the specified asset. It is intended for emergency or regulatory situations requiring immediate suspension of all activities related to the asset. This action typically requires authorization from a privileged origin, which might be the asset’s admin or a specific governance mechanism in place. The freeze remains in effect until an `thaw_asset` operation is performed. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be frozen. + +#### `[.contract-item-name]#`thaw_asset`#` +```rust +pub fn thaw_asset( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Unfreezes all operations for a specified asset across all accounts, allowing resumption of transfers, minting, and other asset-related activities. + + +The `thaw_asset` function reverses the comprehensive freeze applied by `freeze_asset`, re-enabling the normal operation of all activities related to the asset across all accounts. It is typically used once the conditions necessitating the freeze are no longer applicable or have been resolved. This action usually requires authorization from a privileged origin, such as the asset’s admin or a specific governance mechanism. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be thawed. + +#### `[.contract-item-name]#`transfer_ownership`#` +```rust +pub fn transfer_ownership( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, +) -> DispatchResult +``` +Transfers the ownership of a specific asset to another account. + + +The `transfer_ownership` function is used to change the admin or owner of an asset to a new account. This operation might be necessary for administrative restructuring, transferring responsibilities, or ownership succession scenarios. The origin must be the current owner or authorized to transfer ownership, ensuring proper authorization and security in the ownership transfer process. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which ownership is being transferred. +* `owner: AccountIdLookupOf` -- the account that will become the new owner of the asset. + +#### `[.contract-item-name]#`set_team`#` +```rust +pub fn set_team( + origin: OriginFor, + id: T::AssetIdParameter, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, +) -> DispatchResult +``` +Sets the team managing a specific asset, defining roles for issuance, administration, and freezing operations. + + +The `set_team` function is used to designate specific accounts for managing various aspects of an asset. This includes issuing new units of the asset, administering ownership or other significant changes, and freezing or thawing operations. The origin must be the current owner or another authorized account to ensure proper governance and control over the asset. This function allows for flexible and secure management of assets by clearly separating responsibilities among different roles. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the team is being set. +* `issuer: AccountIdLookupOf` -- the account designated for issuing new units of the asset. +* `admin: AccountIdLookupOf` -- the account designated for administrative tasks. +* `freezer: AccountIdLookupOf` -- the account designated for freezing and thawing the asset. + +#### `[.contract-item-name]#`set_metadata`#` +```rust +pub fn set_metadata( + origin: OriginFor, + id: T::AssetIdParameter, + name: Vec, + symbol: Vec, + decimals: u8, +) -> DispatchResult +``` +Sets or updates the metadata for a specific asset, including its name, symbol, and decimals. + + +The `set_metadata` function is used to define or update the descriptive attributes of an asset. These attributes include the asset’s name, a short symbol, and the number of decimals that represent the asset’s smallest unit. This information is crucial for user interfaces and third-party integrations to properly display and understand the asset’s properties. The origin must have the appropriate permissions to modify an asset’s metadata, ensuring that only authorized users can make changes. Funds of sender are reserved according to the formula: `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into account any already reserved funds. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being set. +* `name: Vec` -- the new name of the asset as a byte array. +* `symbol: Vec` -- the new symbol of the asset as a byte array. +* `decimals: u8` -- the number of decimals places used to represent the asset. + +#### `[.contract-item-name]#`clear_metadata`#` +```rust +pub fn clear_metadata( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Clears the metadata of a specific asset, removing its name, symbol, and decimals information. + + +The `clear_metadata` function is used to remove all descriptive information associated with an asset. This may be necessary in situations where the asset’s properties have changed significantly, or the asset is being deprecated. Clearing metadata is an important aspect of asset management and requires proper authorization to ensure that only valid users can perform this action. Once cleared, the asset will no longer have descriptive labels or detailed display information until new metadata is set. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being cleared. + +#### `[.contract-item-name]#`force_set_metadata`#` +```rust +pub fn force_set_metadata( + origin: OriginFor, + id: T::AssetIdParameter, + name: Vec, + symbol: Vec, + decimals: u8, + is_frozen: bool, +) -> DispatchResult +``` +Forcibly sets or updates the metadata for a specific asset, including its name, symbol, decimals, and frozen status, regardless of the current owner’s permissions. + + +The `force_set_metadata` function is an administrative tool that allows privileged accounts to set or update the metadata of an asset. This includes force-changing the name, symbol, decimals, and freeze status. It is used in scenarios requiring higher-level intervention, such as regulatory compliance or significant asset restructuring. The origin must be authorized to perform forceful administrative actions, ensuring that only valid scenarios permit such drastic changes. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being forcibly set. +* `name: Vec` -- the new name of the asset as a byte array. +* `symbol: Vec` -- the new symbol of the asset as a byte array. +* `decimals: u8` -- the number of decimal places used to represent the asset. +* `is_frozen: bool` -- the new frozen status of the asset, determining if it can be transacted. + +#### `[.contract-item-name]#`force_clear_metadata`#` +```rust +pub fn force_clear_metadata( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Forcibly clears all metadata of a specific asset, including its name, symbol, and decimals, by an administrative order. + + +The `force_clear_metadata` function is an administrative action used in circumstances that require overriding the usual asset management, such as regulatory compliance or significant changes to the asset’s structure or purpose. This function removes all descriptive information, essentially resetting the asset’s public-facing details. The origin must be a privileged account authorized to perform such forceful actions, ensuring that the clearance is done under proper oversight and for valid reasons. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being forcibly cleared. + +#### `[.contract-item-name]#`force_asset_status`#` +```rust +pub fn force_asset_status( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, + #[pallet::compact] min_balance: T::Balance, + is_sufficient: bool, + is_frozen: bool, +) -> DispatchResult +``` +Forcibly changes the status of a specific asset, including ownership, management team, minimum balance, sufficiency status, and frozen status. + + +The `force_asset_status` function is a powerful administrative tool used to configure or reconfigure critical aspects of an asset’s behavior and control. It is typically used in scenarios requiring immediate intervention or significant restructuring. The function allows changing the owner, issuer, admin, and freezer accounts, setting the minimum balance required for the asset, and determining whether the asset should be considered sufficient for existential deposit purposes or frozen entirely. Due to its significant impact, this function is restricted to privileged origins authorized for such impactful changes. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being modified. +* `owner: AccountIdLookupOf` -- the account designated as the new owner of the asset. +* `issuer: AccountIdLookupOf` -- the account designated for issuing new units of the asset. +* `admin: AccountIdLookupOf` -- the account designated for administrative tasks. +* `freezer: AccountIdLookupOf` -- the account designated for freezing and thawing the asset. +* `min_balance: T::Balance` -- the new minimum balance required for the asset. +* `is_sufficient: bool` -- indicates if the asset should be considered sufficient for existential deposit purposes. +* `is_frozen: bool` -- indicates if the asset is to be frozen across all accounts. + +#### `[.contract-item-name]#`approve_transfer`#` +```rust +pub fn approve_transfer( + origin: OriginFor, + id: T::AssetIdParameter, + delegate: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Approves a delegate to transfer a specified amount of a specific asset on behalf of the origin account. + + +The `approve_transfer` function allows the origin account to delegate transfer rights for a portion of their assets to another account, known as the delegate. This is useful in scenarios where temporary or limited rights to transfer assets are needed without transferring full ownership. The approval specifies an exact amount of the asset that the delegate is allowed to transfer. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. This function is often used in decentralized applications to enable features like spending allowances and automated payments. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which transfer rights are being granted. +* `delegate: AccountIdLookupOf` -- the account being granted the rights to transfer the specified amount of the asset. +* `amount: T::Balance` -- the amount of the asset that the delegate is approved to transfer. + +#### `[.contract-item-name]#`cancel_approval`#` +```rust +pub fn cancel_approval( + origin: OriginFor, + id: T::AssetIdParameter, + delegate: AccountIdLookupOf, +) -> DispatchResult +``` +Cancels a previously granted approval for a delegate to transfer a specified asset on behalf of the origin account. + + +The `cancel_approval` function revokes the rights previously granted to a delegate to transfer a portion of the origin’s assets. This might be used when the need for the delegate’s rights has expired or if the original approval was made in error. The action ensures that the delegate can no longer transfer any amount of the specified asset from the origin’s account. This function is important for maintaining control and security over asset delegation and is a common feature in permission and rights management within asset systems. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which transfer rights are being revoked. +* `delegate: AccountIdLookupOf` -- the account from which the rights to transfer the asset are being revoked. + +#### `[.contract-item-name]#`force_cancel_approval`#` +```rust +pub fn force_cancel_approval( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + delegate: AccountIdLookupOf, +) -> DispatchResult +``` +Forcibly cancels a previously granted approval for a delegate to transfer a specific asset on behalf of the owner account. + + +The `force_cancel_approval` function is an administrative action used to revoke transfer rights from a delegate, typically in situations of emergency or misuse. Unlike `cancel_approval`, this function can be initiated by an admin or authority other than the asset’s owner, reflecting its more forceful nature. It’s critical for situations where the asset owner cannot revoke the approval themselves or in governance scenarios where broader control is necessary. As with all forceful actions, the origin must have the necessary administrative privileges. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. Origin must be either ForceOrigin or Signed origin with the signer being the Admin account of the asset `id`. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the approval is being forcibly canceled. +* `owner: AccountIdLookupOf` -- the account that owns the asset and had previously granted transfer rights. +* `delegate: AccountIdLookupOf` -- the account from which the rights to transfer the asset are being forcibly revoked. + +#### `[.contract-item-name]#`transfer_approved`#` +```rust +pub fn transfer_approved( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + destination: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Executes a transfer of a specified amount of an asset from an owner to a destination account, using a previously granted approval. + + +The `transfer_approved` function allows a delegate (the origin) to transfer assets within the limits of an approval granted by the asset’s owner. This enables scenarios where third parties are given limited rights to manage assets. The function ensures that the delegate cannot exceed the approved amount or perform transfers without a valid approval. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. It’s a critical function for flexible asset management in decentralized systems and applications. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being transferred. +* `owner: AccountIdLookupOf` -- the account that owns the asset and had previously granted transfer rights. +* `destination: AccountIdLookupOf` -- the account receiving the asset. +* `amount: T::Balance` -- the amount of the asset to be transferred, which must be within the approved amount. + +#### `[.contract-item-name]#`touch`#` +```rust +pub fn touch( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Creates an asset account for non-provider assets. + + +A deposit will be taken from the signer account. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being updated. + +#### `[.contract-item-name]#`refund`#` +```rust +pub fn refund( + origin: OriginFor, + id: T::AssetIdParameter, + allow_burn: bool, +) -> DispatchResult +``` +Refunds any reserved balance of a specific asset back to the asset’s owner or burns it based on the provided parameter. + + +The `refund` function is designed to handle situations where an asset’s reserved balance needs to be reconciled. This might occur in scenarios such as the completion of a contract, dissolution of a stake, or other instances where reserved funds are to be released. The `allow_burn` parameter determines if the refunded amount should be returned to the asset’s owner or burned, removing it from circulation. This function requires careful use and is typically controlled by the asset’s owner or an administrative authority. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the reserved balance is being refunded. +* `allow_burn: bool` -- indicates whether the reserved balance should be returned to the owner (false) or burned (true). + +#### `[.contract-item-name]#`set_min_balance`#` +```rust +pub fn set_min_balance( + origin: OriginFor, + id: T::AssetIdParameter, + min_balance: T::Balance, +) -> DispatchResult +``` +Sets a new minimum balance for a specific asset. + + +The `set_min_balance` function is used to define or update the minimum balance required to hold a particular asset. This is crucial for preventing dust accounts and ensuring economic viability of the asset system. Changing the minimum balance affects all holders of the asset, as accounts below the new minimum might be cleaned up or require additional funding. This operation requires authorization from the asset’s owner or another privileged account, ensuring that the change is made with proper oversight and consideration of its effects. Only works if there aren’t any accounts that are holding the asset or if the new value of `min_balance` is less than the old one. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the minimum balance is being set. +* `min_balance: T::Balance` -- the new minimum balance required to hold the asset. + +#### `[.contract-item-name]#`touch_other`#` +```rust +pub fn touch_other( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Create an asset account for `who`. + + +A deposit will be taken from the signer account. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being updated. +* `who: AccountIdLookupOf` -- the account for which the asset’s timestamp is being updated. + +#### `[.contract-item-name]#`refund_other`#` +```rust +pub fn refund_other( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Refunds the reserved balance of a specific asset back to another account’s owner. + + +The `refund_other` function is similar to the `refund` function but targets another account rather than the caller’s own. This allows administrators or authorized personnel to manage reserved balances across different accounts, potentially as part of a broader asset management or reconciliation process. This action might be necessary in scenarios such as contract completion, resolving disputes, or other instances where reserved funds need to be released or reallocated. The origin must have the necessary permissions to ensure that this function is used appropriately and by authorized entities. Useful if you are the depositor. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the reserved balance is being refunded. +* `who: AccountIdLookupOf` -- the account from which the reserved balance will be refunded. + +#### `[.contract-item-name]#`block`#` +```rust +pub fn block( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Blocks a specific account from unprivileged transacting a specific asset. + + +The `block` function is used to restrict a particular account from performing unprivileged transactions involving a specified asset. This might be used in scenarios such as suspected fraud, regulatory compliance, or other security or administrative reasons. Once an account is blocked, it cannot transfer, mint, or burn the asset until it is unblocked. The origin must have the necessary administrative rights or privileges to enforce such restrictions, ensuring that the action is authorized and appropriate for the given context. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which transactions are being blocked. +* `who: AccountIdLookupOf` -- the account that is being blocked from transacting the asset. diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/aura_ext.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/aura_ext.mdx new file mode 100644 index 00000000..ae37b7c4 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/aura_ext.mdx @@ -0,0 +1,43 @@ +--- +title: cumulus_pallet_aura_ext +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Purpose + +This pallet integrates parachain’s own block production mechanism (for example AuRa) into Cumulus parachain system. It allows: + +* to manage the unincluded blocks from the current slot +* to validate produced block against the relay chain + +## Configuration and Integration link:https://github.com/paritytech/polkadot-sdk/tree/release-polkadot-v1.10.0/cumulus/pallets/aura-ext[pass:[],role=heading-link] + +There is no special config for this integration and it has no dispatchables, but you need to integrate it with other `parachain-system` crate: + +### Integrate `BlockExecutor` + +When you invoke the `register_validate_block` macro, you should provide `cumulus_pallet_aura_ext::BlockExecutor` to it to allow `aura-ext` to validate the blocks produced by `aura` + +```rust +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} +``` + +### Integrate `ConsensusHook` + +Also you might want to manage the consensus externally and control the segment that is not yet included (its capacity, speed and etc.) `aura-ext` provides the `FixedVelocityConsensusHook` that allows to check if we are still in the limits for the slot. + +```rust +impl cumulus_pallet_parachain_system::Config for Runtime { + ... + type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, + >; +} +``` diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/balances.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/balances.mdx new file mode 100644 index 00000000..3129e13d --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/balances.mdx @@ -0,0 +1,230 @@ +--- +title: pallet_balances +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/balances/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +The Balances pallet provides functions for: + +* Getting and setting free balances. +* Retrieving total, reserved, and unreserved balances. +* Repatriating a reserved balance to a beneficiary account that exists. +* Transferring a balance between accounts (when not reserved). +* Slashing an account balance. +* Account creation and removal. +* Managing total issuance. +* Setting and managing [locks](/substrate-runtimes/2.0.2/glossary#lock). + +## Config + +* Pallet-specific configs + * `RuntimeHoldReason` -- The overarching [hold](/substrate-runtimes/2.0.2/glossary#hold) reason. + * `RuntimeFreezeReason` -- The overarching [freeze](/substrate-runtimes/2.0.2/glossary#freeze) reason. + * `Balance` -- The balance of an account + * `DustRemoval` -- Handler for the unbalanced reduction when removing a dust account. + * `ExistentialDeposit` -- The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO! + * `AccountStore` -- The means of storing the balances of an account. + * `ReserveIdentifier` -- The ID type for [reserves](/substrate-runtimes/2.0.2/glossary#reserve). The use of reserves is deprecated in favor of holds. See `https://github.com/paritytech/substrate/pull/12951/` + * `FreezeIdentifier` -- The ID type for freezes. + * `MaxLocks` -- The maximum number of locks that should exist on an account. + * `MaxReserves` -- The maximum number of named reserves that can exist on an account. The use of reserves is deprecated in favor of holds. See `https://github.com/paritytech/substrate/pull/12951/` + * `MaxFreezes` -- The maximum number of individual freeze locks that can exist on an account at any time. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- [Weight](/substrate-runtimes/2.0.2/glossary#weight) information for extrinsics in this pallet. + +## Events + +* `Endowed(account, free_balance)` -- An account was created with some free balance. +* `DustLost(account, amount)` -- An account was removed whose balance was non-zero but below ExistentialDeposit, resulting in an outright loss. +* `Transfer(from, to, amount)` -- Transfer succeeded. +* `BalanceSet(who, free)` -- A balance was set by root. +* `Reserved(who, amount)` -- Some balance was reserved (moved from free to reserved). +* `Unreserved(who, amount)` -- Some balance was unreserved (moved from reserved to free). +* `ReserveRepatriated(from, to, amount, destination_status)` -- Some balance was moved from the reserve of the first account to the second account. The final argument indicates the destination balance type. +* `Deposit(who, amount)` -- Some amount was deposited (e.g. for transaction fees). +* `Withdraw(who, amount)` -- Some amount was withdrawn from the account (e.g. for transaction fees). +* `Slashed(who, amount)` -- Some amount was removed from the account (e.g. for misbehavior). +* `Minted(who, amount)` -- Some amount was minted into an account. +* `Burned(who, amount)` -- Some amount was burned from an account. +* `Suspended(who, amount)` -- Some amount was suspended from an account (it can be restored later). +* `Restored(who, amount)` -- Some amount was restored into an account. +* `Upgraded(who)` -- An account was upgraded. +* `Issued(amount)` -- Total issuance was increased by `amount`, creating a credit to be balanced. +* `Rescinded(amount)` -- Total issuance was decreased by `amount`, creating a debt to be balanced. +* `Locked(who, amount)` -- Some balance was locked. +* `Unlocked(who, amount)` -- Some balance was unlocked. +* `Frozen(who, amount)` -- Some balance was frozen. +* `Thawed(who, amount)` -- Some balance was thawed. +* `TotalIssuanceForced(old, new)` -- Total issuance was forcefully changed. + +## Errors + +* `VestingBalance` -- Vesting balance too high to send value. +* `LiquidityRestrictions` -- Account liquidity restrictions prevent withdrawal. +* `InsufficientBalance` -- Balance too low to send value. +* `ExistentialDeposit` -- Value too low to create an account due to existential deposit. +* `Expendability` -- Transfer/payment would kill the account. +* `ExistingVestingSchedule` -- A vesting schedule already exists for this account. +* `DeadAccount` -- Beneficiary account must pre-exist. +* `TooManyReserves` -- Number of named reserves exceed `MaxReserves`. +* `TooManyHolds` -- Number of holds exceeds `MaxHolds`. +* `TooManyFreezes` -- Number of freezes exceeds `MaxFreezes`. +* `IssuanceDeactivated` -- The issuance cannot be modified since it is already deactivated. +* `DeltaZero` -- The delta cannot be zero. + +## Dispatchables + +#### `[.contract-item-name]#`transfer_allow_death`#` +```rust +pub fn transfer_allow_death( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, +) -> DispatchResult +``` +Transfers the `value` from `origin` to `dest`. + + +`allow_death` means, that if the account balance drops below the ExistentialDeposit limit, it might be reaped/deleted. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `dest: AccountIdLookupOf` -- recipient. +* `value: T::Balance` -- amount to transfer. + +#### `[.contract-item-name]#`transfer_keep_alive`#` +```rust +pub fn transfer_keep_alive( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, +) -> DispatchResult +``` +Transfers the `value` from `origin` to `dest`. + + +`keep_alive` means, with a check that the transfer will not kill the origin account. + + + +99% of the time you want `transfer_allow_death` instead. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `dest: AccountIdLookupOf` -- recipient. +* `value: T::Balance` -- amount to transfer. + +#### `[.contract-item-name]#`force_transfer`#` +```rust +pub fn force_transfer( + origin: OriginFor, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, +) -> DispatchResult +``` +Exactly as `transfer_allow_death`, except the origin must be root and the source account may be specified. + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, root) account. +* `source: AccountIdLookupOf` -- sender (forced by root). +* `dest: AccountIdLookupOf` -- recipient. +* `value: T::Balance` -- amount to transfer. + +#### `[.contract-item-name]#`transfer_all`#` +```rust +pub fn transfer_all( + origin: OriginFor, + dest: AccountIdLookupOf, + keep_alive: bool, +) -> DispatchResult +``` +Transfer the entire transferable balance from the caller account. + + +This function only attempts to transfer _transferable_ balances. This means that any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be transferred by this function. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `dest: AccountIdLookupOf` -- recipient. +* `keep_alive: bool` -- A boolean to determine if the `transfer_all` operation should send all of the transferable funds (including existential deposits) the account has, causing the sender account to be killed (false), or transfer everything transferable, except at least the existential deposit, which will guarantee to keep the sender account alive (true). + +#### `[.contract-item-name]#`force_unreserve`#` +```rust +pub fn force_unreserve( + origin: OriginFor, + who: AccountIdLookupOf, + amount: T::Balance, +) -> DispatchResult +``` +Unreserve some balance from a user by force. The caller (origin) must be root. + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `who: AccountIdLookupOf` -- the account for which the balance is to be unreserved. +* `amount: T::Balance` -- the amount of balance to be unreserved. + +#### `[.contract-item-name]#`upgrade_accounts`#` +```rust +pub fn upgrade_accounts( + origin: OriginFor, + who: Vec, +) -> DispatchResultWithPostInfo +``` +Upgrade the specified account(s). + +***Params:*** + +* `origin: OriginFor` -- caller, must be `Signed`. +* `who: Vec` -- the account(s) to be upgraded. + + +This will waive the transaction fee if at least all but 10% of the accounts need to be upgraded. + + +#### `[.contract-item-name]#`force_set_balance`#` +```rust +pub fn force_set_balance( + origin: OriginFor, + who: AccountIdLookupOf, + #[pallet::compact] new_free: T::Balance, +) -> DispatchResult +``` +Set the regular balance of a given account. The caller (origin) must be root. + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `who: AccountIdLookupOf` -- the account for which the balance will be set. +* `new_free: T::Balance` -- the amount of free balance that will be set to the given account. + +#### `[.contract-item-name]#`force_adjust_total_issuance`#` +```rust +pub fn force_adjust_total_issuance( + origin: OriginFor, + direction: AdjustmentDirection, + #[pallet::compact] delta: T::Balance, +) -> DispatchResult +``` +Adjust the total issuance in a saturating way. + +Can only be called by root and always needs a positive delta. + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `direction: AdjustmentDirection` -- the direction of issuance change (increase or decrease). +* `delta: T::Balance` -- the amount of free balance that will be set to the given account. diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/collator-selection.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/collator-selection.mdx new file mode 100644 index 00000000..11f351b3 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/collator-selection.mdx @@ -0,0 +1,227 @@ +--- +title: collator_selection +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/cumulus/pallets/collator-selection/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +This pallet is needed to manage the set of [collators](/substrate-runtimes/2.0.2/glossary#collator) for each session and to provision the next session with the actual list of collators. + +## Config + +* Pallet-specific configs + * `UpdateOrigin` — defines the list of origins that are able to modify the settings of collators (e.g. set and remove list of [invulnerables](/substrate-runtimes/2.0.2/glossary#invulnerable), desired [candidates](/substrate-runtimes/2.0.2/glossary#candidate), [candidacy bond](/substrate-runtimes/2.0.2/glossary#candidacy-bond)). This type should implement the trait `EnsureOrigin`. + * `PotId` — id of account that will hold a [Pot](/substrate-runtimes/2.0.2/glossary#pot). + * `MaxCandidates` — maximum number of candidates + * `MinEligibleCollators` — minimum number of collators to collect for the session + * `MaxInvulnerables` — maximum number of invulnerables + * `KickThreshold` — number of blocks that should pass since the last block produced by a candidate collator for it to be removed from a candidates list and not participate in collation for the next session. + * `ValidatorId` — type for validator id + * `ValidatorIdOf` — type that allows to convert AccountId to ValidatorId + * `ValidatorRegistration` — type that checks that AccountId has its validator keys registered. +* Common configs: + * `RuntimeEvent` + * `Currency` + * `WeightInfo` + +## Dispatchables + +#### `[.contract-item-name]#`set_invulnerables`#` +```rust +pub fn set_invulnerables(new: Vec) +``` +Sets a new list of invulnerable collators. The call must be signed and origin of the call must fulfill the EnsureOrigin check. + + +This call does not maintain the mutual exclusiveness of candidates and invulnerables lists. + + +***Params:*** + +* `new: Vec` — a list of AccountIds of new invulnerables + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `TooFewEligibleCollators` — empty list of invulnerables was submitted and the number of candidates is smaller than `Config::MinEligibleCollators` +* `TooManyInvulnerables` — submitted list length is longer than `Config::MaxInvulnerables` + +***Events:*** + +* `InvalidInvulnerableSkipped(account_id)` — submitted invulnerable does not have validator key or it is not registered +* `NewInvulnerables(invulnerables)` — new invulnerable list was set + +#### `[.contract-item-name]#`set_desired_candidates`#` +```rust +pub fn set_desired_candidates(max: u32) +``` +Set a new maximum possible number of candidates. If it is higher than `Config::MaxCandidates`, you should consider to rerun the benchmarks. The caller’s origin should fulfill the `Config::EnsureOrigin` check. + +***Params:*** + +* `max: u32` — new desired candidates number + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. + +***Events:*** + +* `NewDesiredCandidates(desired_candidates)` + +#### `[.contract-item-name]#`set_candidacy_bond`#` +```rust +pub fn set_candidacy_bond(max: u32) +``` +Set the amount for the deposit to be a candidate for collator. + +***Params:*** + +* `bond: u32` — new amount for candidate deposit + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. + +***Events:*** + +* `NewCandidacyBond(bond_amount)` + +#### `[.contract-item-name]#`register_as_candidate`#` +```rust +pub fn register_as_candidate() +``` +Register the caller as a collator candidate. Caller should be signed, have registered session keys and have amount needed for candidacy bond deposit. If successful, candidate will participate in collation process starting from the next session. + +***Errors:*** + +* `BadOrigin` — call is not signed +* `TooManyCandidates` — number of collators is already at its maximum (specified in `desired_candidates` getter) +* `AlreadyInvulnerable` — caller is already in invulnerable collators list, it does not need to be a candidate to become a collator +* `NoAssociatedValidatorId` — caller does not have a session key. +* `ValidatorNotRegistered` — caller session key is not registered +* `AlreadyCandidate` — caller is already in candidates list +* `InsufficientBalance` — candidate does not have enough funds for deposit for candidacy bond +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `CandidateAdded(account_id, deposit)` + +#### `[.contract-item-name]#`leave_intent`#` +```rust +pub fn leave_intent() +``` +Unregister the caller from being a collator candidate. If successful, deposit will be returned and during the next session change collator will no longer participate in collation process. This call must be signed. + +***Errors:*** + +* `BadOrigin` — call is not signed +* `TooFewEligibleCollators` — the number of collators for the next session will be less than `Config::MinEligibleCollators` in case of unregistration so the process is stopped. +* `NotCandidate` — caller is not on candidate list, nothing to unregister + +***Events:*** + +* `CandidateRemoved(account_id)` + +#### `[.contract-item-name]#`add_invulnerable`#` +```rust +pub fn add_invulnerable(who: T::AccountId) +``` +Add a new invulnerable. Call must be signed and caller pass `Config::EnsureOrigin` check. If a new invulnerable was previously a candidate, it will be removed from them. + +**Params:** + +* `who: T::AccountId` — an account to add to invulnerables list + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `NoAssociatedValidatorId` — new invulnerable does not have a session key. +* `ValidatorNotRegistered` — new invulnerable session key is not registered +* `AlreadyInvulnerable` — caller is already in invulnerable collators list + +***Events:*** + +* `InvulnerableAdded(account_id)` + +#### `[.contract-item-name]#`remove_invulnerable`#` +```rust +pub fn remove_invulnerable(who: T::AccountId) +``` +Remove an invulnerable from the list. Call must be signed and caller pass `Config::EnsureOrigin` check. + +**Params:** + +* `who: T::AccountId` — an account to add to invulnerables list + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `TooFewEligibleCollators` — the number of invulnerable will become less than `Config::MinEligibleCollators` after the removal. +* `NotInvulnerable` — the `who` is not an invulnerable + +***Events:*** + +* `InvulnerableRemoved(account_id)` + +#### `[.contract-item-name]#`update_bond`#` +```rust +pub fn update_bond(new_deposit: BalanceOf) +``` +Update the candidacy bond of origin to the new value. + +**Params:** + +* `new_deposit: BalanceOf` — new value for the candidacy bond + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `DepositTooLow` - new deposit is smaller than required candidacy bond. +* `NotCandidate` - caller’s origin is not a candidate +* `IdenticalDeposit` - deposit have not changed +* `InsufficientBalance` — candidate does not have enough funds for deposit for candidacy bond +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. +* `InvalidUnreserve` - after the unreserve the number of candidates becomes less than desired. +* `InsertToCandidateListFailed` - candidate list is at maximum capacity. Should not happen in usual scenarios. Chain is misconfigured. + +***Events:*** + +* `CandidateBondUpdated(account_id, deposit)` + +#### `[.contract-item-name]#`take_candidate_slot`#` +```rust +pub fn take_candidate_slot( + deposit: BalanceOf, + target: T::AccountId, +) +``` +Try to replace the target’s candidacy by making a bigger candidacy bond. + +**Params:** + +* `deposit: BalanceOf` — value for the candidacy bond +* `target: T::AccountId` - target candidate to replace + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `AlreadyInvulnerable` — caller is already in invulnerable collators list. +* `InsufficientBond` - the deposit is less the candidacy bond or target’s deposit. +* `NoAssociatedValidatorId` — caller does not have a session key. +* `ValidatorNotRegistered` — caller session key is not registered. +* `AlreadyCandidate` — caller is already in candidates list. +* `TargetIsNotCandidate` - target is not in candidate list. +* `InsufficientBalance` — candidate does not have enough funds for deposit for candidacy bond +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `CandidateReplaced(old, new, deposit)` diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/message-queue.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/message-queue.mdx new file mode 100644 index 00000000..32e9d7e5 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/message-queue.mdx @@ -0,0 +1,155 @@ +--- +title: pallet_message_queue +--- + +Branch/Release: `release-polkadot-v1.10.0` + +Source Code [pass:[](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/message-queue/src/lib.rs),role=heading-link] + +## Purpose + +Flexible FRAME pallet for implementing message queues. This pallet can also initiate message processing using the `MessageProcessor` (see `Config`). + +## Config +* Pallet-specific configs: + * `MessageProcessor` -- Processor for messages + * `Size` -- Page/heap size type. + * `QueueChangeHandler` -- Code to be called when a message queue changes - either with items introduced or removed. + * `QueuePausedQuery` -- Queried by the pallet to check whether a queue can be serviced. + * `HeapSize` -- The size of the page; this also serves as the maximum message size which can be sent. + * `MaxStale` -- The maximum number of stale pages (i.e. of overweight messages) allowed before culling can happen. Once there are more stale pages than this, then historical pages may be dropped, even if they contain unprocessed overweight messages. + * `ServiceWeight` -- The amount of weight (if any) which should be provided to the message queue for servicing enqueued items `on_initialize`. This may be legitimately `None` in the case that you will call `ServiceQueues::service_queues` manually or set [`Self::IdleMaxServiceWeight`] to have it run in `on_idle`. + * `IdleMaxServiceWeight` -- The maximum amount of weight (if any) to be used from remaining weight `on_idle` which should be provided to the message queue for servicing enqueued items `on_idle`. Useful for parachains to process messages at the same block they are received. If `None`, it will not call `ServiceQueues::service_queues` in `on_idle`. +* Common configs: + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- Weight information for extrinsics in this pallet. + +## Dispatchables + +#### `[.contract-item-name]#`execute_overweight`#` +```rust +pub fn execute_overweight( + origin: OriginFor, + message_origin: MessageOriginOf, + page: PageIndex, + index: T::Size, + weight_limit: Weight, +) -> DispatchResultWithPostInfo +``` +Execute an overweight message. + + +Temporary processing errors will be propagated whereas permanent errors are treated +as success condition. + + + +The `weight_limit` passed to this function does not affect the `weight_limit` set in other parts of the pallet. + + +***Params:*** + +* `param1: Type1` -- description of the parameter +* `origin: OriginFor` -- Must be `Signed`. +* `message_origin: MessageOriginOf` -- indicates where the message to be executed arrived from (used for finding the respective queue that this message belongs to). +* `page: PageIndex` -- The page in the queue in which the message to be executed is sitting. +* `index: T::Size` -- The index into the queue of the message to be executed. +* `weight_limit: Weight` -- The maximum amount of weight allowed to be consumed in the execution +of the message. This weight limit does not affect other parts of the pallet, and it is only used for this call of `execute_overweight`. + +***Errors:*** + +* `QueuePaused` -- if the queue that overweight message to be executed belongs to is paused. +* `NoPage` -- if the page that overweight message to be executed belongs to does not exist. +* `NoMessage` -- if the overweight message could not be found. +* `Queued` -- if the overweight message is already scheduled for future execution. +For a message to be labeled as overweight, the pallet must have previously attempted execution and +encountered failure due to insufficient weight for processing. Once marked as overweight, the message +is excluded from the queue for future execution. +* `AlreadyProcessed` -- if the overweight message is already processed. +* `InsufficientWeight` -- if the `weight_limit` is not enough to execute the overweight message. +* `TemporarilyUnprocessable` -- if the message processor `Yield`s execution of this message. This means processing should be reattempted later. + +***Events:*** + +* `ProcessingFailed(id, origin, error)` +* `Processed(id, origin, weight_used, success)` + +#### `[.contract-item-name]#`reap_page`#` +```rust +pub fn reap_page( + origin: OriginFor, + message_origin: MessageOriginOf, + page_index: PageIndex, +) -> DispatchResult +``` + +Remove a page which has no more messages remaining to be processed or is stale. + +***Params:*** + +* `param1: Type1` -- description of the parameter +* `origin: OriginFor` -- Must be `Signed`. +* `message_origin: MessageOriginOf` -- indicates where the messages arrived from (used for finding the respective queue that this page belongs to). +* `page_index: PageIndex` -- The page to be reaped + +***Errors:*** + +* `NotReapable` -- if the page is not stale yet. +* `NoPage` -- if the page does not exist. + +***Events:*** + +* `PageReaped(origin, index)` -- the queue (origin), and the index of the page + +## Important Mentions and FAQ's + + +The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always stay within +the required limit. This implies that the message processor hook can calculate the weight of a message without executing it. + + +#### How does this pallet work under the hood? + +* This pallet utilizes queues to store, enqueue, dequeue, and process messages. +* Queues are stored in `BookStateFor` storage, with their origin serving as the key (so, we can identify queues by their origins). +* Each message has an origin (message_origin), that defines into which queue the message will be stored. +* Messages are stored by being appended to the last `Page` of the Queue’s Book. A Queue is a book along with the MessageOrigin for that book. +* Each book keeps track of its pages, and the state (begin, end, count, etc.) +* Each page also keeps track of its messages, and the state (remaining, first, last, etc.) +* `ReadyRing` contains all ready queues as a double-linked list. A Queue is ready if it contains at least one Message which can be processed. +* `ServiceHead` is a pointer to the `ReadyRing`, pointing at the next `Queue` to be serviced. Service means: attempting to process the messages. + +**Execution:** + +* `service_queues` → returns the weight that is consumed by this function + * we will process a queue, till either: + * there is no more message left + * if there is no more message left in the queue, we won’t stop, service_head will proceed with the next queue + * or weight is insufficient + * if weight is insufficient for the next message in the queue, service_head will try to switch to next queue, and try to process message from that queue. This will go on, until it visits every queue, and no message can be processed. Only then, it will stop. + * each call to `service_queues`, we will bump the header, and start processing the next queue instead of the previous one to prevent starvation + * Example: + * service head is on queue 2 + * we called `service_queues`, which bumped the service head to queue 3 + * we processed messages from queue 3, + * but weight was insufficient for the next message in queue 3, + * so we switched to queue 4, (we don’t bump the service head for that) + * weight was insufficient for queue 4 and other queues as well, and we made a round trip across queues, till we reach queue 3, and we stopped. + * `service_queues` call finished + * service head is on queue 3 + * we called `service_queue` again, which bumped the service head to queue 4 (although there are still messages left in queue 3) + * we continue processing from queue 4. + * but, to preserve priority, if we made a switch to a new queue due to weight, we don’t bump the service head. So, the next call, will be starting on the queue where we left off. + * Example: + * service head is on queue 2 + * we called `service_queues`, which bumped the service head to queue 3 + * we processed messages from queue 3, + * but weight was insufficient for the next message in queue 3, + * so we switched to queue 4, (we don’t bump the service head for that) + * we processed a message from queue 4 + * weight was insufficient for queue 4 and other queues as well, and we made a round trip across queues, till we reach queue 3, and we stopped. + * `service_queues` call finished + * service head is on queue 3 (there are still messages in queue 3) + * we called `service_queue` again, which bumped the service head to queue 4 + * we continue processing from queue 4, although we were processing queue 4 in the last call diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/multisig.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/multisig.mdx new file mode 100644 index 00000000..e96617a1 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/multisig.mdx @@ -0,0 +1,232 @@ +--- +title: pallet_multisig +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code: link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/multisig/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +This module enables multi-signature operations in your runtime. It allows multiple signed origins (accounts) to coordinate and dispatch a call. For the call to execute, the threshold number of accounts from the set (signatories) must approve it. + +## Config + +* Pallet-specific configs + * `DepositBase` -- The base amount of currency needed to [reserve](/substrate-runtimes/2.0.2/glossary#reserve) for creating a multisig execution or to store a dispatch call for later. Recall: The deposit to be made by the account that creates the multisig is: `threshold * DepositFactor + DepositBase` + * `DepositFactor` -- The amount of currency needed per unit threshold when creating a multisig execution. Recall: The deposit to be made by the account that creates the multisig is: `threshold * DepositFactor + DepositBase` + * `MaxSignatories` -- The maximum amount of signatories allowed in the multisig. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `RuntimeCall` -- The overarching call type. + * `Currency` -- The currency mechanism. + * `WeightInfo` -- [Weight](/substrate-runtimes/2.0.2/glossary#weight) information for extrinsics in this pallet. + +## Dispatchables + +#### `[.contract-item-name]#`approve_as_multi`#` +```rust +pub fn approve_as_multi( + threshold: u16, + other_signatories: Vec, + maybe_timepoint: Option>>, + call_hash: [u8; 32], + max_weight: Weight +) -> DispatchResultWithPostInfo +``` +Register approval for a dispatch to be made from a deterministic composite account. + +Since the first register (from origin) counts as a vote as well, only `threshold - 1` additional votes are necessary from `other_signatories`. + +Payment: `DepositBase` will be reserved if this is the first approval, plus `threshold` times `DepositFactor`. It is returned once this dispatch happens or is cancelled. + +The dispatch origin for this call must be **Signed**. + +The result is equivalent to the dispatched result if the threshold is exactly 1. Otherwise on success, the result is Ok and the result from the interior call, if it was executed, may be found in the deposited MultisigExecuted event. + + +If this is the final approval, you will want to use `as_multi` instead. `approve_as_multi` won’t trigger the dispatch, even if there are enough approvals. + + +The reason is: `as_multi` needs `call` parameter, whereas `approve_as_mutli` needs `call_hash`. `call_hash` is enough to find the multisig operation entry in the storage, and increment the vote count. We don’t need the `call` itself to increment the vote count. Whereas, if `call` is supplied, and if we have enough approvals, the logic for execution will be triggered. This is a design choice. + +***Params:*** + +* `threshold: u16` -- The total number of approvals for this dispatch before it is executed. Cannot be 1. If you want the threshold to be 1, use as_multi_threshold_1 instead. +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `maybe_timepoint: Option>>` -- Refers to the timepoint of the creation/registration of this call to the multisig storage. If this is the first approval, then this must be None (business logic derives it automatically). If it is not the first approval, then it must be Some, with the timepoint (block number and transaction index) of the first approval transaction. +* `call_hash: [u8; 32]` -- The hash of the call to be executed. +* `max_weight: Weight` -- Maximum weight limit for the call’s execution. + +***Errors:*** + +* `MaxWeightTooLow` -- when the call requires more weight to be executed, the call won’t be executed and an error will be returned. +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `NoTimepoint` -- when this is not the first call, and no timepoint is given +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `UnexpectedTimepoint` -- when this is the first call, and a timepoint is given +* `AlreadyApproved` -- when a signatory tries to approve more than once + +***Events:*** + +* `MultisigApproved(approving, timepoint, multisig, call_hash)` -- when multisig call is approved. This also gives information on who was the last approver (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). +* `NewMultisig (approving, multisig, call_hash`) -- when a multisig call is created. This also gives information on who was the creator (also the first approver) (`approving`), id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +#### `[.contract-item-name]#`as_multi`#` +```rust +pub fn as_multi( + threshold: u16, + other_signatories: Vec, + maybe_timepoint: Option>>, + call: Box<::RuntimeCall>, + max_weight: Weight +) -> DispatchResultWithPostInfo +``` + + +Unless this is the final approval, you will generally want to use `approve_as_multi` instead, since it only requires a hash of the call. + + +`call_hash` is enough to find the multisig operation entry in the storage, and increment the vote count. We don’t need the `call` itself to increment the vote count. + +Whereas, if `call` is supplied, and if we have enough approvals, the logic for execution will be triggered. + +`as_multi` is nearly identical to `approve_as_multi`, the only difference being `call` vs `call_hash`. + +Register approval for a dispatch to be made from a deterministic composite account if approved by a total of `threshold - 1` of `other_signatories`. + +If there are enough, then dispatch the call. + +Payment: `DepositBase` will be reserved if this is the first approval, plus `threshold` times `DepositFactor`. It is returned once this dispatch happens or is cancelled. + +The dispatch origin for this call must be **Signed**. + + +When as_multi is called, if it succeeds (dispatches the call), the multisig operation will be removed from the storage. Meaning, another person cannot trigger the same multisig call. They need to create the same one from scratch again. + + +***Params:*** + +* `threshold: u16` -- The total number of approvals for this dispatch before it is executed. Cannot be 1. If you want the threshold to be 1, use as_multi_threshold_1 instead. +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `maybe_timepoint: Option>>` -- Refers to the timepoint of the creation/registration of this call to the multisig storage. If this is the first approval, then this must be None (business logic derives it automatically). If it is not the first approval, then it must be Some, with the timepoint (block number and transaction index) of the first approval transaction. +* `call: Box<::RuntimeCall>` -- The call to be executed. +* `max_weight: Weight` -- Maximum weight limit for the call’s execution. + +***Errors:*** + +* `MaxWeightTooLow` -- when the call requires more weight to be executed, the call won’t be executed and an error will be returned. +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `NoTimepoint` -- when this is not the first call, and no timepoint is given +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `UnexpectedTimepoint` -- when this is the first call, and a timepoint is given +* `AlreadyApproved` -- when a signatory tries to approve more than once + +***Events:*** + +* `MultisigExecuted(approving, timepoint, multisig, call_hash, result)` -- when multisig call is executed. This also gives information on who was the last approver (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`), and the `result`. +* `MultisigApproved(approving, timepoint, multisig, call_hash)` -- when multisig call is approved. This also gives information on who was the last approver (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). +* `NewMultisig (approving, multisig, call_hash`) -- when a multisig call is created. This also gives information on who was the creator (also the first approver) (`approving`), id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +#### `[.contract-item-name]#`cancel_as_multi`#` +```rust +pub fn cancel_as_multi( + threshold: u16, + other_signatories: Vec, + timepoint: Timepoint>, + call_hash: [u8; 32] +) -> DispatchResult +``` +Cancel a pre-existing, ongoing multisig transaction. Any deposit reserved previously for this operation will be unreserved on success. + + +Only the owner of the multisig operation can cancel it (not even other signatories). + + +Cancel operation does not require multi-signature. The owner calling this function is enough on its own to cancel this. + + +Multisig operations are stored in the storage with double keys, hence other_signatories and threshold are necessary for the identification of the multisig operation. + +***Params:*** + +* `threshold: u16` -- The total number of approvals for this dispatch before it is executed. Cannot be 1. If you want the threshold to be 1, use as_multi_threshold_1 instead. +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `timepoint: Option>>` -- Refers to the timepoint of the creation/registration of this call to the multisig storage. If this is the first approval, then this must be None (business logic derives it automatically). If it is not the first approval, then it must be Some, with the timepoint (block number and transaction index) of the first approval transaction. +* `call_hash: [u8; 32]` -- The hash of the call to be executed. + +***Errors:*** + +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `NotFound` -- when the multisig call is not found in the storage +* `NotOwner` -- when someone who is not the owner tried to cancel + +***Events:*** + +* `MultisigCancelled(cancelling, timepoint, multisig, call_hash)` -- when multisig call is cancelled. This also gives information on who cancelled (`cancelling`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +#### `[.contract-item-name]#`as_multi_threshold_1`#` +```rust +pub fn as_multi_threshold_1( + other_signatories: Vec, + call: Box<::RuntimeCall> +) -> DispatchResultWithPostInfo +``` +Immediately dispatch a multi-signature call using a single approval from the caller. + +The dispatch origin for this call must be **Signed**. + +A real use case scenario could be for example a business that has a bank account and says "any one of the 3 founders can authorize payments from this account". + +***Params:*** + +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `call: Box<::RuntimeCall>` -- The call to be executed. + +***Errors:*** + +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `NotFound` -- when the multisig call is not found in the storage +* `NotOwner` -- when someone who is not the owner tried to cancel + +***Events:*** + +* `MultisigCancelled(cancelling, timepoint, multisig, call_hash)` -- when multisig call is cancelled. This also gives information on who cancelled (`cancelling`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +## Important Mentions and FAQ's + +**Big Picture Examples** + +* funding the multisig account (same for `1-out-of-n multisig accounts`, and `m-out-of-n multisig accounts`) + * find the public key of the shared account + * use polkadot JS to create a multisig account + * or, use `multi_account_id` in the source code + * fund the account by simply sending some money to the derived public key + * 👉 check this documentation if you have questions about accounts and their creation [multisig_accounts](/substrate-runtimes/2.0.2/misc/multisig-accounts) +* `m-out-of-n multisig account` example: + * Alice, Bob, Charlie, and Dylan want to create a shared account, and the threshold for this shared account should be 3 (at least 3 people should approve the transactions for this account). + * One of the signatories should create the multisig operation by calling `as_multi`, and should provide the necessary arguments + * Others can approve this call using `approve_as_multi`, however, `approve_as_multi` will not dispatch the call. This will only increase the approval amount. + * If the approver wants to dispatch the call as well, they should use `as_multi` instead. + * **Niche Details:** + * If a signatory tries to call `approve_as_multi` after the threshold is surpassed, they will get an error: `AlreadyApproved`, because this action is meaningless. + * A signatory can call `as_multi` to dispatch the call, even if they approved the same multisig before. +* `1-out-of-n multisig account` example: + * Alice, Bob, and Charlie want to create a shared account, and the threshold for this shared account should be 1 (any person can spend from this account, without any approval). + * any of them can call `as_multi_threshold_1`, and spend the money without requiring approval from others + * **Niche Details:** + * `as_multi_threshold_1` does not store multisig operations in storage. Because there is no need to do so. + * Q: if we are not storing it, how can other signatories use this shared account in the future? + * A: the account’s balance is stored in blockchain. The account’s public key is derived from the public keys of the signatories, combined with the threshold. So, the caller has permission to spend the balance that belongs to the derived public key. + * we are not storing `1-out-of-n multisig operations`, but we are storing `m-out-of-n multisig operations`, since we have to keep track of the approvals. + * It does not make sense to cancel `1-out-of-n multisig operations`, because `as_multi_threshold_1` immediately dispatches the call, there is no state in which canceling is a viable option for `1-out-of-n multisig operations`. diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/parachain-system.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/parachain-system.mdx new file mode 100644 index 00000000..45860f3a --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/parachain-system.mdx @@ -0,0 +1,117 @@ +--- +title: parachain_system +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/cumulus/pallets/parachain-system/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +This pallet is a core element of each parachain. It will: + +* Aggregate information about built blocks +* Process binary code upgrades +* Process incoming messages from both relay chain and other parachains (if a channel is established between them) +* Send outgoing messages to relay chain and other parachains +* Build collation info when requested by [collator](/substrate-runtimes/2.0.2/glossary#collator) + +## Config + +* Pallet-specific configs: + * `OnSystemEvent` — a handler that will be called when new [validation data](/substrate-runtimes/2.0.2/glossary#validation-data) will be set (once each block). New validation data will also be passed to it. Look to `trait OnSystemEvent` for more details. + * `SelfParaId` — getter for a parachain id of this chain + * `OutboundXcmpMessageSource` — source of outgoing XCMP messages. It is queried in `finalize_block` and later included into collation information + * `DmpQueue` — a handler for the incoming **downward** messages from relay chain + * `ReservedDmpWeight` — [weight](/substrate-runtimes/2.0.2/glossary#weight) reserved for DMP message processing. This config seems to be is not used as the function that processes these messages (`enqueue_inbound_downward_messages`) returns used weight. + * `XcmpMessageHandler` — a handler for the incoming _horizontal_ messages from other parachains + * `ReservedXcmpWeight` — default weight limit for the for the XCMP message processing. May be overriden by storage `ReservedXcmpWeightOverride` . If incoming messages in block will exceed the weight limit, they won’t be processed. + * `CheckAssociatedRelayNumber` — type that implements `trait CheckAssociatedRelayNumber` . Currently there are three implementations: no check (`AnyRelayNumber`), strict increase (`RelayNumberStrictlyIncreases`), monotonic increase (`RelayNumberMonotonicallyIncreases`). It is needed to maintain some order between blocks in relay chain and parachain. + * `ConsensusHook` — this is a feature-enabled config ( for the management of the [unincluded segment](/substrate-runtimes/2.0.2/glossary#unincluded-segment). Requires the implementation of `trait ConsensusHook`. There are several implementations of it, in `parachain-system` crate (`FixedCapacityUnincludedSegment`) and in `aura-ext` crate (`FixedVelocityConsensusHook`). It is needed to maintain the logic of segment length handling. +* Common parameters for all pallets: + * `RuntimeEvent` + * `WeightInfo` + +## Dispatchables + +#### `[.contract-item-name]#`set_validation_data`#` +```rust +pub fn set_validation_data( + data: ParachainInherentData, +) +``` +This call is an inherent, you can’t call this from another dispatchable or from client side. This call sets up validation data for collation, processes code upgrades and updates unincluded segments. + +#### `[.contract-item-name]#`sudo_send_upward_message`#` +```rust +pub fn sudo_send_upward_message( + message: UpwardMessage, +) +``` +Send a message to relay as a sudo. + +***Params:*** + +* `message` — a vec of bytes that represents a message that you send to the relay + +***Errors:*** + +* `BadOrigin` — call was made not from a sudo + +#### `[.contract-item-name]#`authorize_upgrade`#` +```rust +pub fn authorize_upgrade( + code_hash: T::Hash, + check_version: bool, +) +``` + +Authorize the upgrade. This call will put the hash and flag to the storage `AuthorizedUpgrade`. This call must be made as a sudo. + +***Params:*** + +* `code_hash` — hash of the authorized runtime binary +* `check_version` — flag that indicates that the code should be checked for the possibility to upgrade. It will happen during the upgrade process itself. + +***Errors:*** + +* `BadOrigin` — call was made not from a sudo + +***Events:*** + +* `UpgradeAuthorized(code_hash)` + +#### `[.contract-item-name]#`enact_authorized_upgrade`#` +```rust +pub fn enact_authorized_upgrade( + code: Vec, +) +``` + +Validate and perform the authorized upgrade. + +***Params:*** + +* `code` — runtime binary for the upgrade + +***Errors:*** + +* `NothingAuthorized` — there is no authorized upgrade, call `authorize_upgrade` in advance +* `Unauthorized` — there is another upgrade authorized + +## Important Mentions and FAQ's + +### Pallet's workflow + +* Block Initialization + * Remove already processed [validation code](/substrate-runtimes/2.0.2/glossary#validation-code) + * Update `UnincludedSegment` with latest parent hash + * Cleans up `ValidationData` and other functions. + * Calculate weights for everything that was done in `on_finalize` hook +* Inherents — `set_validation_data` call + * Clean the included segments from `UnincludedSegment` and update the `AggregatedUnincludedSegment` + * Update `ValidationData`, `RelayStateProof` and other configs from relay. + * Process the `ValidationCode` upgrade +* Block Finalization + * Enqueue all received messages from relay chain and other parachains + * Update `UnincludedSegment` and `AggregatedUnincludedSegment` with the latest block data diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/proxy.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/proxy.mdx new file mode 100644 index 00000000..93c64195 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/proxy.mdx @@ -0,0 +1,324 @@ +--- +title: pallet_proxy +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/proxy/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +This pallet enables delegation of rights to execute certain call types from one origin to another. + +## Config + +* Pallet-specific configs: + * `ProxyType` -- a type that describes different variants of [proxy](/substrate-runtimes/2.0.2/glossary#proxy). It must implement `Default` trait and `InstanceFilter` trait. + * `ProxyDepositBase` -- a base amount of currency that defines a deposit for proxy creation. + * `ProxyDepositFactor` -- an amount of currency that will be frozen along with the `ProxyDepositBase` for each additional proxy. + * `MaxProxies` -- maximum number of proxies that single account can create. + * `MaxPending` -- maximum number of [announcements](/substrate-runtimes/2.0.2/glossary#announcement) that can be made per account. + * `CallHasher` -- a type implementing a `Hash` trait. Will be used to hash the executed call. + * `AnnouncementDepositBase` -- a base amount of currency that defines a deposit for announcement creation. + * `AnnouncementDepositFactor` -- an amount of currency that will be frozen along with the `AnnouncementDepositBase` for each additional announcement. +* Common configs: + * `RuntimeEvent` + * `RuntimeCall` + * `Currency` + +## Dispatchables + +#### `[.contract-item-name]#`add_proxy`#` +```rust +pub fn add_proxy( + delegate: <::Lookup as StaticLookup>::Source, + proxy_type: T::ProxyType, + delay: BlockNumberFor +) +``` +Create a new `Proxy` that allows `delegate` to execute calls that fulfill `proxy_type` check on your origin’s behalf. + +The origin must be signed for this call. + +This call will take (or modify) a deposit based on number of proxies created by [delegator](/substrate-runtimes/2.0.2/glossary#delegator) and calculated by this formula: `ProxyDepositBase + ProxyDepositFactor * ` + +There may not be more proxies than `MaxProxies` + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — account that will become a proxy for the origin +* `proxy_type: T::ProxyType` — type of calls that will be allowed for the delegate +* `delay: BlockNumberFor` — number of blocks that needs to happen between announcement and call for this proxy + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — delegate not found +* `NoSelfProxy` — delegate and call origin is the same account +* `Duplicate` — proxy already exists +* `TooMany` — too many proxies created for this delegate with this type and the same [delay](/substrate-runtimes/2.0.2/glossary#delay) +* `InsufficientBalance` — delegator does not have enough funds for deposit for proxy creation +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `ProxyAdded(delegator, delegatee, proxy_type, delay)` + +#### `[.contract-item-name]#`announce`#` +```rust +pub fn announce( + real: AccountIdLookupOf, + call_hash: CallHashOf, +) +``` +Announce a call that will be executed using a proxy. As a result announcement will be created. You must create an announcement if the proxy you are using specified a delay greater than zero. In that case you will be able to execute a call after the number of blocks specified by delay. + +The origin must be signed for this call. + +This call will take (or modify) a deposit calculated by this formula: `AnnouncementDepositBase + AnnouncementDepositFactor * ` + +There may not be more announcements than `MaxPending` + +***Params:*** + +* `real: AccountIdLookupOf` — the account on which behalf this call will be made +* `call_hash: CallHashOf` — hash of the call that is going to be made + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `real` account not found +* `NotProxy` — there is no proxy between the caller and real +* `TooMany` — there is more announcements for this sender than specified in `MaxPending` +* `InsufficientBalance` — caller does not have enough funds for deposit for announcement creation +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `Announced(real, proxy, call_hash)` + +#### `[.contract-item-name]#`proxy`#` +```rust +pub fn proxy( + real: AccountIdLookupOf, + force_proxy_type: Option, + call: Box<::RuntimeCall>, +) +``` + +Dispatch a `call` on behalf of `real` account using a proxy that was created in advance. Proxy must be created for the call sender to execute the call. + +The origin must be signed for this call. + +If the proxy requires an announcement before the call, this dispatchable will fail. + +***Params:*** + +* `real: AccountIdLookupOf` — the account on which behalf this call will be made +* `force_proxy_type: Option` — specific [proxy type](/substrate-runtimes/2.0.2/glossary#proxy-type) to get proxy for. If not specified, first one found in the storage will be used. +* `call: Box<::RuntimeCall>` — a call to execute + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `real` account not found +* `NotProxy` — there is no proxy between the caller and real +* `Unannounced` — there was a delay specified but not fulfilled + +***Events:*** + +* `ProxyExecuted(result)` + +#### `[.contract-item-name]#`proxy_announced`#` +```rust +pub fn proxy_announced( + delegate: <::Lookup as StaticLookup>::Source, + real: <::Lookup as StaticLookup>::Source, + force_proxy_type: Option, + call: Box<::RuntimeCall> +) +``` + +Execute previously announced call using a proxy and remove the announcement. Proxy must be created for the call sender to execute the call. + +The origin must be signed for this call. + +This call will fail if delay after announcement have not passed or call was not announced. + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — the account proxy was given to and who announced the call +* `real: <::Lookup as StaticLookup>::Source` — delegator of the proxy, on whose behalf call will be executed +* `force_proxy_type: Option` — specific proxy type to get proxy for. If not specified, first one found in the storage will be used. +* `call: Box<::RuntimeCall>` — a call to execute + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `real` or `delegate` account not found +* `NotProxy` — there is no proxy between the `delegate` and `real` +* `Unannounced` — there was a delay specified but not fulfilled or call was not announced + +***Events:*** + +* `ProxyExecuted(result)` + +#### `[.contract-item-name]#`reject_announcement`#` +```rust +pub fn reject_announcement( + delegate: <::Lookup as StaticLookup>::Source, + call_hash: <::CallHasher as Hash>::Output +) +``` + +Remove the given announcement. Deposit is returned in case of success. + +May be called from delegator of the proxy to remove announcement made by [delegatee](/substrate-runtimes/2.0.2/glossary#delegatee). + +The origin must be signed for this call. + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — account that created an announcement +* `call_hash: <::CallHasher as Hash>::Output` — hash that was created for the announcement + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `delegate` account not found +* `NotFound` — proxy not found for this delegator and delegatee + +#### `[.contract-item-name]#`remove_announcement`#` +```rust +pub fn remove_announcement( + real: <::Lookup as StaticLookup>::Source, + call_hash: <::CallHasher as Hash>::Output +) +``` + +Remove the given announcement. Deposit is returned in case of success. + +May be called from delegatee of the proxy to remove announcement made by them. + +The origin must be signed for this call. + +***Params:*** + +* `real: <::Lookup as StaticLookup>::Source` — delegator of the proxy for the announcement to remove +* `call_hash: <::CallHasher as Hash>::Output` — hash of announced call + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `delegate` account not found +* `NotFound` — proxy not found for this delegator and delegatee + +#### `[.contract-item-name]#`remove_proxies`#` +```rust +pub fn remove_proxies() +``` + +Removes all proxies _issued to_ the caller. The origin must be signed for this call. + +***Errors:*** + +* `BadOrigin` — request not signed + +#### `[.contract-item-name]#`remove_proxy`#` +```rust +pub fn remove_proxy( + delegate: <::Lookup as StaticLookup>::Source, + proxy_type: T::ProxyType, + delay: BlockNumberFor +) +``` + +Remove the proxy issued by the caller. Deposit is returned to the delegator. + +Origin must be signed for this call. + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — account to whom this proxy was issued +* `proxy_type: T::ProxyType` — type of the issued proxy +* `delay: BlockNumberFor` — delay of the issued proxy + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `delegate` account not found +* `NotFound` — no such proxy exists + +***Events:*** + +* `ProxyRemoved(delegator, delegatee, proxy_type, delay)` + +#### `[.contract-item-name]#`create_pure`#` +```rust +pub fn create_pure( + proxy_type: T::ProxyType, + delay: BlockNumberFor, + index: u16 +) +``` + +This call creates a [pure account](/substrate-runtimes/2.0.2/glossary#pure-account) with a proxy issued to it from the call’s origin. + +The origin must be signed for this call. + +***Params:*** + +* `proxy_type: T::ProxyType` — type of calls that will be allowed for the proxy +* `delay: BlockNumberFor` — number of blocks that needs to happen between announcement and call for this proxy +* `index: u16` — A disambiguation index, in case this is called multiple times in the same +transaction (e.g. with `utility::batch`). Unless you’re using `batch` you probably just +want to use `0`. + +***Errors:*** + +* `BadOrigin` — request not signed +* `Duplicate` — `create_pure` was called more than once with the same parameters in the same transaction +* `TooMany` — there is more announcements for this sender than specified in `MaxPending` +* `InsufficientBalance` — delegator does not have enough funds for deposit for proxy creation +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `PureCreated(pure, who, proxy_type, disambiguation_index)` + +#### `[.contract-item-name]#`kill_pure`#` +```rust +pub fn kill_pure( + spawner: <::Lookup as StaticLookup>::Source, + proxy_type: T::ProxyType, + index: u16, + height: BlockNumberFor, + ext_index: u32 +) +``` + +Remove a previously created pure account. + +Requires a `Signed` origin, and the sender account must have been created by a call to +`pure` with corresponding parameters. + + +All access to this account will be lost. + + +***Params:*** + +* `spawner` — account who created a proxy and pure account +* `proxy_type` — type of proxy used for it +* `index` — the disambiguation index used for pure account creation +* `height` — the height of the chain when the call to `pure` was processed. +* `ext_index` — the extrinsic index in which the call to `pure` was processed. + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `spawner` account not found +* `NoPermission` — emitted when account tries to remove somebody but not itself diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/transaction_payment.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/transaction_payment.mdx new file mode 100644 index 00000000..66d23e72 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/transaction_payment.mdx @@ -0,0 +1,53 @@ +--- +title: pallet_transaction_payment +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/transaction-payment/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +`pallet-transaction-payment` implements transaction fee logic. + +In substrate, every transaction has an associated `call`, and each `call` has its own [weight](/substrate-runtimes/2.0.2/glossary#weight) function. The weight function estimates the time it takes to execute the `call`. + +`Config::WeightToFee` is a mapping between the smallest unit of compute (**Weight**) and smallest unit of fee. + +This pallet also exposes +- how to update fees for the next block based on past fees (`Config::FeeMultiplierUpdate`) +- how fees are paid (`Config::OnChargeTransaction`) + +The base fee and adjusted [weight](/substrate-runtimes/2.0.2/glossary#weight-fee) and [length](/substrate-runtimes/2.0.2/glossary#length-fee) fees constitute the _inclusion fee_, which is the minimum fee for a transaction to be included in a block. The formula of final fee: +```rust, ignore +inclusion_fee = base_fee + length_fee + [fee_multiplier_update * weight_fee]; +final_fee = inclusion_fee + tip; +``` +The inputs are defined below in the glossary and config sections. + +## Config + +* Pallet-specific handlers: + * `OnChargeTransaction` -- Handler for withdrawing, refunding and depositing the transaction fee. Type must implement the trait `OnChargeTransaction`. + * `FeeMultiplierUpdate` -- Handler to define how base fees change over time (over blocks). Type must implement the trait `MultiplierUpdate`. Possible assignments include `ConstantFee`, `SlowAdjustingFee`, and `FastAdjustingFee`. +* Pallet-specific converters: + * `WeightToFee` -- Mapping between the smallest unit of weight and smallest unit of fee. Type must implement the trait `WeightToFee>`. + * `LengthToFee` -- Convert a length value into a deductible fee based on the currency type. Type must implement the trait `WeightToFee>`. +* Pallet-specific constants: + * `OperationalFeeMultiplier` -- A fee mulitiplier for `Operational` extrinsics to compute "virtual tip" to boost their `priority`. Type must implement the trait `Get`. +* Common configs: + * `RuntimeEvent` + +## Dispatchables + +There are no dispatchables (and no errors) in this pallet. This pallet is only intended to configure the transaction fee logic for a chain. + +***Events:*** + +* `TransactionFeePaid(who, actual_fee, tip)` -- a transaction fee was paid by account `who` with total amount of `actual_fee + tip`. + +## More Reading + +* [Substrate Weight & Fees](https://www.shawntabrizi.com/blog/substrate/substrate-weight-and-fees/) by Shawn Tabrizi + * [Substrate Docs: Transaction Weight & Fees](https://docs.substrate.io/build/tx-weights-fees/) +* [Substrate Docs: How to Calculate Fees](https://docs.substrate.io/reference/how-to-guides/weights/calculate-fees/#:~:text=Weight%20fee%20%2D%20A%20fee%20calculated,change%20as%20the%20chain%20progresses) diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/treasury.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/treasury.mdx new file mode 100644 index 00000000..a24aca32 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/treasury.mdx @@ -0,0 +1,223 @@ +--- +title: pallet_treasury +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/treasury/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +The Treasury pallet provides a “pot” of funds that can be managed by stakeholders in the system and a structure for making spending proposals from this pot. + +## Config + +* Pallet-specific configs + * `SpendPeriod` -- Period between successive spends. "Spend" means, treasury spending money to a proposal. `SpendPeriod` determines how often the treasury pallet distributes the assets to the proposals. + * `Burn` -- Percentage of spare funds (if any) that are burnt per spend period. + * `BurnDestination` -- Handler for the unbalanced decrease when treasury funds are burned. + * `SpendFunds` -- Runtime hooks to external pallet using treasury to compute spend funds. + * `MaxApprovals` -- The maximum number of approvals that can wait in the spending queue. NOTE: This parameter is also used within the Bounties Pallet extension if enabled. + * `AssetKind` -- Type parameter representing the asset kinds to be spent from the treasury. + * `Beneficiary` -- Type parameter used to identify the beneficiaries eligible to receive treasury spends. + * `BeneficiaryLookup` -- Converting trait to take a source type and convert to [`Self::Beneficiary`]. + * `Paymaster` -- Type for processing spends of [Self::AssetKind] in favor of [`Self::Beneficiary`]. + * `BalanceConverter` -- Type for converting the balance of an [Self::AssetKind] to the balance of the native asset, solely for the purpose of asserting the result against the maximum allowed spend amount of the [`Self::SpendOrigin`]. + * `PayoutPeriod` -- The period during which an approved treasury spend has to be claimed by the beneficiary of the proposal. +* Pallet-specific origins: + * `RejectOrigin` -- Origin from which rejections must come. + * `SpendOrigin` -- The origin required for approving spends from the treasury outside of the proposal process. The `Success` value is the maximum amount in a native asset that this origin is allowed to spend at a time. +* Common configs + * `Currency` -- The staking balance. + * `RuntimeEvent` -- The overarching event type. + * `PalletId` -- The treasury’s pallet id, used for deriving its sovereign account ID. + * `WeightInfo` -- Weight information for extrinsics in this pallet. + * `BenchmarkHelper` -- Helper type for benchmarks. + +## Dispatchables + +#### `[.contract-item-name]#`spend_local`#` +```rust +pub fn spend_local( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + beneficiary: AccountIdLookupOf, +) -> DispatchResult +``` +Propose and approve a spend of treasury funds, enables the creation of spends using the native currency of the chain, utilizing the funds stored in the pot. + + +For record-keeping purposes, the proposer is deemed to be equivalent to the beneficiary. + + + +The behavior and API of the old `spend` call capable of spending local DOT tokens remain unchanged, and is now under the name `spend_local`. The revised +new `spend` call is able to spend any asset kind managed by the treasury. + + +***Params:*** + +* `origin: OriginFor` -- Must be [`Config::SpendOrigin`] with the `Success` value being at least `amount`. +* `value: BalanceOf` -- The amount to be transferred from the treasury to the `beneficiary`. +* `beneficiary: AccountIdLookupOf` -- The destination account for the transfer. + +***Errors:*** + +* `InsufficientPermission` -- if the amount to be spent is greater than what the dispatcher of this call is allowed to spend. +* `TooManyApprovals` -- when `MaxApprovals` limit is hit, and cannot add a new proposal to the storage. + +***Events:*** + +* `SpendApproved(proposal_index, amount, beneficiary)` + +#### `[.contract-item-name]#`spend`#` +```rust +pub fn spend( + origin: OriginFor, + asset_kind: Box, + amount: AssetBalanceOf, + beneficiary: Box<<>::BeneficiaryLookup as StaticLookup>::Source>, + valid_from: Option> +) -> DispatchResult +``` + +Propose and approve a spend of treasury funds, allows spending any asset kind managed by the treasury. + + +The behavior and the API of the previous version of this function is kept the same and renamed to `spend_local`. The new feature `valid_from` is not added to `spend_local` for backward compatibility. + + +***Params:*** + +* `origin: OriginFor` -- Must be [`Config::SpendOrigin`] with the `Success` value being at least `amount`. +* `asset_kind: Box` -- An indicator of the specific asset class to be spent. +* `value: BalanceOf` -- The amount to be transferred from the treasury to the `beneficiary`. +* `beneficiary: AccountIdLookupOf` -- The destination account for the transfer. +* `valid_from: Option>` -- The block number from which the spend can be claimed. It can refer to the past if the resulting spend has not yet expired according to the [`Config::PayoutPeriod`]. If `None`, the spend can be claimed immediately after approval. + +***Errors:*** + +* `InsufficientPermission` -- if the amount to be spent is greater than what the dispatcher of this call is allowed to spend. +* `SpendExpired` -- if expiration date is older than now. +* `FailedToConvertBalance` -- when conversion between `asset_kind` and `native currency` fails. + +***Events:*** + +* `AssetSpendApproved(index, asset_kind, amount, beneficiary, valid_from, expire_at)` -- `index` is the index of the proposal. Rest is self-explanatory. + +#### `[.contract-item-name]#`remove_approval`#` +```rust +pub fn remove_approval( + origin: OriginFor, + proposal_id: ProposalIndex +) -> DispatchResult +``` + +Force a previously approved proposal to be removed from the approval queue. + +***Params:*** + +* `origin: OriginFor` -- Must be [Config::RejectOrigin]. +* `proposal_id: ProposalIndex` -- The index of a proposal. + +***Errors:*** + +* `ProposalNotApproved` -- The proposal does not exist in the approved proposals queue. + +***Events:*** + +* `AssetSpendApproved(index, asset_kind, amount, beneficiary, valid_from, expire_at)` -- `index` is the index of the proposal. Rest is self-explanatory. + +#### `[.contract-item-name]#`payout`#` +```rust +pub fn payout(origin: OriginFor, index: SpendIndex) -> DispatchResult +``` + +Claims a spend. + +Spends must be claimed within some temporal bounds. A spend may be claimed within one [`Config::PayoutPeriod`] from the `valid_from` block. +In case of a payout failure, the spend status must be updated with the `check_status` dispatchable before retrying with the current function. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `index: SpendIndex` -- The index of the spend. + +***Errors:*** + +* `InvalidIndex` -- The spend could not be found. +* `EarlyPayout` -- The spend tried to be claimed before it became valid (see `valid_from` field). +* `SpendExpired` -- The spend tried to be claimed after it expired. +* `AlreadyAttempted` -- The same spend tried to be claimed before. +* `PayoutError` -- An error occurred during the payment, related to `Paymaster::pay` function. + +***Events:*** + +* `Paid(index, payment_id)` + +#### `[.contract-item-name]#`check_status`#` +```rust +pub fn check_status( + origin: OriginFor, + index: SpendIndex +) -> DispatchResultWithPostInfo +``` + +Check the status of the spend and remove it from the storage if processed. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `index: SpendIndex` -- The index of the spend. + +***Errors:*** + +* `InvalidIndex` -- The spend could not be found. +* `NotAttempted` -- The payout was not attempted. +* `Inconclusive` -- The spend is still in progress. + +***Events:*** + +* `SpendProcessed(index)` -- Spend is successfully processed. +* `PaymentFailed(index, payment_id)` -- The payout was failed, and can be retried again. This error also gives the `payment_id` info for further investigation. + +#### `[.contract-item-name]#`void_spend`#` +```rust +pub fn void_spend(origin: OriginFor, index: SpendIndex) -> DispatchResult +``` + +Void previously approved spend. + +A spend void is only possible if the payout has not been attempted yet. + + +even if the payout is failed, it still counts towards an attempt, and cannot be voided at this point. + + +***Params:*** + +* `origin: OriginFor` -- Must be [Config::RejectOrigin]. +* `index: SpendIndex` -- The index of the spend. + +***Errors:*** + +* `InvalidIndex` -- The spend could not be found. +* `AlreadyAttempted` -- The same spend tried to be claimed before. + +***Events:*** + +* `AssetSpendVoided(index)` + +## Important Mentions and FAQ's + +You might have come across the below from official documentation or source-code: + +* `propose_spend` will be removed in February 2024. Use spend instead. +* `reject_proposal` will be removed in February 2024. Use spend instead. +* `approve_proposal` will be removed in February 2024. Use spend instead. + +The new `spend` dispatchable will not be able to solely `propose` or `approve` proposals separately, nor `reject` them. + +After the deprecation update, `treasury` pallet no longer tracks `unapproved` proposals, but only approved ones. + +The idea is to use the `treausry` pallet combined with some other pallet which will provide the functionality of tracking unapproved proposals (reject, approve, propose). For Polkadot it’s OpenGov (referenda and conviction voting pallets). diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/xcm.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/xcm.mdx new file mode 100644 index 00000000..7270539a --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/xcm.mdx @@ -0,0 +1,451 @@ +--- +title: pallet_xcm +--- + +Branch/Release: `release-polkadot-v1.10.0` + +Source Code [pass:[](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/polkadot/xcm/pallet-xcm/src/lib.rs),role=heading-link] + +## Purpose + +`pallet-xcm` is responsible for filtering, routing, and executing incoming XCM. + +## Config + +* Pallet-specific origins: + * `AdminOrigin` -- The origin that is allowed to call privileged operations on the XCM pallet. Type must implement trait `EnsureOrigin`. + * `ExecuteXcmOrigin` -- Required origin for executing XCM messages, including the teleport functionality. If successful, it returns a `[MultiLocation](/substrate-runtimes/2.0.2/glossary#multilocation)` which exists as an interior location within this chain’s XCM context. Type must implement trait `EnsureOrigin`. + * `SendXcmOrigin` -- Required origin for sending XCM messages. If successful, it resolves to `MultiLocation`. Type must implement trait `EnsureOrigin`. +* Pallet-specific ID types: + * `RemoteLockConsumerIdentifier` -- The ID type for local consumers of remote locks. The type must implement `Copy`. +* Pallet-specific handlers: + * `Currency` -- Lockable currency used in the pallet. Type must implement the trait `LockableCurrency`. + * `Weigher` -- Measures [weight](/substrate-runtimes/2.0.2/glossary#weight) consumed by XCM local execution. Type must implement the trait `WeightBounds`. + * `XcmExecutor` -- Means of executing XCM. Type must implement the trait `ExecuteXcm`. + * `XcmRouter` -- The type used to dispatch an XCM to its destination. Type must implement the trait `SendXcm`. +* Pallet-specific filters: + * `XcmExecuteFilter` -- XCM filter for which messages to be executed using `XcmExecutor` must pass. Type must implement trait `Contains<(MultiLocation, Xcm<::RuntimeCall>)>`. + * `XcmReserveTransferFilter` -- XCM filter for which messages to be reserve-transferred using the dedicated extrinsic must pass.Type must implement the trait `Contains<(MultiLocation, Vec)>`. + * `XcmTeleportFilter` -- XCM filter for which messages to be teleported using the dedicated extrinsic must pass. Type must implement the trait `Contains<(MultiLocation, Vec)>`. + * `TrustedLockers` -- Returns whether or not the origin can be trusted for a specific asset. Type must implement `ContainsPair` where each pair is an `asset` and an `origin` thereby entrusting `origin` for operations relating to `asset`. +* Pallet-specific converters: + * `CurrencyMatcher` -- The `[MultiAsset](/substrate-runtimes/2.0.2/glossary#multiasset)` matcher for `Currency`. Type must implement the trait `MatchesFungible`. + * `SovereignAccountOf` -- How to get an `AccountId` value from a `MultiLocation`, useful for handling asset locks. Type must implement `ConvertLocation`. +* Pallet-specific constants: + * `VERSION_DISCOVERY_QUEUE_SIZE` -- `u32` measures the size of the version discovery queue. Typically set to `100`. + * `AdvertisedXcmVersion` -- The latest supported XCM version that this chain accepts. Type must implement the trait `Get`. Commonly set to `pallet_xcm::CurrentXcmVersion`. + * `MaxLockers` -- The maximum number of local XCM locks that a single account may have. Type must implement the trait `Get`. + * `MaxRemoteLockConsumers` -- The maximum number of consumers a single remote lock may have. Type must implement the trait `Get`. + * `UniversalLocation` -- This chain’s Universal Location. Type must implement the trait `Get`. +* Common configs: + * `RuntimeEvent` + * `RuntimeCall` + * `RuntimeOrigin` + * `WeightInfo` + +## Dispatchables + +#### `[.contract-item-name]#`send`#` +```rust +pub fn send( + origin: OriginFor, + dest: Box, + message: Box>, +) +``` + + +DEPRECATED. `send` will be removed after June 2024. Use `send_blob` instead. + + +* Deprecation explanation: + * `pallet-xcm` has a new pair of extrinsics, `execute_blob` and `send_blob`. These are meant to be used instead of `execute` and `send`, which are now deprecated and will be removed eventually. These new extrinsics just require you to input the encoded XCM. + * There’s a new utility in PolkadotJS Apps for encoding XCMs you can use: https://polkadot.js.org/apps/#/utilities/xcm Just pass in the encoded XCM to the new extrinsics and you’re done. + * The migration from the old extrinsic to the new is very simple. If you have your message `xcm: VersionedXcm`, then instead of passing in `Box::new(xcm)` to both `execute` and `send`, you would pass in `xcm.encode().try_into()` and handle the potential error of its encoded length being bigger than `MAX_XCM_ENCODED_SIZE`. + * `pallet-contracts` takes the XCM encoded now as well. It follows the same API as `execute_blob` and `send_blob`. + +Send a versioned XCM `message` to the destination `dest`. + +The origin must be `SendXcmOrigin` for this call. + +***Params:*** + +* `dest: Box` — destination for the XCM +* `message: Box>` — versioned XCM to be sent to the multilocation `dest` + +***Errors:*** + +* `InvalidOrigin` — origin did not match `SendXcmOrigin` +* `BadVersion` — version for XCM not valid + +***Events:*** + +* `Sent(origin, destination, message, message_id)` -- The versioned XCM `message` was sent from the `origin` to the `destination`. + +#### `[.contract-item-name]#`execute`#` +```rust +pub fn execute( + origin: OriginFor, + message: Box::RuntimeCall>>, + max_weight: Weight, +) +``` + + +DEPRECATED. `execute` will be removed after June 2024. Use `execute_blob` instead. + + +* Deprecation explanation: + * `pallet-xcm` has a new pair of extrinsics, `execute_blob` and `send_blob`. These are meant to be used instead of `execute` and `send`, which are now deprecated and will be removed eventually. These new extrinsics just require you to input the encoded XCM. + * There’s a new utility in PolkadotJS Apps for encoding XCMs you can use: https://polkadot.js.org/apps/#/utilities/xcm Just pass in the encoded XCM to the new extrinsics and you’re done. + * The migration from the old extrinsic to the new is very simple. If you have your message `xcm: VersionedXcm`, then instead of passing in `Box::new(xcm)` to both `execute` and `send`, you would pass in `xcm.encode().try_into()` and handle the potential error of its encoded length being bigger than `MAX_XCM_ENCODED_SIZE`. + * `pallet-contracts` takes the XCM encoded now as well. It follows the same API as `execute_blob` and `send_blob`. + +Execute an XCM message from a local, signed, origin. + +The origin must be `ExecuteXcmOrigin` for this call. + + +A successful return to this does NOT imply that the `msg` was executed successfully to completion; only that SOME of it was executed. + + +***Params:*** + +* `message: Box>` — versioned XCM to be executed +* `max_weight: Weight` -- No more than this amount of `Weight` will be consumed during this execution attempt. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin` +* `BadVersion` —- version for XCM not valid + +***Events:*** + +* `Attempted(outcome)` -- Indicates whether the `msg` was executed completely or only partially. + +#### `[.contract-item-name]#`force_xcm_version`#` +```rust +pub fn force_xcm_version( + origin: OriginFor, + location: Box, + version: XcmVersion, +) +``` +Set that a particular destination can be communicated with through a particular version of XCM. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `location: Box` —- The destination that is being described. +* `version: XcmVersion` -- The latest version of XCM that `location` supports. + +***Errors:*** + +* `BadOrigin` — origin did not match `AdminOrigin` + +***Events:*** + +* `Event::SupportedVersionChanged location, version ` -- `location` was updated to support the latest version of XCM `version` + +#### `[.contract-item-name]#`force_default_xcm_version`#` +```rust +pub fn force_default_xcm_version( + origin: OriginFor, + maybe_xcm_version: Option, +) +``` +Set a safe XCM version (the version that XCM should be encoded with if the most recent version a destination can accept is unknown). + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `maybe_xcm_version: Option` —- The default XCM encoding version, or `None` to disable. + +***Errors:*** + +* `BadOrigin` — origin did not match `AdminOrigin` + +***Events:*** + +None + +#### `[.contract-item-name]#`force_subscribe_version_notify`#` +```rust +pub fn force_subscribe_version_notify( + origin: OriginFor, + location: Box, +) +``` +Ask a location to notify us regarding their XCM version and any changes to it. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `location: Box`: The location to which we should subscribe for XCM version notifications. + +***Errors:*** + +* `BadOrigin` — origin did not match `AdminOrigin` + +***Events:*** + +None + +#### `[.contract-item-name]#`force_unsubscribe_version_notify`#` +```rust +pub fn force_unsubscribe_version_notify( + origin: OriginFor, + location: Box, +) +``` +Require that a particular destination should no longer notify us regarding any XCM version changes. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `location: Box`: The location from which we are but no longer wish to subscribe to XCM version notifications. + +***Errors:*** + +* `BadOrigin` —- origin did not match `AdminOrigin` +* `NoSubscription` -- subscription not found to `location` +* `BadLocation` -- location not found + +***Events:*** + +None + +#### `[.contract-item-name]#`limited_reserve_transfer_assets`#` +```rust +pub fn limited_reserve_transfer_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, +) +``` +Transfer some assets from the local chain to the sovereign account of a destination chain and forward a notification XCM. + +The origin must be `ExecuteXcmOrigin` for this call. + +***Params:*** + +* `dest: Box` -- Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Willgenerally be an `AccountId32` value. +* `assets: Box` -- The assets to be withdrawn. This should include the assets used to pay the fee on the `dest` side. +* `fee_asset_item: u32` -- The index into `assets` of the item which should be used to pay fees. +* `weight_limit: WeightLimit` -- The remote-side weight limit, if any, for the XCM fee purchase. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcm` +* `BadVersion` -- `beneficiary` or `assets` have incorrect versioning +* `TooManyAssets` -- assets length exceeds MAX_ASSETS_FOR_TRANSFER which equals 2 in this code + +***Events:*** + +* `Event::Attempted outcome ` -- Attempted the reserve transfer with returned status `outcome` + +#### `[.contract-item-name]#`limited_teleport_assets`#` +```rust +pub fn limited_teleport_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, +) +``` +Teleport some assets from the local chain to some destination chain. + +Fee payment on the destination side is made from the asset in the `assets` vector of index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight is needed than `weight_limit`, then the operation will fail and the assets send may be at risk. + +The origin must be `ExecuteXcmOrigin` for this call. + +***Params:*** + +* `dest: Box` -- Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to teleport from parachain to parachain, or `X1(Parachain(..))` to teleport from relay to parachain. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. +* `assets: Box` -- The assets to be withdrawn. This should include the assets used to pay the fee on the `dest` side. +* `fee_asset_item: u32` -- The index into `assets` of the item which should be used to pay fees. +* `weight_limit: WeightLimit` -- The remote-side weight limit, if any, for the XCM fee purchase. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcm` +* `BadVersion` -- `beneficiary` or `assets` have incorrect versioning +* `TooManyAssets` -- assets length exceeds MAX_ASSETS_FOR_TRANSFER which equals 2 in this code + +***Events:*** + +* `Event::Attempted outcome ` -- Attempted the teleport status with returned status `outcome` + +#### `[.contract-item-name]#`force_suspension`#` +```rust +pub fn force_suspension( + origin: OriginFor, + suspended: bool, +) +``` +Set or unset the global suspension state of the XCM executor. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `suspended: bool` -- `true` to suspend, `false` to resume. + +***Errors:*** + +None + +***Events:*** + +None + +#### `[.contract-item-name]#`transfer_assets`#` +```rust +pub fn transfer_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, +) +``` +Transfer some assets from the local chain to the destination chain through their local, destination or remote reserve, or through teleports. + +***Params:*** + +* `dest: Box` -- Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. +* `assets`: The assets to be withdrawn. This should include the assets used to pay the fee on the `dest` (and possibly reserve) chains. +* `fee_asset_item: u32` -- The index into `assets` of the item which should be used to pay fees. +* `weight_limit: WeightLimit` -- The remote-side weight limit, if any, for the XCM fee purchase. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin`. +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `TooManyAssets` -- `assets` contain more than `MAX_ASSETS_FOR_TRANSFER = 2` to transfer. +* `Empty` -- can be a number of different errors: + * `fee_asset_item` is not present in `assets`. + * some fungible asset in `assets` has a value of 0. + * fees or asset transfer type was not determined. +* `TooManyReserves` -- there are more than one transfer type for an asset. +* `InvalidAssetUnknownReserve` -- transfer type can not be determined for a given asset. +* `InvalidAssetUnsupportedReserve` -- asset or fees transfer type is remote reserve and asset and fees asset are different. +* `Filtered` -- can be a number of different errors: + * `XcmReserveTransferFilter` filtered the asset. + * `XcmTeleportFilter` filtered the asset +* `CannotReanchor` -- asset can’t be reanchored. +* `CannotCheckOutTeleport` -- asset can’t be teleported +* `UnweighableMessage` -- prepared XCM message had issues with weighing (i.e. more instructions than the limit). +* `LocalExecutionIncomplete` -- local execution of XCM message have failed. +* `FeesNotMet` -- unable to charge fees. See the error log of any node to see the details. + +***Events:*** + +* `Sent(origin, destination, message, message_id)` + +***Deprecated Extrinsics***: +- `teleport_assets` -- Use `limited_teleport_assets` instead. +- `reserve_transfer_assets` -- Use `limited_reserve_transfer_assets` instead. + +#### `[.contract-item-name]#`claim_assets`#` +```rust +pub fn claim_assets( + origin: OriginFor, + assets: Box, + beneficiary: Box, +) -> DispatchResult +``` + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `assets: Box` -- The exact assets that were trapped. Use the version to specify what version was the latest when they were trapped. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin`. +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `UnweighableMessage` -- prepared XCM message had issues with weighing (i.e. more instructions than the limit). +* `LocalExecutionIncomplete` -- local execution of XCM message have failed. + +***Events:*** + +None + +#### `[.contract-item-name]#`execute_blob`#` +```rust +pub fn execute_blob( + origin: OriginFor, + encoded_message: BoundedVec, + max_weight: Weight, +) -> DispatchResultWithPostInfo +``` + +Execute an XCM from a local, signed, origin. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `encoded_message: BoundedVec` -- The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`]. +* `max_weight: Weight` -- No more than `max_weight` will be used in its attempted execution. If this is less than the maximum amount of weight that the message could take to be executed, then no execution attempt will be made. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin`. +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `Filtered` -- can be a number of different errors: +* `LocalExecutionIncomplete` -- local execution of XCM message have failed. +* `UnableToDecode` -- unable to decode the XCM. +* `XcmTooLarge` -- XCM encoded length is larger than `MaxXcmEncodedSize`. + +***Events:*** + +* `Attempted(outcome)` -- Indicates whether the `msg` was executed completely or only partially. + +#### `[.contract-item-name]#`send_blob`#` +```rust +pub fn send_blob( + origin: OriginFor, + dest: Box, + encoded_message: BoundedVec, +) -> DispatchResult +``` + +Send an XCM from a local, signed, origin. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `dest: Box` -- The destination, `dest`, will receive this message with a `DescendOrigin` instruction that makes the origin of the message be the origin on this system. +* `encoded_message: BoundedVec` -- The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`]. + +***Errors:*** + +* `InvalidOrigin` -- origin did not match `SendXcmOrigin` +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `UnableToDecode` -- unable to decode the XCM. +* `FeesNotMet` -- unable to charge fees. See the error log of any node to see the details. +* `Unreachable` -- The desired destination was unreachable, generally because there is a no way of routing to it. +* `SendFailure` -- There was some other issue (i.e. not to do with routing) in sending the message. Perhaps a lack of space for buffering the message. + +***Events:*** + +* `Sent(origin, destination, message, message_id)` -- The versioned XCM `message` was sent from the `origin` to the `destination`. + +## More Reading + +[Polkadot Wiki XCM Use Cases](https://wiki.polkadot.network/docs/learn-xcm-usecases) diff --git a/docs/content/substrate-runtimes/2.0.2/pallets/xcmp-queue.mdx b/docs/content/substrate-runtimes/2.0.2/pallets/xcmp-queue.mdx new file mode 100644 index 00000000..c9b5d682 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/pallets/xcmp-queue.mdx @@ -0,0 +1,133 @@ +--- +title: cumulus_pallet_xcmp_queue +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code link:https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/cumulus/pallets/xcmp-queue/src/lib.rs[pass:[],role=heading-link] + +## Purpose + +Responsible for the Queues (both incoming and outgoing) for XCMP messages. This pallet does not actually receive or send messages. Its responsibility is to place the incoming and outgoing XCMP messages in their respective queues and manage these queues. + +## Config + +* Pallet-specific configs + * `ChannelInfo` -- Configures two functions `get_channel_status` and `get_channel_info` to provide information about channels (`ChannelStatus` and `ChannelInfo`). + * `VersionWrapper` -- Means of converting an `Xcm` into a `VersionedXcm`. Xcm’s should be versioned in order to pass the validation. + * `XcmpQueue` -- Defines max length of an XCMP message, and how `enqueue_message` should behave. + * `MaxInboundSuspended` -- The maximum number of inbound XCMP channels that can be suspended simultaneously. Any further channel suspensions will fail and messages may get dropped without further notice. Choosing a high value (1000) is okay. + * `ControllerOrigin` -- The origin that is allowed to resume or suspend the XCMP queue. + * `ControllerOriginConverter` -- The conversion function used to attempt to convert an XCM `[MultiLocation](/substrate-runtimes/2.0.2/glossary#multilocation)` origin to a superuser origin. This is used for allowing the superuser’s queue to send messages even to paused queues. + * `PriceForSiblingDelivery` -- The price for delivering an XCM to a sibling parachain destination. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- The [weight](/substrate-runtimes/2.0.2/glossary#weight) information of this pallet. + +## Dispatchables + +#### `[.contract-item-name]#`suspend_xcm_execution`#` +```rust +pub fn suspend_xcm_execution(origin: OriginFor) -> DispatchResult +``` +Suspends all XCM executions for the XCMP queue. + + +`origin` must pass the `ControllerOrigin` check. + + +this does not change the status of the in/out bound channels. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable + +***Errors:*** + +* `AlreadySuspended` -- description of conditions, when this error happens + +#### `[.contract-item-name]#`resume_xcm_execution`#` +```rust +pub fn resume_xcm_execution(origin: OriginFor) -> DispatchResult +``` +Resumes all XCM executions for the XCMP queue. + + +`origin` must pass the `ControllerOrigin` check. + + +this does not change the status of the in/out bound channels. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable + +***Errors:*** + +* `AlreadyResumed` -- description of conditions, when this error happens + +#### `[.contract-item-name]#`update_suspend_threshold`#` +```rust +pub fn update_suspend_threshold(origin: OriginFor, new: u32) -> DispatchResult +``` +Overwrites the number of pages which must be in the queue for the other side to be told to suspend their sending. + + +`origin` must be root. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable +* `new: u32` -- new page threshold for suspend + +#### `[.contract-item-name]#`update_drop_threshold`#` +```rust +pub fn update_drop_threshold(origin: OriginFor, new: u32) -> DispatchResult +``` +Overwrites the number of pages which must be in the queue after which we drop any further messages from the channel. + + +`origin` must be root. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable +* `new: u32` -- new page threshold for drop + +#### `[.contract-item-name]#`update_resume_threshold`#` +```rust +pub fn update_resume_threshold(origin: OriginFor, new: u32) -> DispatchResult +``` +Overwrites the number of pages which the queue must be reduced to before it signals that message sending may recommence after it has been suspended. + + +`origin` must be root. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable +* `new: u32` -- new page threshold for resume + +## Important Mentions and FAQ's + + +messages are not ordered when they are received, but they are ordered when they are sent. `Signal` messages are prioritized over `non-signal` messages. + + +Messages and signals are stored in different queues. When the messages to be sent are taken with `take_outbound_messages`, they will be ordered: + +* if there are signals present for outbound messages that targeting a parachain, we will only send signals, not messages +* messages (that are not signals) won’t be ordered + + +polkadot/xcm/src/v3 has `SendXcm` trait, which has 2 blank methods validate and deliver. For each router struct, one can implement `SendXcm` for it. + + +1. `deliver` method take `destination` as a parameter, and calls `send_fragment` function with the target parachain id. +2. `send_fragment` puts the message to the queue of the given parachain id. + * unlike it’s name, `deliver` method does not actually delivers the message. It is calling `send_fragment`, which places a message fragment on the outgoing XCMP queue for recipient. So, `deliver` is only putting the message to the respective outgoing xcmp queue diff --git a/docs/content/substrate-runtimes/2.0.2/runtime/xcm_executor.mdx b/docs/content/substrate-runtimes/2.0.2/runtime/xcm_executor.mdx new file mode 100644 index 00000000..81a3aa59 --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/runtime/xcm_executor.mdx @@ -0,0 +1,73 @@ +--- +title: XCM Executor +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Purpose + +`XcmExecutor` is responsible for executing XCM messages locally. + +`XcmExecutor` is usually the assignment for `pallet_xcm::Config::XcmExecutor` and is thereby used to execute XCM in that pallet. + + + +`XcmExecutor` is not a pallet, but rather it is a `struct` type parameterized by a `Config` trait. The inner config is the `trait Config` which parameterizes the outer config `struct XcmExecutor`. Both the inner and outer configs are configured in the runtime. + + + +## Inner Config + +The inner `trait Config` used to parameterize `XcmExecutor` has the following associated types. + +* Handlers: + * `XcmSender` -- How to send an onward XCM message. Type must implement the trait `SendXcm`. + * `AssetTransactor` -- How to withdraw and deposit an asset. Type must implement the trait `TransactAsset`. + * `Trader` -- The means of purchasing [weight](/substrate-runtimes/2.0.2/glossary#weight) credit for XCM execution. Type must implement the trait `WeightTrader`. + * `ResponseHandler` -- What to do when a response of a query is found. Type must implement the trait `OnResponse`. + * `AssetTrap` -- The general asset trap handler for when assets are left in the Holding Register at the end of execution. Type must implement the trait `DropAssets`. + * `AssetLocker` -- Handler for asset locking. Type must implement the trait `AssetLock`. + * `AssetExchanger` -- Handler for exchanging assets. Type must implement the trait `AssetExchange`. + * `AssetClaims` -- The handler for when there is an instruction to claim assets. Type must implement the trait `ClaimAssets`. + * `SubscriptionService` -- The handler for version subscription requests. Type must implement the trait `VersionChangeNotifier`. + * `FeeManager` -- Configure the fees. Type must implement the trait `FeeManager`. + * `MessageExporter` -- The method of exporting a message. Type must implement the trait `ExportXcm`. + * `CallDispatcher` -- The call dispatcher used by XCM. Type must implement the trait `CallDispatcher`. + * `HrmpNewChannelOpenRequestHandler` -- Allows optional logic execution for the `HrmpNewChannelOpenRequest` XCM notification. + * `HrmpChannelAcceptedHandler` -- Allows optional logic execution for the `HrmpChannelAccepted` XCM notification. + * `HrmpChannelClosingHandler` -- Allows optional logic execution for the `HrmpChannelClosing` XCM notification. +* Filters: + * `IsReserve` -- Combinations of (Asset, Location) pairs which we trust as reserves. Type must implement the trait `ContainsPair`. + * `IsTeleporter` -- Combinations of (Asset, Location) pairs which we trust as teleporters. Type must implement the trait `ContainsPair`. + * `Aliasers` -- A list of (Origin, Target) pairs allowing a given Origin to be substituted with its corresponding Target pair. Type must implement the trait `ContainsPair`. + * `Barrier` -- Whether or not to execute the XCM at all. Type must implement `ShouldExecute`. + * `UniversalAliases` -- The origin locations and specific universal [junctions](/substrate-runtimes/2.0.2/glossary#junctions) to which they are allowed to elevate themselves. Type must implement the trait `Contains<(MultiLocation, Junction)>`. + * `SafeCallFilter` -- The safe call filter for `Transact`. Use this type to explicitly whitelist calls that cannot undergo recursion. Type must implement the trait `Contains`. +* Converters: + * `OriginConverter` -- How to get a call origin from a `OriginKind` value. Type must implement the trait `ConvertOrigin<::RuntimeOrigin>`. +* Accessors: + * `Weigher` -- The means of determining an XCM message’s weight. Type must implement the trait `WeightBounds`. + * `PalletInstancesInfo` -- Information on all pallets. Type must implement the trait `PalletsInfoAccess`. +* Constants: + * `UniversalLocation` -- This chain’s Universal Location. Type must implement the trait `Get`. + * `MaxAssetsIntoHolding` -- The maximum number of assets we target to have in the Holding Register at any one time. Type must implement the trait `Get`. +* Common configs: + * `RuntimeCall` + +## Outer Config + +The outer `struct XcmExecutor` configures the following fields: + +* `holding` -- Assets allowed in the holding register. Type must be `Assets`. +* `holding_limit` -- The maximum number of assets in the holding register. Type must be `usize`. +* `context` -- Type must be `XcmContext`. +* `trader` -- Type must be `Config::Trader` which must implement the trait `WeightTrader`. +* `error` -- The most recent error result and instruction index into the fragment in which it occurred, if any. Type must be `Option<(u32, XcmError)>`. +* `total_surplus` -- Type must be `Weight`. +* `total_refunded` -- Type must be `Weight`. +* `error_handler: Xcm`, +* `error_handler_weight` -- Type must be `Weight`. +* `appendix` -- Type must be `Xcm`. +* `appendix_weight` -- Type must be `Weight`. +* `transact_status` -- Type must be `MaybeErrorCode`. +* `fees_mode` -- Type must be `FeesMode`. diff --git a/docs/content/substrate-runtimes/2.0.2/runtimes/evm.mdx b/docs/content/substrate-runtimes/2.0.2/runtimes/evm.mdx new file mode 100644 index 00000000..2120fafd --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/runtimes/evm.mdx @@ -0,0 +1,125 @@ +--- +title: EVM Runtime +--- + +## Purpose + +EVM Runtime Template is built on top of the [Generic Runtime Template](/substrate-runtimes/2.0.2/runtimes/generic). + +The purpose of this template is to provide EVM compatibilities embedded into the runtime. + +With this template, you can: +* Deploy Solidity Smart Contracts to your runtime. +* Interact with the deployed Smart Contracts. +* Deploy and interact with ERC20 tokens. +* Migrate your existing dAPP from Ethereum ecosystem to Polkadot ecosystem. + +The pallet list is constructed by the contributions of Parity, the community, and OpenZeppelin. + +To maximize the compatibility with EVM, we decided to use `AccountId20` type instead of `AccountId32`. +This choice allowed us to use the signature scheme compatible with Ethereum (ECDSA). +Which, in turn result in better compatibility with tools across the Ethereum ecosystem including wallets, developer tooling, etc. +Metamask integration for example is way simpler for chains with the Ethereum account and signature type. + +## Configuration + +### System Support +
+click to expand + + +* [frame_system](https://paritytech.github.io/polkadot-sdk/master/frame_system/index.html#) is responsible from creating the runtime, initializing the storage, and providing the base functionality for the runtime. +* [cumulus_pallet_parachain_system](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_parachain_system/index.html#) handles low-level details of being a parachain. +* [pallet_timestamp](https://paritytech.github.io/polkadot-sdk/master/pallet_timestamp/index.html#) provides a way for consensus systems to set and check the onchain time. +* [parachain_info](https://docs.rs/staging-parachain-info/latest/staging_parachain_info/index.html#) provides a way for parachains to report their parachain id and the relay chain block number. +* [pallet_proxy](https://docs.rs/pallet-proxy/latest/pallet_proxy/#) enables delegation of rights to execute certain call types from one origin to another. +* [pallet_utility](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/index.html#) contains two basic pieces of functionality: + * Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a single dispatch. This can be useful to amalgamate proposals, combining `set_code` with corresponding `set_storage`s, for efficient multiple payouts with just a single signature verify, or in combination with one of the other two dispatch functionality. + * [force_batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.force_batch): Sends a batch of dispatch calls. Errors are allowed and won’t interrupt + * [batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch): Sends a batch of dispatch calls. This will return `Ok` in all circumstances. To determine the success of the batch, an event is deposited. If a call failed and the batch was interrupted, then the `BatchInterrupted` event is deposited, along with the number of successful calls made and the error of the failed call. If all were successful, then the `BatchCompleted` event is deposited. + * [batch_all](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch_all): Send a batch of dispatch calls and atomically execute them. The whole transaction will rollback and fail if any of the calls failed. + * Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from an alternative signed origin. Each account has 2 * 2**16 possible “pseudonyms” (alternative account IDs) and these can be stacked. This can be useful as a key management tool, where you need multiple distinct accounts (e.g. as controllers for many staking accounts), but where it’s perfectly fine to have each of them controlled by the same underlying keypair. Derivative accounts are, for the purposes of proxy filtering considered exactly the same as the origin and are thus hampered with the origin’s filters. +* [pallet_multisig](https://docs.rs/pallet-multisig/latest/pallet_multisig/#) enables multi-signature operations in your runtime. This module allows multiple signed origins (accounts) to coordinate and dispatch a call. For the call to execute, the threshold number of accounts from the set (signatories) must approve it. +* [pallet_scheduler](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/#) schedules runtime calls. +* [preimage](https://docs.rs/pallet-preimage/latest/pallet_preimage/#) allows for the users and the runtime to store the preimage of a hash on chain. This can be used by other pallets for storing and managing large byte-blobs. + +
+ +### Monetary +
+click to expand + + +* [pallet_balances](https://docs.rs/pallet-balances/latest/pallet_balances/#) provides functions for: + * Getting and setting free balances. + * Retrieving total, reserved and unreserved balances. + * Repatriating a reserved balance to a beneficiary account that exists. + * Transferring a balance between accounts (when not reserved). + * Slashing an account balance. + * Account creation and removal. + * Managing total issuance. + * Setting and managing locks. +* [pallet_transaction_payment](https://docs.rs/pallet-transaction-payment/latest/pallet_transaction_payment/#) provides the basic logic needed to pay the absolute minimum amount needed for a transaction to be included. This includes: + * **base fee**: This is the minimum amount a user pays for a transaction. It is declared as a base **weight** in the runtime and converted to a fee using `WeightToFee`. + * **weight fee**: A fee proportional to amount of weight a transaction consumes. + * **length fee**: A fee proportional to the encoded length of the transaction. + * **tip**: An optional tip. Tip increases the priority of the transaction, giving it a higher chance to be included by the transaction queue. +* [pallet_assets](https://paritytech.github.io/polkadot-sdk/master/pallet_assets/index.html#) deals with sets of assets implementing fungible traits, via [fungibles] traits in a simple, secure manner. This pallet makes heavy use of concepts such as Holds and Freezes from the [frame_support::traits::fungible] traits, therefore you should read and understand those docs as a prerequisite to understanding this pallet. +* [pallet_treasury](https://docs.rs/pallet-treasury/latest/pallet_treasury/#) provides a “pot” of funds that can be managed by stakeholders in the system and a structure for making spending proposals from this pot. +* [#pallet_asset_manager](https://github.com/moonbeam-foundation/moonbeam/blob/master/pallets/asset-manager/src/lib.rs) allows to register new assets if certain conditions are met. + +
+ +### Governance +
+click to expand + + +* [pallet_sudo](https://docs.rs/pallet-sudo/latest/pallet_sudo/#) provides a way to execute privileged runtime calls using a specified sudo (“superuser do”) account. +* [pallet_conviction_voting](https://docs.rs/pallet-conviction-voting/latest/pallet_conviction_voting/#) manages actual voting in polls +* [pallet_referenda](https://docs.rs/pallet-referenda/latest/pallet_referenda/#) executes referenda. No voting logic is present here, and the Polling and PollStatus traits are used to allow the voting logic (likely in a pallet) to be utilized. +* [pallet_custom_origins](https://github.com/OpenZeppelin/polkadot-runtime-templates/blob/main/evm-template/runtime/src/configs/governance/origins.rs#) allows custom origins for governance. // TODO: double check this, is it really our own pallet, or just copy paste? +* [pallet_whitelist](https://docs.rs/pallet-whitelist/latest/pallet_whitelist/index.html#) allows some configurable origin: `Config::WhitelistOrigin` to whitelist some hash of a call, and allows another configurable origin: `Config::DispatchWhitelistedOrigin` to dispatch them with the root origin. + +
+ +### Collator support +
+click to expand + + +* [pallet_authorship](https://docs.rs/pallet-authorship/latest/pallet_authorship/#) provides authorship tracking for FRAME runtimes. This tracks the current author of the block and recent uncles. +* [pallet_collator_selection](https://paritytech.github.io/polkadot-sdk/master/pallet_collator_selection/index.html#) - manages the collators of a parachain. ***Collation is *not** a secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT safety assumptions of the chosen set. This pallet can: + * set invulnerable candidates (fixed candidates) + * set desired candidates (ideal number of non-fixed) + * set candidacy bond + * remove invulnerability (turn candidate into not fixed) + * and many more (all related to collators) +* [pallet_session](https://paritytech.github.io/polkadot-sdk/master/pallet_session/index.html#) allows validators to manage their session keys, provides a function for changing the session length, and handles session rotation. +* [pallet_aura](https://docs.rs/pallet-aura/latest/pallet_aura/#) extends Aura consensus by managing offline reporting. It can: + * get the current slot + * get the slot duration + * change and initialize authorities + * ensure the correctness of the state of this pallet +* [cumulus_pallet_aura_ext](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_aura_ext/index.html#) extends the Substrate AuRa pallet to make it compatible with parachains. It provides the Pallet, the Config and the GenesisConfig. + +
+ +### XCM Helpers +
+click to expand + + +* [cumulus_pallet_xcmp_queue](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/index.html#) Responsible for the Queues (both incoming and outgoing) for XCMP messages. This pallet does not actually receive or send messages. Its responsibility is to place the incoming and outgoing XCMP messages in their respective queues and manage these queues. +* [pallet_xcm](https://docs.rs/pallet-xcm/6.0.0/pallet_xcm/#) is responsible for filtering, routing, and executing incoming XCM. +* [cumulus_pallet_xcm](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcm/index.html#) is responsible from detecting and ensuring whether XCM’s are coming from **Relay** or **Sibling** chain. +* [MessageQueue](https://docs.rs/pallet-message-queue/latest/pallet_message_queue/#) provides generalized message queuing and processing capabilities on a per-queue basis for arbitrary use-cases. + +
+ +### EVM + +* [Ethereum](https://docs.rs/pallet-ethereum/latest/pallet_ethereum/#) works together with EVM pallet to provide full emulation for Ethereum block processing. +* [EVM](https://docs.rs/pallet-evm/5.0.0/pallet_evm/#) allows unmodified EVM code to be executed in a Substrate-based blockchain. +* [BaseFee](https://github.com/polkadot-evm/frontier/blob/master/frame/base-fee/src/lib.rs#) dynamically adjusts the `BaseFeePerGas` based on elasticity (zero elasticity means constant `BaseFeePerGas`). +* [EvmChainId](https://github.com/polkadot-evm/frontier/blob/master/frame/evm-chain-id/src/lib.rs#) stores the numeric Ethereum-style chain id in the runtime. This pallet can simplify setting up multiple networks with different chain ID by configuring the chain spec without requiring changes to the runtime config. diff --git a/docs/content/substrate-runtimes/2.0.2/runtimes/generic.mdx b/docs/content/substrate-runtimes/2.0.2/runtimes/generic.mdx new file mode 100644 index 00000000..351d8cae --- /dev/null +++ b/docs/content/substrate-runtimes/2.0.2/runtimes/generic.mdx @@ -0,0 +1,117 @@ +--- +title: Generic Runtime +--- + +## Purpose + +Generic Runtime Template is constructed with the purpose of providing the most generic template that is working out of the box. + +The pallet list is constructed by the contributions of Parity, the community, and OpenZeppelin. + +Our motivation for crafting the pallet list was to be as minimalistic as possible, +whilst preserving the most important pallets that are used in the Polkadot ecosystem. + +We designed this template to be generic, so that it can be used as a starting point for any other project/template, +and we aim to base our future templates off of this one. + +To demystify the hard concepts, we provided a documentation, in which you can find: + +* Explanation of the pallets that are used in this template (purpose, terminology, configuration, dispatchables, etc.). +* General guides regarding this template. +* Runtime related guides. +* And miscellaneous topics. + +## Configuration + +### System Support +
+click to expand + + +* [frame_system](https://paritytech.github.io/polkadot-sdk/master/frame_system/index.html#) is responsible from creating the runtime, initializing the storage, and providing the base functionality for the runtime. +* [cumulus_pallet_parachain_system](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_parachain_system/index.html#) handles low-level details of being a parachain. +* [pallet_timestamp](https://paritytech.github.io/polkadot-sdk/master/pallet_timestamp/index.html#) provides a way for consensus systems to set and check the onchain time. +* [parachain_info](https://docs.rs/staging-parachain-info/latest/staging_parachain_info/index.html#) provides a way for parachains to report their parachain id and the relay chain block number. +* [pallet_proxy](https://docs.rs/pallet-proxy/latest/pallet_proxy/#) enables delegation of rights to execute certain call types from one origin to another. +* [pallet_utility](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/index.html#) contains two basic pieces of functionality: + * Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a single dispatch. This can be useful to amalgamate proposals, combining `set_code` with corresponding `set_storage`s, for efficient multiple payouts with just a single signature verify, or in combination with one of the other two dispatch functionality. + * [force_batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.force_batch): Sends a batch of dispatch calls. Errors are allowed and won’t interrupt + * [batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch): Sends a batch of dispatch calls. This will return `Ok` in all circumstances. To determine the success of the batch, an event is deposited. If a call failed and the batch was interrupted, then the `BatchInterrupted` event is deposited, along with the number of successful calls made and the error of the failed call. If all were successful, then the `BatchCompleted` event is deposited. + * [batch_all](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch_all): Send a batch of dispatch calls and atomically execute them. The whole transaction will rollback and fail if any of the calls failed. + * Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from an alternative signed origin. Each account has 2 * 2**16 possible “pseudonyms” (alternative account IDs) and these can be stacked. This can be useful as a key management tool, where you need multiple distinct accounts (e.g. as controllers for many staking accounts), but where it’s perfectly fine to have each of them controlled by the same underlying keypair. Derivative accounts are, for the purposes of proxy filtering considered exactly the same as the origin and are thus hampered with the origin’s filters. +* [pallet_multisig](https://docs.rs/pallet-multisig/latest/pallet_multisig/#) enables multi-signature operations in your runtime. This module allows multiple signed origins (accounts) to coordinate and dispatch a call. For the call to execute, the threshold number of accounts from the set (signatories) must approve it. +* [pallet_scheduler](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/#) schedules runtime calls. +* [preimage](https://docs.rs/pallet-preimage/latest/pallet_preimage/#) allows for the users and the runtime to store the preimage of a hash on chain. This can be used by other pallets for storing and managing large byte-blobs. + +
+ +### Monetary +
+click to expand + + +* [pallet_balances](https://docs.rs/pallet-balances/latest/pallet_balances/#) provides functions for: + * Getting and setting free balances. + * Retrieving total, reserved and unreserved balances. + * Repatriating a reserved balance to a beneficiary account that exists. + * Transferring a balance between accounts (when not reserved). + * Slashing an account balance. + * Account creation and removal. + * Managing total issuance. + * Setting and managing locks. +* [pallet_transaction_payment](https://docs.rs/pallet-transaction-payment/latest/pallet_transaction_payment/#) provides the basic logic needed to pay the absolute minimum amount needed for a transaction to be included. This includes: + * **base fee**: This is the minimum amount a user pays for a transaction. It is declared as a base **weight** in the runtime and converted to a fee using `WeightToFee`. + * **weight fee**: A fee proportional to amount of weight a transaction consumes. + * **length fee**: A fee proportional to the encoded length of the transaction. + * **tip**: An optional tip. Tip increases the priority of the transaction, giving it a higher chance to be included by the transaction queue. +* [pallet_assets](https://paritytech.github.io/polkadot-sdk/master/pallet_assets/index.html#) deals with sets of assets implementing fungible traits, via [fungibles] traits in a simple, secure manner. This pallet makes heavy use of concepts such as Holds and Freezes from the [frame_support::traits::fungible] traits, therefore you should read and understand those docs as a prerequisite to understanding this pallet. +* [pallet_treasury](https://docs.rs/pallet-treasury/latest/pallet_treasury/#) provides a “pot” of funds that can be managed by stakeholders in the system and a structure for making spending proposals from this pot. + +
+ +### Governance +
+click to expand + + +* [pallet_sudo](https://docs.rs/pallet-sudo/latest/pallet_sudo/#) provides a way to execute privileged runtime calls using a specified sudo (“superuser do”) account. +* [pallet_conviction_voting](https://docs.rs/pallet-conviction-voting/latest/pallet_conviction_voting/#) manages actual voting in polls +* [pallet_referenda](https://docs.rs/pallet-referenda/latest/pallet_referenda/#) executes referenda. No voting logic is present here, and the Polling and PollStatus traits are used to allow the voting logic (likely in a pallet) to be utilized. +* [pallet_custom_origins](https://github.com/OpenZeppelin/polkadot-runtime-templates/blob/main/evm-template/runtime/src/configs/governance/origins.rs#) allows custom origins for governance. // TODO: double check this, is it really our own pallet, or just copy paste? +* [pallet_whitelist](https://docs.rs/pallet-whitelist/latest/pallet_whitelist/index.html#) allows some configurable origin: `Config::WhitelistOrigin` to whitelist some hash of a call, and allows another configurable origin: `Config::DispatchWhitelistedOrigin` to dispatch them with the root origin. + +
+ +### Collator support +
+click to expand + + +* [pallet_authorship](https://docs.rs/pallet-authorship/latest/pallet_authorship/#) provides authorship tracking for FRAME runtimes. This tracks the current author of the block and recent uncles. +* [pallet_collator_selection](https://paritytech.github.io/polkadot-sdk/master/pallet_collator_selection/index.html#) - manages the collators of a parachain. ***Collation is *not** a secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT safety assumptions of the chosen set. This pallet can: + * set invulnerable candidates (fixed candidates) + * set desired candidates (ideal number of non-fixed) + * set candidacy bond + * remove invulnerability (turn candidate into not fixed) + * and many more (all related to collators) +* [pallet_session](https://paritytech.github.io/polkadot-sdk/master/pallet_session/index.html#) allows validators to manage their session keys, provides a function for changing the session length, and handles session rotation. +* [pallet_aura](https://docs.rs/pallet-aura/latest/pallet_aura/#) extends Aura consensus by managing offline reporting. It can: + * get the current slot + * get the slot duration + * change and initialize authorities + * ensure the correctness of the state of this pallet +* [cumulus_pallet_aura_ext](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_aura_ext/index.html#) extends the Substrate AuRa pallet to make it compatible with parachains. It provides the Pallet, the Config and the GenesisConfig. + +
+ +### XCM Helpers +
+click to expand + + +* [cumulus_pallet_xcmp_queue](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/index.html#) Responsible for the Queues (both incoming and outgoing) for XCMP messages. This pallet does not actually receive or send messages. Its responsibility is to place the incoming and outgoing XCMP messages in their respective queues and manage these queues. +* [pallet_xcm](https://docs.rs/pallet-xcm/6.0.0/pallet_xcm/#) is responsible for filtering, routing, and executing incoming XCM. +* [cumulus_pallet_xcm](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcm/index.html#) is responsible from detecting and ensuring whether XCM’s are coming from **Relay** or **Sibling** chain. +* [MessageQueue](https://docs.rs/pallet-message-queue/latest/pallet_message_queue/#) provides generalized message queuing and processing capabilities on a per-queue basis for arbitrary use-cases. + +
diff --git a/docs/content/substrate-runtimes/changelog.mdx b/docs/content/substrate-runtimes/changelog.mdx new file mode 100644 index 00000000..86a8acea --- /dev/null +++ b/docs/content/substrate-runtimes/changelog.mdx @@ -0,0 +1,200 @@ +--- +title: Changelog +--- + + +# [v4.0.0](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v4.0.0) - 2025-08-01 + +## What's Changed +* December 2024 Audit by [@4meta5](https://github.com/4meta5) in [#400](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/400) +* Improve Fuzzing runs by [@KitHat](https://github.com/KitHat) in [#402](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/402) +* Fix missing protoc in fuzzing run by [@KitHat](https://github.com/KitHat) in [#403](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/403) +* Support paying DOT as a fee by [@KitHat](https://github.com/KitHat) in [#404](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/404) +* fix: set `with_properties` in generic-template by [@AlexD10S](https://github.com/AlexD10S) in [#406](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/406) +* Upgrade evm-template to stable2412 by [@4meta5](https://github.com/4meta5) in [#407](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/407) +* Upgrade generic-template to polkadot-stable2503 by [@4meta5](https://github.com/4meta5) in [#414](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/414) +* Upgrade evm-template to polkadot-stable2503 by [@4meta5](https://github.com/4meta5) in [#417](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/417) + +**Full Changelog**: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v3.0.0...v4.0.0 + +[Changes][v4.0.0] + + + +# [v3.0.0](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v3.0.0) - 2024-12-20 + +## What's Changed +* Add the option to sync faster with Rococo to the docs by [@ggonzalez94](https://github.com/ggonzalez94) in [#249](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/249) +* Fix MaxLockers by [@KitHat](https://github.com/KitHat) in [#248](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/248) +* Add sccache and use nextest in our workflow by [@ggonzalez94](https://github.com/ggonzalez94) in [#265](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/265) +* Merge v2 branch back to main by [@ggonzalez94](https://github.com/ggonzalez94) in [#273](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/273) +* Add benchmark tests to CI by [@KitHat](https://github.com/KitHat) in [#243](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/243) +* Fuzz Testing Integration by [@KitHat](https://github.com/KitHat) in [#275](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/275) +* fix typos in testing with zombienet guide by [@ozgunozerk](https://github.com/ozgunozerk) in [#276](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/276) +* Remove security warning for EVM template and fix broken link in docs by [@ggonzalez94](https://github.com/ggonzalez94) in [#281](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/281) +* Allow dependabod to automatically open PRs by [@ggonzalez94](https://github.com/ggonzalez94) in [#279](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/279) +* Fix broken CI run on main by [@KitHat](https://github.com/KitHat) in [#283](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/283) +* fix: rename templates binaries in the zombienet files by [@AlexD10S](https://github.com/AlexD10S) in [#290](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/290) +* fix zombienet scripts for accountid20 compatibility by [@ozgunozerk](https://github.com/ozgunozerk) in [#292](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/292) +* disable code coverage for now by [@ozgunozerk](https://github.com/ozgunozerk) in [#297](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/297) +* Add needs triage issue label for new issues by [@ggonzalez94](https://github.com/ggonzalez94) in [#304](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/304) +* Fix typos and add GH action that checks them by [@ggonzalez94](https://github.com/ggonzalez94) in [#311](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/311) +* [#302](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/302) Fix Weekly Fuzzing Run by [@KitHat](https://github.com/KitHat) in [#310](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/310) +* Upgrade polkadot sdk v1.13.0 by [@ozgunozerk](https://github.com/ozgunozerk) in [#298](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/298) +* Upgrade to `polkadot-stable2407-1` from `v1.13.0` by [@ozgunozerk](https://github.com/ozgunozerk) in [#299](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/299) +* Fix workflow versions by [@KitHat](https://github.com/KitHat) in [#317](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/317) +* Fix AFL build for fuzzing run by [@KitHat](https://github.com/KitHat) in [#323](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/323) +* Use `construct runtime v2` by [@ozgunozerk](https://github.com/ozgunozerk) in [#312](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/312) +* [#305](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/305) Fixing unexpectedly high weights for XCM pallet by [@KitHat](https://github.com/KitHat) in [#322](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/322) +* use asset hub as reserve by [@ozgunozerk](https://github.com/ozgunozerk) in [#327](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/327) +* merge changes/fixes from `v2` back into `main` by [@ozgunozerk](https://github.com/ozgunozerk) in [#333](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/333) +* Generic asset manager by [@ozgunozerk](https://github.com/ozgunozerk) in [#341](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/341) +* Implement CheckedMetadataHashExtension by [@4meta5](https://github.com/4meta5) in [#211](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/211) +* xtokens config by [@ozgunozerk](https://github.com/ozgunozerk) in [#331](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/331) +* xtokens on generic by [@ozgunozerk](https://github.com/ozgunozerk) in [#343](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/343) +* xcm-transactor on evm by [@4meta5](https://github.com/4meta5) in [#345](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/345) +* xcm-transactor on generic by [@4meta5](https://github.com/4meta5) in [#346](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/346) +* Benchmarks update for release by [@KitHat](https://github.com/KitHat) in [#355](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/355) +* Pallet grouping macros by [@4meta5](https://github.com/4meta5) in [#300](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/300) +* Reference for macros usage by [@KitHat](https://github.com/KitHat) in [#354](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/354) +* fix antora version by [@KitHat](https://github.com/KitHat) in [#360](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/360) +* Tanssi integration by [@KitHat](https://github.com/KitHat) in [#361](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/361) +* Nonzero minimum period by [@4meta5](https://github.com/4meta5) in [#380](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/380) +* Release 3.0.0 Preparation by [@4meta5](https://github.com/4meta5) in [#385](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/385) + +## New Contributors +* [@AlexD10S](https://github.com/AlexD10S) made their first contribution in [#290](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/290) + +**Full Changelog**: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v2.0.3...v3.0.0 + +[Changes][v3.0.0] + + + +# [v2.0.3](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v2.0.3) - 2024-10-31 + + + +This is a hot fix release that ensures compatibility with both polkadot and ethereum compatibility for our EVM parachain. + +### What's New: + +- decided to use `rococo-local` for the zombienet for now, until `paseo-local` becomes available on polkadot-sdk [#352](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/352) + + +[Changes][v2.0.3] + + + +# [v2.0.2](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v2.0.2) - 2024-10-30 + +This is a hot fix release that ensures compatibility with both polkadot and ethereum compatibility for our EVM parachain. + +### What's New: + +- use `Alith`, `Baltathar`, etc. (evm compatible 20-byte accounts) in our chain spec +- fix chain spec properties for evm compatibility in polkadot ecosystem +- merge `impl_runtime_apis` macro back into `lib.rs`, since this is causing metadata problems with polkadot.js (we will inspect this later and try to find a way to separate it back to its own file) + +Now, our EVM template works in both polkadot setting (32-bytes) and in EVM setting (20-bytes). + +Related PR: use evm compatible accounts, and fix properties for chain spec ([#326](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/326)) + +[Changes][v2.0.2] + + + +# [v2.0.1](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v2.0.1) - 2024-09-13 + +This is a hot fix release that improves zombienet compatibility for our evm template. +Basically adds these 2 fixes on top of the previous release: +- [#292](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/292) +- [#290](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/290) + +Thanks to [@AlexD10S](https://github.com/AlexD10S) for his contributions. + +## What's Changed +* zombienet fixes by [@ozgunozerk](https://github.com/ozgunozerk) and [@AlexD10S](https://github.com/AlexD10S) in [#313](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/313) + + +**Full Changelog**: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v2.0.0...v2.0.1 + +[Changes][v2.0.1] + + + +# [v2.0.0](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v2.0.0) - 2024-08-02 + +# New: EVM Runtime Template + +This release is audited by SRLabs, you can find the audit report [here](https://github.com/OpenZeppelin/polkadot-runtime-templates/blob/v2/audits/2024-07.pdf). + + +## What's Changed +* add zombienet configuration by [@Moliholy](https://github.com/Moliholy) in [#129](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/129) +* Configure OpenGov by [@4meta5](https://github.com/4meta5) in [#130](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/130) +* Add benchmarking compilation check to CI by [@4meta5](https://github.com/4meta5) in [#131](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/131) +* Fix ci by [@ozgunozerk](https://github.com/ozgunozerk) in [#133](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/133) +* [#97](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/97) added async backing to runtime and node by [@KitHat](https://github.com/KitHat) in [#102](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/102) +* merge v1 into main by [@ozgunozerk](https://github.com/ozgunozerk) in [#175](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/175) +* clearer constant namings and explanations by [@ozgunozerk](https://github.com/ozgunozerk) in [#151](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/151) +* Remove security warning from docs and fix style in list by [@ggonzalez94](https://github.com/ggonzalez94) in [#178](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/178) +* Treasury docs by [@ozgunozerk](https://github.com/ozgunozerk) in [#152](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/152) +* Update index - fix chainspec generation command by [@DrW3RK](https://github.com/DrW3RK) in [#181](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/181) +* Transition into monorepo by [@ozgunozerk](https://github.com/ozgunozerk) in [#180](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/180) +* workflow fix by [@ozgunozerk](https://github.com/ozgunozerk) in [#182](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/182) +* Integrated EVM and Ethereum pallets by [@KitHat](https://github.com/KitHat) in [#196](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/196) +* readme and docs update for monorepo by [@ozgunozerk](https://github.com/ozgunozerk) in [#183](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/183) +* Update README.md by [@DrW3RK](https://github.com/DrW3RK) in [#204](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/204) +* [#188](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/188) Added EVM RPC to the node by [@KitHat](https://github.com/KitHat) in [#198](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/198) +* Upgrade evm dependencies by [@ozgunozerk](https://github.com/ozgunozerk) in [#208](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/208) +* [#188](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/188) Added a guide for contract migration by [@KitHat](https://github.com/KitHat) in [#200](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/200) +* Runtime lib restructure by [@ozgunozerk](https://github.com/ozgunozerk) in [#202](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/202) +* Precompiles by [@4meta5](https://github.com/4meta5) in [#201](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/201) +* add property to node for each template by [@ozgunozerk](https://github.com/ozgunozerk) in [#221](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/221) +* [#189](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/189) Predeployed Contracts functionality by [@KitHat](https://github.com/KitHat) in [#218](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/218) +* fixed "missing host function" bug by [@KitHat](https://github.com/KitHat) in [#225](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/225) +* Disable logging for deployment builds by [@4meta5](https://github.com/4meta5) in [#213](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/213) +* Add pov reclaim by [@ozgunozerk](https://github.com/ozgunozerk) in [#229](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/229) +* [evm-template] XCM config for foreign assets by [@4meta5](https://github.com/4meta5) in [#219](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/219) +* Port the trivial audit fixes to evm by [@ozgunozerk](https://github.com/ozgunozerk) in [#236](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/236) +* [#235](https://github.com/OpenZeppelin/polkadot-runtime-templates/issues/235) Updated benchmarks for the next release by [@KitHat](https://github.com/KitHat) in [#238](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/238) +* Feemanager for evm by [@ozgunozerk](https://github.com/ozgunozerk) in [#239](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/239) +* Port docs to sync faster with Rococo to our audit branch by [@ggonzalez94](https://github.com/ggonzalez94) in [#252](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/252) +* Max remote lock consumers by [@ozgunozerk](https://github.com/ozgunozerk) in [#255](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/255) +* RPC Differences Documentation and RPC Testing Toolkit by [@KitHat](https://github.com/KitHat) in [#241](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/241) +* Fix for treasury config and benchmark by [@KitHat](https://github.com/KitHat) in [#240](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/240) +* Bound GasLimitPovRatio to u64::MAX by [@KitHat](https://github.com/KitHat) in [#246](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/246) +* Docs Update by [@KitHat](https://github.com/KitHat) in [#250](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/250) +* Update docs by [@ozgunozerk](https://github.com/ozgunozerk) in [#261](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/261) +* HRMP Channel Guide by [@4meta5](https://github.com/4meta5) in [#214](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/214) +* Fix XCM constants by [@KitHat](https://github.com/KitHat) in [#268](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/268) +* Fix `txpool` feature for EVM template by [@KitHat](https://github.com/KitHat) in [#270](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/270) +* Bump versions by [@ozgunozerk](https://github.com/ozgunozerk) in [#271](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/271) + +## New Contributors +* [@Moliholy](https://github.com/Moliholy) made their first contribution in [#129](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/129) +* [@DrW3RK](https://github.com/DrW3RK) made their first contribution in [#181](https://github.com/OpenZeppelin/polkadot-runtime-templates/pull/181) + +**Full Changelog**: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v1.0.0...v2.0.0 + +[Changes][v2.0.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/polkadot-runtime-templates/releases/tag/v1.0.0) - 2024-04-26 + +Generic Runtime Template for Polkadot Parachains. + +This release is audited by SRLabs, you can find the audit report [here](https://github.com/OpenZeppelin/polkadot-generic-runtime-template/blob/v1-after-audit/audits/2024-04.pdf) + +[Changes][v1.0.0] + + +[v4.0.0]: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v3.0.0...v4.0.0 +[v3.0.0]: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v2.0.3...v3.0.0 +[v2.0.3]: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v2.0.2...v2.0.3 +[v2.0.2]: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v2.0.1...v2.0.2 +[v2.0.1]: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v2.0.0...v2.0.1 +[v2.0.0]: https://github.com/OpenZeppelin/polkadot-runtime-templates/compare/v1.0.0...v2.0.0 +[v1.0.0]: https://github.com/OpenZeppelin/polkadot-runtime-templates/tree/v1.0.0 diff --git a/docs/content/substrate-runtimes/glossary.mdx b/docs/content/substrate-runtimes/glossary.mdx new file mode 100644 index 00000000..ee01b857 --- /dev/null +++ b/docs/content/substrate-runtimes/glossary.mdx @@ -0,0 +1,185 @@ +--- +title: Glossary +--- + +## A + +### Announcement + +Announcement is a statement of call hash for the [proxy](#proxy) to execute in some future block. Required for some proxies where delay is specified. + +### Asset Teleportation + +Asset Teleportation is a movement of an asset by destroying it on one side and creating a clone on the other side + +## B + +### Barrier + +A generic filter which returns whether or not XCM may pass and be executed. If they do not pass the barrier, they are not executed. The barrier is the final filter before XCM execution. It implements the `ShouldExecute` trait and therefore takes as input an XCM and returns a bool indicating whether it should be executed. + +### Burning + +The process of destroying existing assets. + +## C + +### Candidacy Bond + +Candidacy bond is fixed amount to deposit to become a [candidate](#candidate). + +### Candidate + +Candidate is a self-promoted [collator](#collator), who deposited a candidacy bond to participate in collation process + +### Collator + +Collator is a node that gathers collation information and communicates it to relay chain to make a block. + +## D + +### Delay + +Delay is number of block that should pass from [announcement](#announcement) before call execution. + +### Delegatee + +Delegatee is an account that was granted call execution rights with [proxy](#proxy) creation. + +### Delegator + +Delegator is an account that granted call execution rights with [proxy](#proxy) creation. + +## F + +### Freeze + +`Freeze` is reserving some amount from the account. For example, to make a transaction, the amount to be sent might be first frozen, then the checks for the transaction will be made. If the checks pass, the amount will be deducted from the sender’s account and added to the recipient’s. `Freeze` should be preferred if the reserved money can be lost/sent due to external reasons. + +### Fungible Asset + +An asset where any unit of it worth the same. Opposite of [nonfungible_asset](#nonfungible_asset) + +### NonFungible Asset + +An asset meaning that it is not interchangeable with other assets of the same type. For example, a non-fungible asset could be an NFT, while a fungible asset could be a currency token. + +## H + +### Hold +`Hold` means reserving/holding an amount of balance from an account. The `Hold` amount cannot be spent, but still belongs to the account. For example, depositing modifying the state, and occupying space. This deposit will be repaid once the occupied space is freed. + +## I + +### Invulnerable + +Invulnerable is a permissioned [collator](#collator), that will always be part of the collation process + +## J + +### Junction + +Junction is a single item in a path to describe a relative location (think of `dir` or `dir/$file_path` as a junction). + +### Junctions + +Junctions is multiple `Junction`s, formed only through [multilocation](#multilocation) construction. + +## L + +### Length Fee + +A fee proportional to the encoded length of the transaction. + +### Lock + +Nearly the same as `[Hold](#hold)`, with 2 major differences: + +* Locks can store the reason for the locking operation. +* Locks are overlapping, whereas Reserve/Hold’s are additive. Example: + * Apply `Hold` to account A for 20 units, then apply another `Hold` to account A for 10 units -> a total of 30 units will be reserved/hold + * Apply `Lock` to account A for 20 units, then apply another `Lock` to account A for 10 units -> a total of 20 units will be locked, since 10 is smaller than 20. + +## M + +### Minting + +The process of creating new assets. + +### MultiAsset + +Either an amount of a single fungible asset, or one non-fungible asset. + +### MultiLocation + +A path described by junctions leading to the location. + +## N + +### NonFungible Asset + +An asset where each unit is worth a different amount and/or is unique in some way. Opposite of [fungible_asset](#fungible_asset). + +## P + +### Pot + +Pot is a stake that will reward block authors. Block author will get half of the current stake. + +### Proxy + +Proxy is a statement of call execution rights transfer from [delegator](#delegator) to [delegatee](#delegatee). Specified by [proxy_type](#proxy_type) and [delay](#delay). + +### Proxy type + +Proxy type is a type of calls that can be executed using this [proxy](#proxy). + +### Pure account + +Pure account is an account that was spawned only to be a [delegatee](#delegatee) for some [proxy](#proxy). + +## R + +### Reserve + +Deprecated, use `[Hold](#hold)`. + +### Reserve Asset Transfer + +When consensus systems do not have a established layer of trust over which they can transfer assets, they can opt for a trusted 3rd entity to store the assets + +## T + +### Thawing + +The process of unfreezing an asset after being frozen. + +### Tip + +An optional tip. Tip increases the priority of the transaction, giving it a higher chance to be included by the transaction queue. + +## U + +### Unincluded Segment + +A sequence of blocks that were not yet included into the relay chain state transition + +## V + +### Validation Code + +Validation Code is the runtime binary that runs in the parachain + +### Validation Data + +Validation Data is the information passed from the relay chain to validate the next block + +## W + +### Weight + +The time it takes to execute runtime logic. By controlling the execution time that a block can consume, weight bounds the storage changes and computation per block. + +### Weight Fee + +A fee proportional to amount of [weight](#weight) a transaction consumes. diff --git a/docs/content/substrate-runtimes/guides/async_backing.mdx b/docs/content/substrate-runtimes/guides/async_backing.mdx new file mode 100644 index 00000000..9248a1ec --- /dev/null +++ b/docs/content/substrate-runtimes/guides/async_backing.mdx @@ -0,0 +1,34 @@ +--- +title: Async Backing +--- + +Async backing is a feature of parachains that allow to shorten the block time, get more execution time and speed up transaction inclusion in general. You can read in details what async backing is in the [general guide](https://wiki.polkadot.network/docs/learn-async-backing#candidate-receipt) and in the [update guide](https://wiki.polkadot.network/docs/maintain-guides-async-backing). In our generic template we have included async backing under a feature. This page describes how can you build and test the template with async backing enabled. + +## How to build + +Async backing is enabled by the feature `async-backing`. You can build the template with it like this: + +```bash +cargo build --release --features="async-backing" +``` + +## How to test + +You can always test it against Paseo (it should already have enabled async backing), but the fastest way is to test against a local relay chain. In our repository you can find a script, a config and a [manual](https://github.com/OpenZeppelin/polkadot-runtime-templates/tree/main/generic-template/zombienet-config) that will run both relay chain and a parachain. To launch a parachain with a relay chain, you will need to run these commands: + +* Get the Polkadot binaries: + * If you are using some Linux distro, you can download the binaries: + + ``` + ./scripts/zombienet.sh init + ``` + * Otherwise, you have to build it from scratch. It takes + + ``` + ./scripts/zombienet.sh build + ``` +* After that, launch the network: + + ``` + ./scripts/zombienet.sh devnet + ``` diff --git a/docs/content/substrate-runtimes/guides/contract_migration.mdx b/docs/content/substrate-runtimes/guides/contract_migration.mdx new file mode 100644 index 00000000..61c24771 --- /dev/null +++ b/docs/content/substrate-runtimes/guides/contract_migration.mdx @@ -0,0 +1,46 @@ +--- +title: Migrate a Contract from EVM chain +--- + +This EVM template is powered by Frontier. It allows you to deploy almost any Solidity contract you have following these steps. + +## Step 1: Prepare and run the chain + +This step is basic, you can use our [guide for generic template]. It is recommended to use [async backing](/substrate-runtimes/guides/async_backing) for better experience. +The only additional thing that you will need to specify is the EVM Chain Id in your chainspec. For that, you need: + +* During the "Edit the chainspec" step execute one additional step: + * Edit the `evmChainId.chainId` to the value of your choice. Try to select a value that has no existing EVM chain assigned to it. + +## Step 2: Set up the development environment + +For Solidity development you can use [Foundry](https://book.getfoundry.sh/getting-started/installation). Feel free to use any other toolkit like ethers.js or web3.py. + +With the chain running, to start the development you need only two values: a private key from the account with some tokens on it and a url from EVM RPC. + +### Private Key. +If you have formed your own chainspec, you should have some keys ready. Alternatively, you can generate the key using any wallet of your choice (e.g. Metamask) and get some tokens to it. By default, we have these keys for prefunded accounts in the chainspec: + +* Private: `0x94834ac72c525a539097542a738816e8d2d18a60d6460c25e82b306441635dc4` + Public: `0x33c7c88f2B2Fcb83975fCDB08d2B5bf7eA29FDCE` +* Private: `0x3c959c97d3b687af91c27b93bbe8deb2bb2148f01bd48126ae757e202ab625a0` + Public: `0xc02db867898f227416BCB6d97190126A6b04988A` + + +You must not use these keys for any production purpose + + +### Url for the EVM RPC. + +This part is simple. You can use the same url that you would use with the polkadot.js wallet -- it serves the EVM JSON-RPC as well. If you have followed our guide, you can use http://localhost:8844. + +## Step 3: Deploy your contract + +Optional: If you want to test any contract, you can use our [guide about the standard contracts](https://docs.openzeppelin.com/contracts/5.x/#foundry_git). + +When you are ready with the contract just use `forge` that you have installed in a previous step: +``` +forge create --rpc-url --private-key +``` + +Output of this command will contain the contract address. That’s it! diff --git a/docs/content/substrate-runtimes/guides/hrmp_channels.mdx b/docs/content/substrate-runtimes/guides/hrmp_channels.mdx new file mode 100644 index 00000000..3727c478 --- /dev/null +++ b/docs/content/substrate-runtimes/guides/hrmp_channels.mdx @@ -0,0 +1,145 @@ +--- +title: Sending Cross-Chain Messages between Parachains +--- + +The supported way to exchange cross-chain messages (XCM) between parachains is to use Horizontal Relay-routed Message Passing (HRMP) channels. + +Each HRMP channel is unidirectional. In order to enable full connectivity between two parachains, two HRMP channels must be opened: one for sending outgoing XCM and the other for receiving incoming XCM. + +## Opening an HRMP Channel + +Opening a channel between two parachains A and B takes 2 steps: +1. parachain A initiates a channel request +2. parachain B accepts the channel request + +For step (1), parachain A calls `hrmp > hrmpInitOpenChannel(recipient, proposedMaxCapacity, proposedMaxMessageSize)` to create a channel request with the input configuration. + +For step (2), parachain B calls `hrmp > hrmpAcceptOpenChannel(sender)` to accept the channel open request from the input sender. + +In order to dispatch a call from its sovereign origin, a parachain may use governance to send the encoded call in a Transact instruction to the Relay Chain, but it may also execute this logic autonomously (e.g. on the notification that a channel was requested). + +## Connecting to Ecosystem Parachains + +The examples in this section include steps to connect to existing parachains in the Polkadot ecosystem. Examples include the following Polkadot parachains: +1. AssetHub supports managing asset balances as well as the execution of cross-chain transfers. +2. Snowbridge supports sending ERC20 tokens between Polkadot parachains and Ethereum in both directions. +3. HydraDX supports DeFi functionality to provide liquidity to Polkadot. + +For all examples: +. `paraId` for the user’s parachain is set to `43211234`. +. `proposedMaxCapacity` and `proposedMaxMessageSize` are set to the values of Polkadot `config.hrmpChannelMaxCapacity = 1000` and `config.hrmpChannelMaxMessageSize = 102400`, respectively. + +### AssetHub + +AssetHub supports managing asset balances as well as the execution of cross-chain transfers. + +AssetHub is a common-good, system parachain and is therefore controlled by relay chain governance. This means it is sufficient to propose opening both channels at once through the relay chain’s `GeneralAdmin` track. The proposal should set its call (proposed to be executed) to `utility.batchAll` with the input including 2 calls to `hrmp.forceOpenHrmpChannel` to open a channel in each direction between the user parachain and AssetHub. + +The first call to `hrmp.forceOpenHrmpChannel` proposes opening a unidirectional channel to send XCM from the user parachain to AssetHub. If AssetHub’s paraId is set to `1000`, here are the inputs: +``` +hrmp.forceOpenChannel( + sender = 43211234, + recipient = 1000, + max_capacity = 1000, + max_message_size = 102400, +) +``` +Here is the second call to open a unidirectional channel to send XCM from AssetHub to the user parachain: +``` +hrmp.forceOpenChannel( + sender = 1000, + recipient = 43211234, + max_capacity = 1000, + max_message_size = 102400, +) +``` + +[Here](https://polkadot.subsquare.io/referenda/438) is a successful example of this proposal which passed to open 2 HRMP channels between Unique Network and AssetHub. [Here](https://polkadot.polkassembly.io/referenda/594) is another example of a proposal executed to open HRMP Channels between AssetHub and Mythos. + +### Snowbridge + +Snowbridge supports sending ERC20 tokens between Polkadot parachains and Ethereum in both directions. + +Snowbridge leverages AssetHub to mint ERC20 tokens received from Ethereum and send them to parachains. This implies that a prerequisite step for receiving ERC20 tokens via Snowbridge is opening HRMP channels with AssetHub by following the previous section. + +The standard way of interacting with Snowbridge is to make calls to the [Gateway](https://github.com/Snowfork/snowbridge/blob/main/contracts/src/interfaces/IGateway.sol) contract deployed on Ethereum at [this address](https://etherscan.io/address/0x27ca963C279c93801941e1eB8799c23f407d68e7). + +If an ERC20 token has already been bridged before, the user may send the following transaction to the Gateway to send ERC20 tokens to parachain `destinationChain` from Ethereum and deposit into account `destinationAddress` on that parachain. +```solidity, ignore +function sendToken(address token, ParaID destinationChain, MultiAddress destinationAddress, uint128 destinationFee, uint128 amount) + external + payable; +``` + +If the ERC20 tokens has not been bridged before, there is a prerequisite step to register the ERC20 token on AssetHub via the `ForeignAssets` pallet. To register ERC20 tokens for the first time, the user may send the following transaction to the Gateway: +```solidity, ignore +function registerToken(address token) external payable; +``` +The above function sends a message to the AssetHub parachain to register a new fungible asset in the `ForeignAssets` pallet. To account for delivery costs, the above function charges a fee in Ether which may be retrieved beforehand by calling `quoteRegisterFee`. + +For more information, see the [Snowbridge Docs](https://docs.snowbridge.network). For more information on the Snowbridge deployment to Polkadot, see the [governance proposal which initialized Snowbridge on BridgeHub and AssetHub](https://polkadot.polkassembly.io/referenda/680). + +### HydraDX + +HydraDX supports DeFi functionality to provide liquidity to Polkadot. + +The `Opening an HRMP Channel` section shows the general steps for opening two HRMP channels between any two parachains. + +To propose opening a channel to send XCM to HydraDX, the sending parachain may call: +``` +hrmp.hrmpInitOpenChannel( + recipient = 2034, + proposedMaxCapacity = 1000, + proposedMaxMessageSize = 102400, +) +``` + +HydraDX may accept the channel open request from the sending parachain with paraID 43211234 by calling: +``` +hrmpAcceptOpenChannel( + sender = 43211234 +) +``` + +HydraDX may call the following to propose opening a channel to send XCM from HydraDX to the parachain with paraID 43211234: +``` +hrmp.hrmpInitOpenChannel( + recipient = 43211234, + proposedMaxCapacity = 1000, + proposedMaxMessageSize = 102400, +) +``` + +Assuming the HydraDX has paraID 2034, the receiving parachain may accept the channel open request by calling: +``` +hrmpAcceptOpenChannel( + sender = 2034 +) +``` + +Note that in order to dispatch a call from its sovereign origin, a parachain may use governance to send the encoded call in a Transact instruction to the Relay Chain, but it may also execute this logic autonomously (e.g. on the notification that a channel was requested). HRMP extrinsics often must be called from the parachain’s sovereign account as origin, often via a democracy proposal. + +[Here](https://moonbeam.polkassembly.network/referendum/93) is an example of a proposal on Moonbeam to Open/Accept HRMP channels with HydraDX. + +## Bonus: HRMP Channel Notification Handlers + +There are 3 handlers that may be configured as hooks to implement automated logic for when a `HRMP` notification is received: +. `HrmpChannelAcceptedHandler` +. `HrmpChannelClosingHandler` +. `HrmpNewChannelOpenRequestHandler` + +Each follows a similar interface: +```rust +pub trait HandleHrmpNewChannelOpenRequest { + fn handle(sender: u32, max_message_size: u32, max_capacity: u32) -> XcmResult; +} + +pub trait HandleHrmpChannelAccepted { + fn handle(recipient: u32) -> XcmResult; +} + +pub trait HandleHrmpChannelClosing { + fn handle(initiator: u32, sender: u32, recipient: u32) -> XcmResult; +} +``` +The default implementation `()` returns `Ok(())` without executing any effects. Read more in the [Polkadot documentation](https://wiki.polkadot.network/docs/build-hrmp-channels). diff --git a/docs/content/substrate-runtimes/guides/pallet_abstractions.mdx b/docs/content/substrate-runtimes/guides/pallet_abstractions.mdx new file mode 100644 index 00000000..eebc6e2f --- /dev/null +++ b/docs/content/substrate-runtimes/guides/pallet_abstractions.mdx @@ -0,0 +1,145 @@ +--- +title: OpenZeppelin Pallet Abstractions +--- + +### Introduction + +[openzeppelin-pallet-abstractions](https://github.com/OpenZeppelin/openzeppelin-pallet-abstractions) is a Rust macro library designed to simplify and secure the configuration of Polkadot parachain runtimes. By reducing repetitive boilerplate and providing sensible defaults, the library helps developers configure their runtimes with fewer lines of code while ensuring flexibility for customizations. + +Note: This library has not been audited yet and should not be used in production environments. + +### 1. Installation +To start using the `openzeppelin-pallet-abstractions`, add it as a dependency in your Cargo.toml file: +```toml +openzeppelin-pallet-abstractions = { git = "https://github.com/OpenZeppelin/openzeppelin-pallet-abstractions", tag = "v0.1.0" } +``` +Then, import the required macros in your runtime configuration file. + +### 2. Basic Usage + +The library offers a collection of macros for configuring various core runtime elements such as system settings, assets, governance, consensus mechanisms, EVM integration, and XCM compatibility. Let’s walk through a basic example setup: + +##### 1. System configuration +The `impl_openzeppelin_system!` macro facilitates the setup of foundational runtime components by implementing the `SystemConfig` trait for your custom runtime structure. +```rust +use openzeppelin_pallet_abstractions::{impl_openzeppelin_system, SystemConfig}; + +pub struct OpenZeppelinRuntime; + +impl SystemConfig for OpenZeppelinRuntime { + type AccountId = AccountId; + type ExistentialDeposit = ConstU128; + type PreimageOrigin = EnsureRoot; + type ScheduleOrigin = EnsureRoot; + type Version = Version; + // Additional configurations... +} +impl_openzeppelin_system!(OpenZeppelinRuntime); +``` +This expands to configure multiple core pallets like `frame_system`, `pallet_timestamp`, `parachain_info`, and others essential for a functioning parachain runtime. + +##### 2. Additional configurations + +Further configuration can be achieved for pallets grouped by functionality, such as Assets, Consensus, EVM, Governance, and XCM. Each grouping has its own macro and configuration trait, making it easier to set up advanced components without redundant code. + +Example setup for additional configurations in an EVM-based runtime: +```rust +use openzeppelin_pallet_abstractions::{ + impl_openzeppelin_assets, impl_openzeppelin_consensus, impl_openzeppelin_evm, + impl_openzeppelin_governance, impl_openzeppelin_xcm, AssetsConfig, + ConsensusConfig, EvmConfig, GovernanceConfig, XcmConfig, +}; + +impl ConsensusConfig for OpenZeppelinRuntime { + type CollatorSelectionUpdateOrigin = CollatorSelectionUpdateOrigin; +} + +impl GovernanceConfig for OpenZeppelinRuntime { + type ConvictionVoteLockingPeriod = ConstU32<{ 7 * DAYS }>; + type DispatchWhitelistedOrigin = EitherOf, WhitelistedCaller>; + // Additional configurations... +} + +impl_openzeppelin_assets!(OpenZeppelinRuntime); +impl_openzeppelin_consensus!(OpenZeppelinRuntime); +impl_openzeppelin_governance!(OpenZeppelinRuntime); +impl_openzeppelin_xcm!(OpenZeppelinRuntime); +impl_openzeppelin_evm!(OpenZeppelinRuntime); +``` + +##### 3. Advanced Configuration Options + +Each macro allows optional parameters to override the default values. This enables fine-tuning of runtime settings according to the specific needs of your parachain while retaining the benefits of standardized, secure defaults. + +Default Overrides: Customize specific parameters like `ExistentialDeposit`, `BaseXcmWeight`, or `AssetId` while relying on default values for other settings. +Custom Implementations: Integrate custom types (like `EitherOf`, `ConstU32`) or origins (`EnsureRoot`, `WhitelistedCaller`) for advanced use cases. + +##### 4. Security Considerations + +While the library is built with security in mind, it’s essential to review each configuration for your specific runtime and context. + +##### 5. Contributing and Feedback +We encourage contributions from the community to improve the library. Please refer to the [CONTRIBUTING.md](https://github.com/OpenZeppelin/openzeppelin-pallet-abstractions/blob/main/CONTRIBUTING.MD) for guidelines. + +### 3. Using Procedural Macros in OpenZeppelin Pallet Abstractions + +#### 1. `construct_runtime!` Macro + +The `construct_runtime!` macro simplifies the assembly of runtime modules by abstracting over both OpenZeppelin abstractions and standard pallets. This is useful for developers aiming to build a runtime quickly, without manually managing each pallet’s configuration. + +##### Usage Example + +To use the macro, annotate a module with `#[openzeppelin_construct_runtime]` and define structs for each desired abstraction or regular pallet. Here’s a basic example: +```rust +#[openzeppelin_construct_runtime] +mod runtime { + #[abstraction] + struct System; // Available abstractions: System, Consensus, XCM, Assets, Governance, EVM. + #[pallet] + type Pallet = pallet_crate; // Regular pallets defined with `#[pallet]` mimic the `construct_runtime!` structure. +} +``` +Note: Pallet index assignments are handled automatically by this macro. If you require explicit control over pallet indices, please consider submitting a [feature request](https://github.com/OpenZeppelin/openzeppelin-pallet-abstractions/issues). + +##### Supported Abstractions and their Pallets: +* System -- frame_system, pallet_timestamp, parachain_info, pallet_scheduler, pallet_preimage, pallet_proxy, pallet_balances, pallet_utility, cumulus_pallet_parachain_system, pallet_multisig, pallet_session +* Assets -- pallet_assets, pallet_transaction_payment, pallet_asset_manager +* Consensus -- pallet_authorship, pallet_aura, cumulus_pallet_aura_ext, pallet_collator_selection +* Governance -- pallet_sudo, pallet_treasury, pallet_conviction_voting, pallet_whitelist, pallet_custom_origins, pallet_referenda +* XCM -- pallet_message_queue, cumulus_pallet_xcmp_queue, pallet_xcm, cumulus_pallet_xcm, pallet_xcm_transactor, orml_xtokens, pallet_xcm_weight_trader +* EVM -- pallet_ethereum, pallet_evm, pallet_base_fee, pallet_evm_chain_id, pallet_erc20_xcm_bridge + +#### 2. `impl_runtime_apis!` Macro + +The `The impl_runtime_apis!` macro provides a clean interface for implementing runtime APIs. This macro reduces boilerplate by allowing you to specify types and structs, automatically generating the required API implementations. + +##### Usage Example + +To use this macro, annotate a module with `#[openzeppelin_runtime_apis]` and define the types and abstractions needed. + +```rust +#[openzeppelin_runtime_apis] +mod apis { + type Runtime = Runtime; // The runtime generated by `construct_runtime!` + type Block = Block; // Block type required by all abstractions + + #[abstraction] + mod assets { + type TransactionPayment = TransactionPayment; + type RuntimeCall = RuntimeCall; + type Balance = Balance; + } + + // Additional `impl` blocks can be added as necessary. +} +``` + +##### Supported Abstractions, APIs, and Required Configurations: +| Abstraction name | Implemented APIs | Required configs | +| --- | --- | --- | +| EVM | • `fp_rpc::EthereumRuntimeRPCApi`• `fp_rpc::ConvertTransactionRuntimeApi` | • `RuntimeCall` -- runtime call generated by `construct_runtime` macro• `Executive` -- `frame_executive::Executive` specification used by parachain system• `Ethereum` -- `pallet_ethereum` pallet struct generated by `construct_runtime` macro | +| assets | • `pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi`• `pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi` | • `TransactionPayment` -- `pallet_transaction_payment` struct pallet generated by `construct_runtime` macro• `RuntimeCall` -- runtime call generated by `construct_runtime` macro• `Balance` -- type used for balance specification (e.g., in `pallet_balances` config) | +| consensus | • `sp_consensus_aura::AuraApi`• `sp_session::SessionKeys`• `cumulus_primitives_aura::AuraUnincludedSegmentApi` (if `async-backing` feature is enabled) | • `SessionKeys` -- struct generated by `impl_opaque_keys` macro• `Aura` -- `pallet_aura` struct pallet generated by `construct_runtime` macro (only if `async-backing` feature is not enabled)• `SlotDuration` -- constant used for slot duration definition (only if `async-backing` feature is enabled)• `ConsensusHook` -- type used in `cumulus_pallet_parachain_system::Config::ConsensusHook` (only if `async-backing` feature is enabled) | +| system | • `sp_api::Core`• `sp_api::Metadata`• `sp_block_builder::BlockBuilder`• `sp_transaction_pool::runtime_api::TaggedTransactionQueue`• `sp_offchain::OffchainWorkerApi`• `frame_system_rpc_runtime_api::AccountNonceApi`• `cumulus_primitives_core::CollectCollationInfo`• `frame_try_runtime::TryRuntime` (under a `try-runtime` feature)• `sp_genesis_builder::GenesisBuilder` | • `Executive` -- `frame_executive::Executive` specification used by parachain system• `System` -- `frame_system` pallet struct generated by `construct_runtime` macro• `ParachainSystem` -- `cumulus_pallet_parachain_system` pallet struct generated by `construct_runtime` macro• `RuntimeVersion` -- runtime version, generated by `sp_version::runtime_version`• `AccountId` -- account id type specified in `frame_system::Config`• `Nonce` -- nonce type specified in `frame_system::Config`• `RuntimeGenesisConfig` -- type generated by `construct_runtime` macro• `RuntimeBlockWeights` -- type implementing `Get`, often built by `BlockWeights::builder` | +| benchmarks | • `frame_benchmarking::Benchmark` (under `runtime-benchmarks` feature) | • `Assets` -- `pallet_assets` pallet struct generated by `construct_runtime` macro• `AssetManager` -- `pallet_asset_manager` pallet struct generated by `construct_runtime` macro• `AssetType` -- struct describing foreign assets in XCM configuration• `RuntimeOrigin` -- type generated by `construct_runtime` macro• `RelayLocation` -- `Location` type pointing to the relaychain• `System` -- `frame_system` pallet struct generated by `construct_runtime` macro• `ParachainSystem` -- `cumulus_pallet_parachain_system` pallet struct generated by `construct_runtime` macro• `ExistentialDeposit` -- type describing existential deposit• `AssetId` -- type describing internal asset id• `XCMConfig` -- struct implementing `xcm_executor::Config`, generated by XCM abstraction as `XcmExecutorConfig`• `AccountId` -- account id type specified in `frame_system::Config`• `Cents` -- constant representing 1/100 of the native token• `FeeAssetId` -- asset for XCM fees, generated by XCM abstraction• `TransactionByteFee` -- fee per byte of data, generated by assets abstraction• `Address` -- type describing address format for accounts• `Balances` -- `pallet_balances` pallet struct generated by `construct_runtime` macro | +This macro allows for clear modularization of APIs, facilitating easier maintenance and extension of runtime functionalities. diff --git a/docs/content/substrate-runtimes/guides/pay_dot_as_a_fee.mdx b/docs/content/substrate-runtimes/guides/pay_dot_as_a_fee.mdx new file mode 100644 index 00000000..62c61aa9 --- /dev/null +++ b/docs/content/substrate-runtimes/guides/pay_dot_as_a_fee.mdx @@ -0,0 +1,92 @@ +--- +title: Pay DOT as a Fee +--- + +This feature allows you to set DOT (or any other registered asset) as a fee for transaction execution. Here are the steps that you will need to execute to support it. + +## Configuration + +All you need to configure to start using this feature is an Oracle. You can use some service from any oracle service (like Chainlink, Pyth and others who support Substrate runtimes) or set up a development-mode solution. + +### 1. Add Oracle Members + +To add oracle members, you’ll need to submit transactions with sudo or root privileges: + +```bash +# Add a member (replace with actual address) +subxt tx --url "ws://localhost:9944" \ + --suri "" \ + pallet-membership add_member \ + --account "" + +# Verify members +subxt query --url "ws://localhost:9944" \ + pallet-membership members +``` + +### 2. Format Oracle Values + +Oracle values should be formatted according to your runtime’s `OracleValue` type. Here’s an example for price data: + +```rust +// Example price format +type Price = FixedU128; // price with 18 decimals +type CurrencyId = u32; // for generic template, for EVM we use u128 +``` + +### 3. Submit Oracle Data + +Here’s how to submit oracle data using the CLI: + +```bash +# Submit price feed for DOT/USD +subxt tx --url "ws://localhost:9944" \ + --suri "" \ + orml-oracle feed_values \ + --values '[[1, "12000000000000000000"]]' # replace the CurrencyId with DOT's AssetId and the number with price of native DOT in native tokens +``` + +### 4. Query Oracle Data + +To verify the submitted data: + +```bash +# Query latest price +subxt query --url "ws://localhost:9944" \ + orml-oracle get \ + --key 1 # CurrencyId +``` + +## Development Testing + +For development purposes, you can set up automated price feeds: + +```bash +#!/bin/bash + +while true; do + # Generate random price between $10-15 + PRICE=$((RANDOM % 5000 + 10000)) + PRICE_WITH_DECIMALS="${PRICE}000000000000000" + + subxt tx --url "ws://localhost:9944" \ + --suri "" \ + orml-oracle feed_values \ + --values "[[1, \"$PRICE_WITH_DECIMALS\"]]" + + sleep 60 # Wait 1 minute before next update +done +``` + +## Opting out + +If you want to opt out of this feature, then you can just replace the `pallet_asset_tx_payment` with `pallet_transaction_payment` piece in `SignedExtra`: + +``` +pub type SignedExtra = ( +... +- pallet_asset_tx_payment::ChargeAssetTxPayment, ++ pallet_transaction_payment::ChargeTransactionPayment, +... +); +``` diff --git a/docs/content/substrate-runtimes/guides/predeployed_contracts.mdx b/docs/content/substrate-runtimes/guides/predeployed_contracts.mdx new file mode 100644 index 00000000..775c39b6 --- /dev/null +++ b/docs/content/substrate-runtimes/guides/predeployed_contracts.mdx @@ -0,0 +1,49 @@ +--- +title: Predeployed Contracts +--- + +To enable some bleeding-edge features that EVM provides (like pay gas with any tokens), we have developed an ability to deploy contracts in genesis config. Initially it was done to deploy Entrypoint contract only, but we have supported this functionality for any contract with some limitations. + +## How to use: + +### Step 1: Compiling your contracts + +Currently we are supporting contracts that are compiled with Forge or Hardhat. If you want any other tool to be supported, consider creating an issue and attach and example of compiled contract. + +Here are compilation guides: + +* [Hardhat](https://hardhat.org/hardhat-runner/docs/guides/compile-contracts) +* [Forge](https://book.getfoundry.sh/reference/forge/forge-build) + +When you have compiled the contracts, take the resulting JSON for your contract and save into a single directory for the future deployment. + +### Step 2: Creating the configuration + +In the directory where you have saved the contracts, create a file name "contracts.json". In this file you should store an array of contract metadata, that contains filename of the artifact and the address where you want this contract to be deployed to. So, it should like like this: + +```json +[ + { + "address": "0x81ead4918134AE386dbd04346216E20AB8F822C4", + "filename": "Entrypoint.json" + } +] +``` + +You can take as an example a file in our EVM template in path `contracts/contracts.json`. + +### Step 3: Building the chainspec + +During the step when you generate a chainspec pass the parameter `--predeployed-contracts` with a path to the directory where you have stored the contract artifacts and the configuration: + +```bash +./target/release/parachain-template-node build-spec --disable-default-bootnode --predeployed-contracts= > plain-parachain-chainspec.json +``` + +## Exclude any contracts from genesis config + +If you do not want any contract to be predeployed, you can use the `--no-predeployed-contracts` option when you are generating a plain chainspec. With this flag set you will receive a pristine chainspec without any additional smartcontracts deployed. + +## Limitations + +* Constructors are not executed at the moment. So if your contract needs any initialization, consider deploying it as usual. diff --git a/docs/content/substrate-runtimes/guides/quick_start.mdx b/docs/content/substrate-runtimes/guides/quick_start.mdx new file mode 100644 index 00000000..768c979c --- /dev/null +++ b/docs/content/substrate-runtimes/guides/quick_start.mdx @@ -0,0 +1,124 @@ +--- +title: Quick start +--- + +* Begin by visiting our [repository](https://github.com/OpenZeppelin/polkadot-runtime-templates). You can fork it, or simply clone it to your local directory. +```bash +git clone git@github.com:OpenZeppelin/polkadot-runtime-templates.git +``` +* Move to the directory of the template you want to use. We will use the `generic runtime template` for this tutorial, but it is the same for the same applies to the [EVM Runtime Template](/substrate-runtimes/runtimes/evm). +```bash +cd generic-template +``` +* Build a release version of the runtime and node: +```bash +cargo build --release +``` +* Receive some `PSO` from the [Paseo faucet](https://paritytech.github.io/polkadot-testnet-faucet/) +* Reserve a ParaId on Paseo: + * Go to [PolkadotJS](https://polkadot.js.org/apps). Check that it points to Paseo testnet. + * Go to `Network` > `Parachains` + * Go to `Parathreads` tab + * Click the `+ ParaId` button + * Save the `parachain id` for the further usage. + * Click `Submit` and `Sign and Submit`. +* Generate and customize a chainspec: + + + + +We use the `generic-template-node` executable throughout all the commands since we are using the `generic-template`, but make sure to update the name of the executable if you are using any of the other runtime template. + + + +* Generate a plain chainspec with this command: + + ```bash + ./target/release/generic-template-node build-spec --disable-default-bootnode > plain-parachain-chainspec.json + ``` +* Edit the chainspec: + * Update `name`, `id` and `protocolId` to unique values. + * Change `relay_chain` from `paseo-local` to `paseo`. + * Change `para_id` and `parachainInfo.parachainId` from `1000` to the previously saved parachain id. +* Generate a raw chainspec with this command: + + ```bash + ./target/release/generic-template-node build-spec --chain plain-parachain-chainspec.json --disable-default-bootnode --raw > raw-parachain-chainspec.json + ``` + + + + +At this point, you are free to use the omni-node. The command for using omni-node takes the form: `polkadot-omni-node --chain `. For more information about using omni-node, see the [polkadot-omni-node documentation](https://docs.polkadot.com/develop/toolkit/parachains/polkadot-omni-node/). + + + +* Run two nodes and wait until it syncs with the Paseo relay chain. This can take a fairly long time(up to 2 days), so we can use the `fast-unsafe` flag to make the process faster since we are on a testnet(~ 3 hours). `fast` downloads the blocks without executing the transactions, and `unsafe` skips downloading the state proofs(which we are ok with since it is a testnet). + + ```bash + ./target/release/generic-template-node \ + --alice \ + --collator \ + --force-authoring \ + --chain raw-parachain-chainspec.json \ + --base-path \ + --port 40333 \ + --rpc-port 8844 \ + -- \ + --execution wasm \ + --chain \ + --port 30343 \ + --rpc-port 9977 \ + --sync fast-unsafe + ``` + + ```bash + ./target/release/generic-template-node \ + --bob \ + --collator \ + --force-authoring \ + --chain raw-parachain-chainspec.json \ + --base-path \ + --port 40333 \ + --rpc-port 8845 \ + -- \ + --execution wasm \ + --chain \ + --port 30343 \ + --rpc-port 9977 \ + --sync fast-unsafe + ``` + * `` is where the downloaded chain state will be stored. It can be any folder on your computer. + * `` is where your Paseo chainspec is stored. You can download this file from the [official Polkadot sdk repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/polkadot/node/service/chain-specs/paseo.json). +* Register a parathread: + * Generate a genesis state: + + ```bash + ./target/release/generic-template-node export-genesis-state --chain raw-parachain-chainspec.json para--genesis-state + ``` + * Generate a genesis wasm: + + ```bash + ./target/release/generic-template-node export-genesis-wasm --chain raw-parachain-chainspec.json para--wasm + ``` + * Go to [PolkadotJS](https://polkadot.js.org/apps). Check that it points to Paseo testnet. + * Go to `Network` > `Parachains`. + * Go to `Parathreads` tab. + * Click the `+ ParaThread` button. + * Insert `para--wasm` to `code` field. + * Insert `para--genesis-state` to `initial state` field. + * Click `Submit` and `Sign and Submit`. +* When a parachain gets synced with a relaychain, you may start producing blocks as a parathread: + * Create some transaction with a PolkadotJS pointing to your parachain setup. + * With a PolkadotJS pointing to Paseo go to `Developer` > `Extrinsics`. + * Submit an extrinsic `onDemandAssignmentProvider.placeOrderAllowDeath` or `onDemandAssignmentProvider.placeOrderKeepAlive`: + * `maxAmount` should be not less than 10_000_000 and it is amount of 0.00001 PAS. It is an amount of PAS paid for the block. + * `paraId` should be set to your parachain id. + * Click `Submit` and `Sign and Submit`. + * In some time your parathread will produce a block and in one of the next blocks of Paseo there will be an inclusion of this block + +## What's next? + +* Read our general guides to understand more about the concepts of runtime development. +* Learn more about the runtime configuration. Currently, we have two runtime templates: [Generic Runtime Template](/substrate-runtimes/runtimes/generic) and [EVM Runtime Template](/substrate-runtimes/runtimes/evm). +* Explore the documentation for pallets. It may be useful if you are considering building a frontend for your parachain. diff --git a/docs/content/substrate-runtimes/guides/rpc_differences.mdx b/docs/content/substrate-runtimes/guides/rpc_differences.mdx new file mode 100644 index 00000000..954c974b --- /dev/null +++ b/docs/content/substrate-runtimes/guides/rpc_differences.mdx @@ -0,0 +1,12 @@ +--- +title: RPC Differences +--- + +The EVM in a Substrate node has almost the same RPC calls common to all nodes. But some of them are not applicable for Frontier. + +These are the calls that behave differently than would be expected by regular Ethereum nodes: + +* `eth_sign` / `eth_signTransaction` -- these methods do not exist in the Frontier and this template because it, unlike Ethereum nodes, does not hold any keys. +* `eth_syncing` -- it can have additional fields `warp_chunks_amount` and `warp_chunks_processed` that describe warp syncing process. + +Other calls comply to the same scheme as Ethereum nodes provide. diff --git a/docs/content/substrate-runtimes/guides/testing_with_zombienet.mdx b/docs/content/substrate-runtimes/guides/testing_with_zombienet.mdx new file mode 100644 index 00000000..57faecea --- /dev/null +++ b/docs/content/substrate-runtimes/guides/testing_with_zombienet.mdx @@ -0,0 +1,318 @@ +--- +title: Testing Solidity Smart Contracts with Zombienet +--- + +In this tutorial, we will demonstrate how to deploy your parachain using Zombienet, and test the functionalities of your parachain. + +Below are the main steps of this demo: + +1. Deploy our parachain against the locally simulated Paseo testnet by Zombienet. +2. Deploy a Solidity smart contract on our parachain. +3. Successfully invoke the Solidity smart contract deployed on our parachain. + +## Step 1: Deploy The Parachain + +1. git clone https://github.com/OpenZeppelin/polkadot-runtime-templates +2. move to evm template directory + +```bash +cd evm-template +``` + +3. replace the content of `zombinet-config/devnet.toml` with: + +```toml +[relaychain] +chain = "paseo-local" +default_command = "./bin-v1.6.0/polkadot" + +[[relaychain.nodes]] +name = "alice" +validator = true + +[[relaychain.nodes]] +name = "bob" +validator = true + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler] +scheduling_lookahead = 2 + +[relaychain.genesis.runtimeGenesis.patch.configuration.config.inclusion] +max_candidate_depth = 3 +allowed_ancestry_len = 2 +``` + +4. build the zombienet: + +```bash +./scripts/zombienet.sh build +``` + + - if you came across this error (click to expand): + +
+Details + +```bash +error[E0635]: unknown feature `stdsimd` + --> /Users/ozgunozerk/.cargo/registry/src/index.crates.io-6f17d22bba15001f/ahash-0.7.6/src/lib.rs:33:42 + | +33 | #![cfg_attr(feature = "stdsimd", feature(stdsimd))] +``` + +`Cd` into the `polkadot-sdk` directory (the path should be visible on the error message), and run the below command to update `ahash`: + +```bash +cargo update -p ahash@0.7.6 +``` +
+ + - or if you came across this error (click to expand): + +
+Details + +```rust +assertion failed [block != nullptr]: BasicBlock requested for unrecognized address +``` + +just re-run 🙂 +
+ +5. run the zombinet: + + ```bash + ./scripts/zombienet.sh devnet + ``` + +6. build it with `async-backing` feature: + + ```bash + cargo build --release --features="async-backing" + ``` + +7. copy the `Direct Link` from `Alice's` tab from Zombienet TUI: + + ![Alice Direct Link](/alice-direct-link.png) + +8. Open the link with Chrome: [PolkadotJS](https://polkadot.js.org/apps). As of 2024 July, it doesn't work on Safari and Brave, and it is buggy on Firefox. + +9. Reserve a `ParaId` on Zombienet: + 1. Go to `Network` > `Parachains` + 2. Go to `Parathreads` tab + 3. Click the `+ ParaId` button + 4. Save a `parachain id` for the further usage. + 5. Click `Submit` and `Sign and Submit` (you can use `Alice` as the account). + +10. Preparing necessary files to become a Parachain: + 1. Generate a plain chainspec: + + ```bash + ./target/release/evm-template-node build-spec --disable-default-bootnode > plain-parachain-chainspec.json + ``` + + 2. Edit the chainspec: + 1. Update `name`, `id` and `protocolId` to unique values (optional). + 2. Change `para_id` and `parachainInfo.parachainId` from `1000` to the previously saved parachain id (probably 2000 if that's your first time ;) ). + 3. Edit the `evmChainId.chainId` to the value of your choice. Try to select a value that has no existing EVM chain assigned to it (should be ok to leave as is for the most cases). + + 3. Generate a raw chainspec: + + ```bash + ./target/release/evm-template-node build-spec --chain plain-parachain-chainspec.json --disable-default-bootnode --raw > raw-parachain-chainspec.json + ``` + + 4. Generate the genesis state: + + ```bash + ./target/release/evm-template-node export-genesis-state --chain raw-parachain-chainspec.json > genesis-state + ``` + + 5. Generate the validation code: + + ```bash + ./target/release/evm-template-node export-genesis-wasm --chain raw-parachain-chainspec.json > genesis-wasm + ``` + +11. Registering the Parachain: + 1. Go back to `polkadot.js.org/apps` (remember Chrome). Go to `Developer/Sudo`. + 2. select `pasasSudoWrapper` and `sudoScheduleParaInitialize(id, genesis)` + 3. enter the reserved id (2000) to `id` field + 4. enable `file upload` for both `genesisHead` and `validationCode` → because we will upload files for these. + 5. select `Yes` for `paraKind` → meaning when we register our parachain, it will be a parachain rather than a parathread. + 6. drag&drop your `genesis-state` file generated in step `10.d` into `genesisHead` field (good luck with the aiming) + 7. drag&drop your `genesis-wasm` file generated in `10.e` into `validationCode` field + 8. It should look like below: + + ![Register Parachain](/register-parachain.png) + + 9. `Submit Sudo`! + +12. copy the path to `chain-spec` from zombienet terminal from `Bob` (beware, this file is changing every time you spin up a new zombienet): + + ![Zombie Chain Spec](/zombie-chain-spec.png) + +13. run the node, and provide the `chain-spec` you copied from the last step into `--chain` part: + - be sure to clear your storage if you were running a node before + + ```bash + ./target/release/evm-template-node \ + --alice \ + --collator \ + --force-authoring \ + --chain raw-parachain-chainspec.json \ + --base-path storage/alice \ + --port 40333 \ + --rpc-port 8844 \ + -- \ + --execution wasm \ + --chain /var/folders/...redacted.../paseo-local.json \ + --port 30343 \ + --rpc-port 9977 + ``` + +14. your node should be running without any problem, and should see block production in your node terminal! + + ![Node Success](/node-success.png) + +## Step 2: Deploy a Solidity Smart Contract + +1. Install Foundry with: `curl -L https://foundry.paradigm.xyz | bash` + +2. have a smart contract file ready, any smart contract of your choice! We will go with a cute `HelloWorld.sol` smart contract for this tutorial: + + ```solidity + pragma solidity ^0.8.0; + + contract HelloWorld { + string public greeting = "Hello, World!"; + + function getGreeting() public view returns (string memory) { + return greeting; + } + } + ``` + +3. Create a new javascript project with the below files: + 1. `package.json`: + + ```json + { + "name": "ts-wallet", + "version": "1.0.0", + "description": "", + "main": "index.js", + "type": "module", + "scripts": { + "exec": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "web3": "^4.8.0" + } + } + ``` + + 2. `sanity_check.js`: + + ```javascript + import { Web3 } from "web3"; + + const web3 = new Web3("ws://127.0.0.1:8844"); + + console.log("Balance:"); + web3.eth.getBalance("0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac").then(console.log); + + let raw = await web3.eth.accounts.signTransaction({ + gas: 21000, + gasPrice: 10000000000, + from: "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac", // Alith's address + to: "0x7c98a1801f0B28dF559bCd828fc67Bd6ab558074", // Baltathar's address + value: '100000000000000000' + }, "0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133"); // Alith's private key + + let res = await web3.eth.sendSignedTransaction(raw.rawTransaction); + console.log("Transaction details:"); + console.log(res); + ``` + + 3. `invoke_smart_contract.js`: + + ```javascript + import { Web3 } from "web3"; + import { MyAbi } from "./abi.js"; + + const web3 = new Web3("ws://127.0.0.1:8844"); + + let contract = new web3.eth.Contract(MyAbi, "0x4045F03B68919da2c440F023Fd7cE2982BfD3C03"); + let s = await contract.methods.getGreeting().call(); + + console.log(s); + ``` + + 4. `abi.js`: + + ```javascript + export var MyAbi = [ + { + "type": "function", + "name": "getGreeting", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "greeting", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + } + ]; + ``` + +4. run the below command, and you should see the balance, and then the transaction details printed, proving everything works so far! + + ```bash + node sanity_check.js + ``` + +5. open a terminal instance where the current directory has the `HelloWorld.sol` file, and run the below command to deploy the contract with Alith's private key: + + ```bash + forge create --rpc-url http://localhost:9933 --private-key 0x5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133 HelloWorld.sol:HelloWorld + ``` + + - don't forget to copy the address this contract deployed to (shown in the output of the command)! + +## Step 3: Invoke The Solidity Smart Contract + +1. replace the contract address in `invoke_smart_contract.js` with the address you copied! + +2. build the `abi` of the smart contract with: + + ```bash + forge build --silent && jq '.abi' ./out/HelloWorld.sol/HelloWorld.json + ``` + +3. Surprise! We already give you the abi of this in `abi.js` file in step 2. If you used another contract than `HelloWorld`, replace that `abi.js` file's content with your contracts `abi`. + +4. run the below command, and you should see your smart contract in action: + + ```bash + node invoke_smart_contract.js + ``` diff --git a/docs/content/substrate-runtimes/guides/weights_fees.mdx b/docs/content/substrate-runtimes/guides/weights_fees.mdx new file mode 100644 index 00000000..e510e888 --- /dev/null +++ b/docs/content/substrate-runtimes/guides/weights_fees.mdx @@ -0,0 +1,58 @@ +--- +title: Weights & Fees +--- + +Weight is a metric to estimate the time it takes to execute code. + +In Substrate, every transaction has a weight. The block production algorithm selects the set of transactions from the transaction pool that achieve block saturation without exceeding `MaximumBlockWeight`. The `AvailableBlockRatio` ensures only a fraction of `MaximumBlockWeight` is used for regular transactions while system-critical operations (operational transactions) may use all remaining block weight. + +Substrate charges every transaction’s caller with fees in proportion to the cost of execution. It does so by converting each transaction’s weight to fees via `WeightToFee`. + +Weights which underestimate cost of execution expose a DDOS vulnerability. If a transaction takes up less space than it should, too many transactions may be included in the block and the block’s execution time may exceed expected block time. For parachains, this DDOS vulnerability can be critical; it may prevent future block production by bricking the chain until the parachain state is reset on the relay chain. + +It is imperative for runtime developers to be conservative when approximating weight. Moreover, runtime complexity that is non-linear must be limited and bounded. + +## Runtime-Specific Weights + +Production runtimes should never set `WeightInfo = ()` in production because this configuration underestimates weight. Instead, every production runtime should generate and set its own weights specific to its runtime. + +1. Ensure passing status for benchmarking compilation by running this command: +``` +cargo build --features runtime-benchmarks +``` +2. Run the benchmarking command and write its output to the runtime. Here is the command via bash script: +``` +#!/bin/sh + +# Build release runtime benchmarks +cargo build --release --features=runtime-benchmarks + +# Collect all pallets needed for benchmarking +# Makes the assumption all pallets are present at: /pallets/$PALLET_NAME +pallets=$(ls pallets/) + +# Generate weights +for pallet_name in $pallets; do + ./target/release/node benchmark pallet \ + --pallet pallet_$pallet_name \ + --extrinsic "*" \ + --steps 50 \ + --repeat 20 \ + --output ./runtime/src/weights/$pallet_name.rs +done +``` +1. Automate the benchmarking pipeline. The first step may be automated by enforcing the command in the CI. The second step may be automated by integrating a benchmarking github bot. For an example, see [Parity’s Command Bot](https://github.com/paritytech/command-bot). + +## More Reading + +Technically, **Weight** represents the time it takes to execute code on **production hardware** used by nodes actively participating in the network’s consensus. + +``` +1 unit of weight = 1 picosecond of execution time on target reference hardware +``` + +It is important to ONLY generate weights on **production hardware**. [Polkadot Reference Hardware Docs](https://wiki.polkadot.network/docs/maintain-guides-how-to-validate-polkadot#:~:text=Reference%20Hardware%E2%80%8B,instance%20on%20GCP%20and%20c6i) provides more information on Polkadot validator hardware requirements. + +### References + +[Substrate Weight & Fees](https://www.shawntabrizi.com/blog/substrate/substrate-weight-and-fees/) by [Shawn Tabrizi](https://github.com/shawntabrizi/) diff --git a/docs/content/substrate-runtimes/index.mdx b/docs/content/substrate-runtimes/index.mdx new file mode 100644 index 00000000..e3c7f302 --- /dev/null +++ b/docs/content/substrate-runtimes/index.mdx @@ -0,0 +1,13 @@ +--- +title: Polkadot Parachain Runtimes +--- + +A collection of secure runtime templates to build parachains more easily on Polkadot. + +## Runtimes +* [Generic Runtime Template](/substrate-runtimes/runtimes/generic) +* [EVM Runtime Template](/substrate-runtimes/runtimes/evm) + +## Where to get started +* [Quick Start](/substrate-runtimes/guides/quick_start): a generic parachain runtime that works out of the box. It has all the must have features, and allows further customization based on your project’s needs. Generic Runtime Template also serves as the base for our other runtime templates. +* [Testing with Zombienet](/substrate-runtimes/guides/testing_with_zombienet): a more opinionated parachain runtime template that maximizes Ethereum compatibility by using `AccountId20` and configures a local EVM instance. You can easily migrate/deploy Solidity Smart Contracts to this one. diff --git a/docs/content/substrate-runtimes/misc/multisig-accounts.mdx b/docs/content/substrate-runtimes/misc/multisig-accounts.mdx new file mode 100644 index 00000000..ce1026df --- /dev/null +++ b/docs/content/substrate-runtimes/misc/multisig-accounts.mdx @@ -0,0 +1,15 @@ +--- +title: Multisig Accounts +--- + +* Multisig accounts do not have a private key. They are managed by their signatories. + * Q: how can it be secure if it does not have a private key + * A: substrate uses `origin` for accounts. Each account signs their transaction. Someone who does not belong to the signatories of a multisig account, cannot approve/dispatch calls in the name of the multisig account, even if they wanted to. Knowing the public key of the account is not enough, since the approval message needs to belong to signatories, and each message is signed by the private key. So, effectively, a multisig account indirectly has private keys as many as its signatory count, and thus, a multisig account does not need a separate private key. +* Creating a multisig account is basically just deriving the public key of it. Just like the creation of a new personal account, we are just deriving the public key. We don’t store this public key on-chain for the creation of the account. The only difference between personal accounts and multisig accounts is, the public key derivation process: + * for a single account, the process is roughly: + * generating a private key, + * deriving the public key from the private key. + * for multisig accounts, the process is roughly: + * supplying the public keys of the accounts that will belong to the multisig account + * supplying the threshold for this multisig account (same signatories with different threshold qualifies as a different multisig account) + * deriving the public key from combining and hashing threshold and signatories’ public keys diff --git a/docs/content/substrate-runtimes/pallets/assets.mdx b/docs/content/substrate-runtimes/pallets/assets.mdx new file mode 100644 index 00000000..b6dca6ab --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/assets.mdx @@ -0,0 +1,770 @@ +--- +title: pallet_assets +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[GitHub Repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/assets/src/lib.rs) + +## Purpose + +The `pallet_assets` module is designed to manage and manipulate custom fungible asset types. It provides functionality for asset creation, management, and various operations, allowing for a flexible and scalable approach to asset handling. + +## Pallet specific terms + +* `Asset ID` - A unique identifier for an asset type. +* `Asset Owner` - The account that owns an asset. +* `Admin` - The account that has administrative privileges over an asset, such as: `minting`, `burning`, `freezing`, and `thawing`. +* `Sufficiency` - The amount of an asset that is required to be held in an account in order to perform certain operations, such as: `transfers`, `freezing`, and `thawing`. + +## Config + +* Pallet-specific configs + * `Balance` -- The balance unit type. + * `RemoveItemsLimit` -- The maximum number of items that can be removed in a single `destroy_accounts` and `destroy_approvals` call. + * `AssetId` -- The unique asset identifiers. + * `AssetIdParameter` -- The parameter type for asset identifiers. + * `Currency` -- The currency type. + * `CreateOrigin` -- The origin type that can create an asset. + * `ForceOrigin` -- The origin type that can force an asset action. + * `AssetDeposit` -- The deposit required for reservation to create an asset. + * `AssetAccountDeposit` -- The deposit required for a non-provider asset account for reservation to create an asset account. + * `MetadataDepositBase` -- The base deposit required to reserve metadata. + * `MetadataDepositPerByte` -- The deposit required to reserve metadata per byte. + * `ApprovalDeposit` -- The deposit required to reserve an approval. + * `StringLimit` -- The maximum length of a name or symbol. + * `Freezer` -- A hook to allow a per-asset, per-account minimum balance to be enforced. + * `Extra` -- The extra data to be stored with an account’s asset balance. + * `CallbackHandle` -- An asset created or destroyed callback functions. + * `BenchmarkHelper` -- A helper type to benchmark the pallet’s weight. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- [Weight](/substrate-runtimes/glossary#weight) information for extrinsics in this pallet. + +## Events + +* `Created(asset_id, creator, owner)` -- An asset was created. +* `Issued(asset_id, owner, amount)` -- An asset was issued. +* `Transferred(asset_id, from, to, amount)` -- An asset was transferred. +* `Burned(asset_id, owner, balance)` -- An asset was burned. +* `TeamChanged(asset_id, issuer, admin, freezer)` -- The management team of an asset was changed. +* `OwnerChanged(asset_id, owner)` -- The owner of an asset was changed. +* `Frozen(asset_id, who)` -- An account `who` was frozen. +* `Thawed(asset_id, who)` -- An account `who` was thawed. +* `AssetFrozen(asset_id)` -- An asset was frozen. +* `AssetThawed(asset_id)` -- An asset was thawed. +* `AccountsDestroyed(asset_id, accounts_destroyed, accounts_remaining)` -- A number `accounts_destroyed` accounts were destroyed for an asset. +* `ApprovalsDestroyed(asset_id, approvals_destroyed, approvals_remaining)` -- A number `approvals_destroyed` approvals were destroyed for an asset. +* `DestructionStarted(asset_id)` -- The destruction of an asset class was started. +* `Destroyed(asset_id)` -- An asset class was destroyed. +* `ForceCreated(asset_id, owner)` -- An asset was force created. +* `MetadataSet(asset_id, name, symbol, decimals, is_frozen)` -- The metadata of an asset was set. +* `MetadataCleared(asset_id)` -- The metadata of an asset was cleared. +* `ApprovedTransfer(asset_id, source, delegate, amount)` -- An approval was made for a delegate to transfer a specific amount of an asset on behalf of the owner. +* `ApprovalCancelled(asset_id, owner, delegate)` -- An approval was cancelled for a delegate to transfer a specific amount of an asset on behalf of the owner. +* `TransferredApproved(asset_id, owner, delegate, destination, amount)` -- A delegate transferred a specific amount of an asset on behalf of the owner. +* `AssetStatusChanged(asset_id)` -- The attributes of an asset was forcely changed. +* `AssetMinBalanceChanged(asset_id, new_min_balance)` -- The minimum balance of an asset was changed by an owner. +* `Touched(asset_id, who, depositor)` -- An account `who` was created with a deposit from `depositor`. +* `Blocked(asset_id, who)` -- An account `who` was blocked. + +## Errors + +* `BalanceLow` -- Account balance must be greater than or equal to the transfer amount. +* `NoAccount` -- The account to alter does not exist. +* `NoPermission` -- The signing account has no permission to do the operation. +* `Unknown` -- The given asset ID is unknown. +* `Frozen` -- The origin account is frozen. +* `InUse` -- The asset ID is already taken. +* `BadWitness` -- Invalid witness data given. +* `MinBalanceZero` -- Minimum balance should be non-zero. +* `UnavailableConsumer` -- Unable to increment the consumer reference counters on the account. Either no provider reference exists to allow a non-zero balance of a non-self-sufficient asset, or one fewer then the maximum number of consumers has been reached. +* `BadMetadata` -- Invalid metadata given. +* `Unapproved` -- No approval exists that would allow the transfer. +* `WouldDie` -- The source account would not survive the transfer and it needs to stay alive. +* `AlreadyExists` -- The asset-account already exists. +* `NoDeposit` -- The asset-account doesn’t have an associated deposit. +* `WouldBurn` -- The operation would result in funds being burned. +* `LiveAsset` -- The asset is a live asset and is actively being used. Usually emit for operations such as `start_destroy` which require the asset to be in a destroying state. +* `AssetNotLive` -- The asset is not live, and likely being destroyed. +* `IncorrectStatus` -- The asset status is not the expected status. +* `NotFrozen` -- The asset should be frozen before the given operation. +* `CallbackFailed` -- Callback action resulted in error. + +## Dispatchables + +#### `create` +```rust +pub fn create( + origin: OriginFor, + id: T::AssetIdParameter, + admin: AccountIdLookupOf, + min_balance: T::Balance, +) -> DispatchResult +``` +Issues a new class of fungible assets from a public origin. If an asset was not created, raises `CallbackFailed`. + + +The origin must conform to the configured `CreateOrigin` and have sufficient funds free. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the identifier of the asset to be created. +* `admin: AccountIdLookupOf` -- the admin of this asset, who can execute all privileged functions. +* `min_balance: T::Balance` -- the minimum balance of this new asset that any single account must have. + +#### `force_create` +```rust +pub fn force_create( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + is_sufficient: bool, + #[pallet::compact] min_balance: T::Balance, +) -> DispatchResult +``` +Issues a new class of fungible assets from a privileged origin. + + +The origin must conform to `ForceOrigin`. Unlike `create`, no funds are reserved. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the unique identifier of the asset to be created. +* `owner: AccountIdLookupOf` -- the owner of this asset, who can mint/burn tokens. +* `is_sufficient: bool` -- whether the owner account should be checked for minimum balance. + +#### `start_destroy` +```rust +pub fn start_destroy( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Start the process of destroying a fungible asset class. + + +`start_destroy` is the first in a series of extrinsics that should be called, to allow destruction of an asset class. The origin must conform to `ForceOrigin` or must be `Signed` by the asset’s `owner`. The asset class must be frozen before calling `start_destroy`. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the identifier of the existing asset to be destroyed. + +#### `destroy_accounts` +```rust +pub fn destroy_accounts( + origin: OriginFor, + id: T::AssetIdParameter, +) -> DispatchResultWithPostInfo +``` + +Destroys accounts associated with a given asset. + + +`destroy_accounts` is the second in a series of extrinsics that should only be called after `start_destroy`, to allow destruction of an asset class. The origin must conform to `ForceOrigin` or must be `Signed` by the asset’s `owner`. It will call `T::RemoveItemsLimit::get()` so if the number of accounts exceeds this limit, this function need to be called several times. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `id: T::AssetIdParameter` -- the identifier of the existing asset to be destroyed. + +#### `destroy_approvals` +```rust +pub fn destroy_approvals( + origin: OriginFor, + id: T::AssetIdParameter, +) -> DispatchResult +``` +Clears approvals associated with a given asset. + + +It should be called after `start_destroy`. It will call `T::RemoveItemsLimit::get()` so if the number of approvals exceeds this limit, this function need to be called several times. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, root) account. +* `id: T::AssetIdParameter` -- the identifier of the asset for which all approvals will be destroyed. + +#### `finish_destroy` +```rust +pub fn finish_destroy( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Completes the process of asset destruction initiated by `start_destroy`. + + +This function is the final step in the asset destruction process. It should only be called after all checks and balances have been done, typically after `start_destroy` and `destroy_accounts` are successfully called. The origin must conform to `ForceOrigin` or be `Signed` by the asset’s owner, ensuring that this sensitive action is adequately authorized. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, root) account. +* `id: T::AssetIdParameter` -- the identifier of the asset to be destroyed. + +#### `mint` +```rust +pub fn mint( + origin: OriginFor, + id: T::AssetIdParameter, + beneficiary: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Mints new units of a specific asset and assigns them to a beneficiary account. + + +The `mint` function allows authorized accounts to increase the supply of an asset. The origin must be authorized to mint assets, typically configured via `MintOrigin`. The `#[pallet::compact]` attribute is used to optimize the storage of the `amount` parameter. This function ensures that the total supply doesn’t overflow and that the beneficiary is capable of holding the asset. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which units are to be minted. +* `beneficiary: AccountIdLookupOf` -- the account that will receive the newly minted units. +* `amount: T::Balance` -- the amount of new units to be minted and credited to the beneficiary. + +#### `burn` +```rust +pub fn burn( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Destroys units of a specific asset from the specified account. + + +The `burn` function decreases the supply of an asset by removing a specified amount from a particular account. The origin must be authorized to burn assets, typically configured via `BurnOrigin`. The `#[pallet::compact]` attribute optimizes the storage of the `amount` parameter. This function is used for asset management, such as reducing supply or removing assets from circulation for regulatory compliance. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset from which units are to be burned. +* `who: AccountIdLookupOf` -- the account from which the units will be destroyed. +* `amount: T::Balance` -- the amount of units to be burned from the specified account. + +#### `transfer` +```rust +pub fn transfer( + origin: OriginFor, + id: T::AssetIdParameter, + target: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Transfers a specified amount of a specific asset to a target account. + + +The `transfer` function allows assets to be moved between accounts. The origin must be signed by the account wishing to transfer assets and must have sufficient balance. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. This function checks for liquidity, asset validity, and the receiving account’s ability to accept the asset, ensuring secure and accurate transactions. + + +***Params:*** + +* `origin: OriginFor` -- the caller account initiating the transfer. +* `id: T::AssetIdParameter` -- the identifier of the asset to be transferred. +* `target: AccountIdLookupOf` -- the recipient account of the asset units. +* `amount: T::Balance` -- the amount of units to transfer from the caller to the target account. The amount actually transferred may be slightly greater in the case that the transfer would otherwise take the sender balance above zero but below the minimum balance. Must be greater than zero. + +#### `transfer_keep_alive` +```rust +pub fn transfer_keep_alive( + origin: OriginFor, + id: T::AssetIdParameter, + target: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Transfers a specified amount of a specific asset to a target account, ensuring that the transfer does not result in the sender’s total demise. + + +The `transfer_keep_alive` function is similar to `transfer` but includes an additional check that prevents the transfer if it would cause the origin account to be reaped. This is critical for ensuring the account’s continued existence, particularly for accounts with minimum balance requirements. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. Like `transfer`, it ensures secure and precise asset movement between accounts. + + +***Params:*** + +* `origin: OriginFor` -- the caller account initiating the transfer. +* `id: T::AssetIdParameter` -- the identifier of the asset to be transferred. +* `target: AccountIdLookupOf` -- the recipient account of the asset units. +* `amount: T::Balance` -- the amount of units to transfer from the caller to the target account, while ensuring the sender’s account remains active. The amount actually transferred may be slightly greater in the case that the transfer would otherwise take the sender balance above zero but below the minimum balance. Must be greater than zero. + +#### `force_transfer` +```rust +pub fn force_transfer( + origin: OriginFor, + id: T::AssetIdParameter, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Forces the transfer of a specified amount of a specific asset from a source account to a destination account. + + +The `force_transfer` function is an administrative tool that allows a privileged origin, typically configured via `ForceOrigin`, to move assets between accounts without consent from the source. This might be used in exceptional scenarios, such as legal or administrative resolutions. The `#[pallet::compact]` attribute optimizes storage of the `amount` parameter. This function must be used with caution due to its power and potential impact on account balances. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be forcibly transferred. +* `source: AccountIdLookupOf` -- the account from which the asset units will be debited. +* `dest: AccountIdLookupOf` -- the account to which the asset units will be credited. +* `amount: T::Balance` -- the amount of units to forcibly transfer from the source to the destination account. The amount actually transferred may be slightly greater in the case that the transfer would otherwise take the sender balance above zero but below the minimum balance. Must be greater than zero. + +#### `freeze` +```rust +pub fn freeze( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Freezes the specified asset in a particular account, preventing further unprivileged transfers of the asset from the frozen account. + + +The `freeze` function is used to halt all operations for a specified asset in a given account, typically for regulatory or compliance reasons. This action can only be performed by an authorized origin, usually the asset’s admin or another account with special privileges. Once an account is frozen, it cannot transact the specified asset until an `thaw` operation is performed. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be frozen. +* `who: AccountIdLookupOf` -- the account in which the asset will be frozen. Must already exist as an entry in `Account`s of the asset. + +#### `thaw` +```rust +pub fn thaw( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Unfreezes the specified asset in a particular account, allowing the resumption of unprivileged transfers of the asset. + + +The `thaw` function reverses the effect of `freeze`, re-enabling the account to transact with the specified asset. This action is typically restricted to authorized origins, such as the asset’s admin or other accounts with special privileges. It is used in scenarios where the conditions that led to the initial freezing have been resolved or are no longer applicable. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be thawed. +* `who: AccountIdLookupOf` -- the account in which the asset will be thawed. + +#### `freeze_asset` +```rust +pub fn freeze_asset( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Freezes all operations for a specified asset across all accounts, halting any transfer, minting, or other asset-related activities. + + +The `freeze_asset` function is a comprehensive freeze operation affecting all accounts holding the specified asset. It is intended for emergency or regulatory situations requiring immediate suspension of all activities related to the asset. This action typically requires authorization from a privileged origin, which might be the asset’s admin or a specific governance mechanism in place. The freeze remains in effect until an `thaw_asset` operation is performed. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be frozen. + +#### `thaw_asset` +```rust +pub fn thaw_asset( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Unfreezes all operations for a specified asset across all accounts, allowing resumption of transfers, minting, and other asset-related activities. + + +The `thaw_asset` function reverses the comprehensive freeze applied by `freeze_asset`, re-enabling the normal operation of all activities related to the asset across all accounts. It is typically used once the conditions necessitating the freeze are no longer applicable or have been resolved. This action usually requires authorization from a privileged origin, such as the asset’s admin or a specific governance mechanism. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset to be thawed. + +#### `transfer_ownership` +```rust +pub fn transfer_ownership( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, +) -> DispatchResult +``` +Transfers the ownership of a specific asset to another account. + + +The `transfer_ownership` function is used to change the admin or owner of an asset to a new account. This operation might be necessary for administrative restructuring, transferring responsibilities, or ownership succession scenarios. The origin must be the current owner or authorized to transfer ownership, ensuring proper authorization and security in the ownership transfer process. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which ownership is being transferred. +* `owner: AccountIdLookupOf` -- the account that will become the new owner of the asset. + +#### `set_team` +```rust +pub fn set_team( + origin: OriginFor, + id: T::AssetIdParameter, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, +) -> DispatchResult +``` +Sets the team managing a specific asset, defining roles for issuance, administration, and freezing operations. + + +The `set_team` function is used to designate specific accounts for managing various aspects of an asset. This includes issuing new units of the asset, administering ownership or other significant changes, and freezing or thawing operations. The origin must be the current owner or another authorized account to ensure proper governance and control over the asset. This function allows for flexible and secure management of assets by clearly separating responsibilities among different roles. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the team is being set. +* `issuer: AccountIdLookupOf` -- the account designated for issuing new units of the asset. +* `admin: AccountIdLookupOf` -- the account designated for administrative tasks. +* `freezer: AccountIdLookupOf` -- the account designated for freezing and thawing the asset. + +#### `set_metadata` +```rust +pub fn set_metadata( + origin: OriginFor, + id: T::AssetIdParameter, + name: Vec, + symbol: Vec, + decimals: u8, +) -> DispatchResult +``` +Sets or updates the metadata for a specific asset, including its name, symbol, and decimals. + + +The `set_metadata` function is used to define or update the descriptive attributes of an asset. These attributes include the asset’s name, a short symbol, and the number of decimals that represent the asset’s smallest unit. This information is crucial for user interfaces and third-party integrations to properly display and understand the asset’s properties. The origin must have the appropriate permissions to modify an asset’s metadata, ensuring that only authorized users can make changes. Funds of sender are reserved according to the formula: `MetadataDepositBase + MetadataDepositPerByte * (name.len + symbol.len)` taking into account any already reserved funds. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being set. +* `name: Vec` -- the new name of the asset as a byte array. +* `symbol: Vec` -- the new symbol of the asset as a byte array. +* `decimals: u8` -- the number of decimals places used to represent the asset. + +#### `clear_metadata` +```rust +pub fn clear_metadata( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Clears the metadata of a specific asset, removing its name, symbol, and decimals information. + + +The `clear_metadata` function is used to remove all descriptive information associated with an asset. This may be necessary in situations where the asset’s properties have changed significantly, or the asset is being deprecated. Clearing metadata is an important aspect of asset management and requires proper authorization to ensure that only valid users can perform this action. Once cleared, the asset will no longer have descriptive labels or detailed display information until new metadata is set. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being cleared. + +#### `force_set_metadata` +```rust +pub fn force_set_metadata( + origin: OriginFor, + id: T::AssetIdParameter, + name: Vec, + symbol: Vec, + decimals: u8, + is_frozen: bool, +) -> DispatchResult +``` +Forcibly sets or updates the metadata for a specific asset, including its name, symbol, decimals, and frozen status, regardless of the current owner’s permissions. + + +The `force_set_metadata` function is an administrative tool that allows privileged accounts to set or update the metadata of an asset. This includes force-changing the name, symbol, decimals, and freeze status. It is used in scenarios requiring higher-level intervention, such as regulatory compliance or significant asset restructuring. The origin must be authorized to perform forceful administrative actions, ensuring that only valid scenarios permit such drastic changes. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being forcibly set. +* `name: Vec` -- the new name of the asset as a byte array. +* `symbol: Vec` -- the new symbol of the asset as a byte array. +* `decimals: u8` -- the number of decimal places used to represent the asset. +* `is_frozen: bool` -- the new frozen status of the asset, determining if it can be transacted. + +#### `force_clear_metadata` +```rust +pub fn force_clear_metadata( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Forcibly clears all metadata of a specific asset, including its name, symbol, and decimals, by an administrative order. + + +The `force_clear_metadata` function is an administrative action used in circumstances that require overriding the usual asset management, such as regulatory compliance or significant changes to the asset’s structure or purpose. This function removes all descriptive information, essentially resetting the asset’s public-facing details. The origin must be a privileged account authorized to perform such forceful actions, ensuring that the clearance is done under proper oversight and for valid reasons. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which metadata is being forcibly cleared. + +#### `force_asset_status` +```rust +pub fn force_asset_status( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + issuer: AccountIdLookupOf, + admin: AccountIdLookupOf, + freezer: AccountIdLookupOf, + #[pallet::compact] min_balance: T::Balance, + is_sufficient: bool, + is_frozen: bool, +) -> DispatchResult +``` +Forcibly changes the status of a specific asset, including ownership, management team, minimum balance, sufficiency status, and frozen status. + + +The `force_asset_status` function is a powerful administrative tool used to configure or reconfigure critical aspects of an asset’s behavior and control. It is typically used in scenarios requiring immediate intervention or significant restructuring. The function allows changing the owner, issuer, admin, and freezer accounts, setting the minimum balance required for the asset, and determining whether the asset should be considered sufficient for existential deposit purposes or frozen entirely. Due to its significant impact, this function is restricted to privileged origins authorized for such impactful changes. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being modified. +* `owner: AccountIdLookupOf` -- the account designated as the new owner of the asset. +* `issuer: AccountIdLookupOf` -- the account designated for issuing new units of the asset. +* `admin: AccountIdLookupOf` -- the account designated for administrative tasks. +* `freezer: AccountIdLookupOf` -- the account designated for freezing and thawing the asset. +* `min_balance: T::Balance` -- the new minimum balance required for the asset. +* `is_sufficient: bool` -- indicates if the asset should be considered sufficient for existential deposit purposes. +* `is_frozen: bool` -- indicates if the asset is to be frozen across all accounts. + +#### `approve_transfer` +```rust +pub fn approve_transfer( + origin: OriginFor, + id: T::AssetIdParameter, + delegate: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Approves a delegate to transfer a specified amount of a specific asset on behalf of the origin account. + + +The `approve_transfer` function allows the origin account to delegate transfer rights for a portion of their assets to another account, known as the delegate. This is useful in scenarios where temporary or limited rights to transfer assets are needed without transferring full ownership. The approval specifies an exact amount of the asset that the delegate is allowed to transfer. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. This function is often used in decentralized applications to enable features like spending allowances and automated payments. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which transfer rights are being granted. +* `delegate: AccountIdLookupOf` -- the account being granted the rights to transfer the specified amount of the asset. +* `amount: T::Balance` -- the amount of the asset that the delegate is approved to transfer. + +#### `cancel_approval` +```rust +pub fn cancel_approval( + origin: OriginFor, + id: T::AssetIdParameter, + delegate: AccountIdLookupOf, +) -> DispatchResult +``` +Cancels a previously granted approval for a delegate to transfer a specified asset on behalf of the origin account. + + +The `cancel_approval` function revokes the rights previously granted to a delegate to transfer a portion of the origin’s assets. This might be used when the need for the delegate’s rights has expired or if the original approval was made in error. The action ensures that the delegate can no longer transfer any amount of the specified asset from the origin’s account. This function is important for maintaining control and security over asset delegation and is a common feature in permission and rights management within asset systems. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which transfer rights are being revoked. +* `delegate: AccountIdLookupOf` -- the account from which the rights to transfer the asset are being revoked. + +#### `force_cancel_approval` +```rust +pub fn force_cancel_approval( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + delegate: AccountIdLookupOf, +) -> DispatchResult +``` +Forcibly cancels a previously granted approval for a delegate to transfer a specific asset on behalf of the owner account. + + +The `force_cancel_approval` function is an administrative action used to revoke transfer rights from a delegate, typically in situations of emergency or misuse. Unlike `cancel_approval`, this function can be initiated by an admin or authority other than the asset’s owner, reflecting its more forceful nature. It’s critical for situations where the asset owner cannot revoke the approval themselves or in governance scenarios where broader control is necessary. As with all forceful actions, the origin must have the necessary administrative privileges. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. Origin must be either ForceOrigin or Signed origin with the signer being the Admin account of the asset `id`. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the approval is being forcibly canceled. +* `owner: AccountIdLookupOf` -- the account that owns the asset and had previously granted transfer rights. +* `delegate: AccountIdLookupOf` -- the account from which the rights to transfer the asset are being forcibly revoked. + +#### `transfer_approved` +```rust +pub fn transfer_approved( + origin: OriginFor, + id: T::AssetIdParameter, + owner: AccountIdLookupOf, + destination: AccountIdLookupOf, + #[pallet::compact] amount: T::Balance, +) -> DispatchResult +``` +Executes a transfer of a specified amount of an asset from an owner to a destination account, using a previously granted approval. + + +The `transfer_approved` function allows a delegate (the origin) to transfer assets within the limits of an approval granted by the asset’s owner. This enables scenarios where third parties are given limited rights to manage assets. The function ensures that the delegate cannot exceed the approved amount or perform transfers without a valid approval. The `#[pallet::compact]` attribute is used for efficient storage of the `amount` parameter. It’s a critical function for flexible asset management in decentralized systems and applications. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being transferred. +* `owner: AccountIdLookupOf` -- the account that owns the asset and had previously granted transfer rights. +* `destination: AccountIdLookupOf` -- the account receiving the asset. +* `amount: T::Balance` -- the amount of the asset to be transferred, which must be within the approved amount. + +#### `touch` +```rust +pub fn touch( + origin: OriginFor, + id: T::AssetIdParameter +) -> DispatchResult +``` +Creates an asset account for non-provider assets. + + +A deposit will be taken from the signer account. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being updated. + +#### `refund` +```rust +pub fn refund( + origin: OriginFor, + id: T::AssetIdParameter, + allow_burn: bool, +) -> DispatchResult +``` +Refunds any reserved balance of a specific asset back to the asset’s owner or burns it based on the provided parameter. + + +The `refund` function is designed to handle situations where an asset’s reserved balance needs to be reconciled. This might occur in scenarios such as the completion of a contract, dissolution of a stake, or other instances where reserved funds are to be released. The `allow_burn` parameter determines if the refunded amount should be returned to the asset’s owner or burned, removing it from circulation. This function requires careful use and is typically controlled by the asset’s owner or an administrative authority. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the reserved balance is being refunded. +* `allow_burn: bool` -- indicates whether the reserved balance should be returned to the owner (false) or burned (true). + +#### `set_min_balance` +```rust +pub fn set_min_balance( + origin: OriginFor, + id: T::AssetIdParameter, + min_balance: T::Balance, +) -> DispatchResult +``` +Sets a new minimum balance for a specific asset. + + +The `set_min_balance` function is used to define or update the minimum balance required to hold a particular asset. This is crucial for preventing dust accounts and ensuring economic viability of the asset system. Changing the minimum balance affects all holders of the asset, as accounts below the new minimum might be cleaned up or require additional funding. This operation requires authorization from the asset’s owner or another privileged account, ensuring that the change is made with proper oversight and consideration of its effects. Only works if there aren’t any accounts that are holding the asset or if the new value of `min_balance` is less than the old one. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the minimum balance is being set. +* `min_balance: T::Balance` -- the new minimum balance required to hold the asset. + +#### `touch_other` +```rust +pub fn touch_other( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Create an asset account for `who`. + + +A deposit will be taken from the signer account. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset being updated. +* `who: AccountIdLookupOf` -- the account for which the asset’s timestamp is being updated. + +#### `refund_other` +```rust +pub fn refund_other( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Refunds the reserved balance of a specific asset back to another account’s owner. + + +The `refund_other` function is similar to the `refund` function but targets another account rather than the caller’s own. This allows administrators or authorized personnel to manage reserved balances across different accounts, potentially as part of a broader asset management or reconciliation process. This action might be necessary in scenarios such as contract completion, resolving disputes, or other instances where reserved funds need to be released or reallocated. The origin must have the necessary permissions to ensure that this function is used appropriately and by authorized entities. Useful if you are the depositor. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which the reserved balance is being refunded. +* `who: AccountIdLookupOf` -- the account from which the reserved balance will be refunded. + +#### `block` +```rust +pub fn block( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, +) -> DispatchResult +``` +Blocks a specific account from unprivileged transacting a specific asset. + + +The `block` function is used to restrict a particular account from performing unprivileged transactions involving a specified asset. This might be used in scenarios such as suspected fraud, regulatory compliance, or other security or administrative reasons. Once an account is blocked, it cannot transfer, mint, or burn the asset until it is unblocked. The origin must have the necessary administrative rights or privileges to enforce such restrictions, ensuring that the action is authorized and appropriate for the given context. + + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `id: T::AssetIdParameter` -- the identifier of the asset for which transactions are being blocked. +* `who: AccountIdLookupOf` -- the account that is being blocked from transacting the asset. diff --git a/docs/content/substrate-runtimes/pallets/aura_ext.mdx b/docs/content/substrate-runtimes/pallets/aura_ext.mdx new file mode 100644 index 00000000..b16465fe --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/aura_ext.mdx @@ -0,0 +1,45 @@ +--- +title: cumulus_pallet_aura_ext +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Purpose + +This pallet integrates parachain’s own block production mechanism (for example AuRa) into Cumulus parachain system. It allows: + +* to manage the unincluded blocks from the current slot +* to validate produced block against the relay chain + +## Configuration and Integration + +[GitHub Repository](https://github.com/paritytech/polkadot-sdk/tree/release-polkadot-v1.10.0/cumulus/pallets/aura-ext) + +There is no special config for this integration and it has no dispatchables, but you need to integrate it with other `parachain-system` crate: + +### Integrate `BlockExecutor` + +When you invoke the `register_validate_block` macro, you should provide `cumulus_pallet_aura_ext::BlockExecutor` to it to allow `aura-ext` to validate the blocks produced by `aura` + +```rust +cumulus_pallet_parachain_system::register_validate_block! { + Runtime = Runtime, + BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, +} +``` + +### Integrate `ConsensusHook` + +Also you might want to manage the consensus externally and control the segment that is not yet included (its capacity, speed and etc.) `aura-ext` provides the `FixedVelocityConsensusHook` that allows to check if we are still in the limits for the slot. + +```rust +impl cumulus_pallet_parachain_system::Config for Runtime { + ... + type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< + Runtime, + RELAY_CHAIN_SLOT_DURATION_MILLIS, + BLOCK_PROCESSING_VELOCITY, + UNINCLUDED_SEGMENT_CAPACITY, + >; +} +``` diff --git a/docs/content/substrate-runtimes/pallets/balances.mdx b/docs/content/substrate-runtimes/pallets/balances.mdx new file mode 100644 index 00000000..1ceeaf9a --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/balances.mdx @@ -0,0 +1,232 @@ +--- +title: pallet_balances +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[View on GitHub](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/balances/src/lib.rs) + +## Purpose + +The Balances pallet provides functions for: + +* Getting and setting free balances. +* Retrieving total, reserved, and unreserved balances. +* Repatriating a reserved balance to a beneficiary account that exists. +* Transferring a balance between accounts (when not reserved). +* Slashing an account balance. +* Account creation and removal. +* Managing total issuance. +* Setting and managing [locks](/substrate-runtimes/glossary#lock). + +## Config + +* Pallet-specific configs + * `RuntimeHoldReason` -- The overarching [hold](/substrate-runtimes/glossary#hold) reason. + * `RuntimeFreezeReason` -- The overarching [freeze](/substrate-runtimes/glossary#freeze) reason. + * `Balance` -- The balance of an account + * `DustRemoval` -- Handler for the unbalanced reduction when removing a dust account. + * `ExistentialDeposit` -- The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO! + * `AccountStore` -- The means of storing the balances of an account. + * `ReserveIdentifier` -- The ID type for [reserves](/substrate-runtimes/glossary#reserve). The use of reserves is deprecated in favor of holds. See [PR #12951](https://github.com/paritytech/substrate/pull/12951/) + * `FreezeIdentifier` -- The ID type for freezes. + * `MaxLocks` -- The maximum number of locks that should exist on an account. + * `MaxReserves` -- The maximum number of named reserves that can exist on an account. The use of reserves is deprecated in favor of holds. See [PR #12951](https://github.com/paritytech/substrate/pull/12951/) + * `MaxFreezes` -- The maximum number of individual freeze locks that can exist on an account at any time. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- [Weight](/substrate-runtimes/glossary#weight) information for extrinsics in this pallet. + +## Events + +* `Endowed(account, free_balance)` -- An account was created with some free balance. +* `DustLost(account, amount)` -- An account was removed whose balance was non-zero but below ExistentialDeposit, resulting in an outright loss. +* `Transfer(from, to, amount)` -- Transfer succeeded. +* `BalanceSet(who, free)` -- A balance was set by root. +* `Reserved(who, amount)` -- Some balance was reserved (moved from free to reserved). +* `Unreserved(who, amount)` -- Some balance was unreserved (moved from reserved to free). +* `ReserveRepatriated(from, to, amount, destination_status)` -- Some balance was moved from the reserve of the first account to the second account. The final argument indicates the destination balance type. +* `Deposit(who, amount)` -- Some amount was deposited (e.g. for transaction fees). +* `Withdraw(who, amount)` -- Some amount was withdrawn from the account (e.g. for transaction fees). +* `Slashed(who, amount)` -- Some amount was removed from the account (e.g. for misbehavior). +* `Minted(who, amount)` -- Some amount was minted into an account. +* `Burned(who, amount)` -- Some amount was burned from an account. +* `Suspended(who, amount)` -- Some amount was suspended from an account (it can be restored later). +* `Restored(who, amount)` -- Some amount was restored into an account. +* `Upgraded(who)` -- An account was upgraded. +* `Issued(amount)` -- Total issuance was increased by `amount`, creating a credit to be balanced. +* `Rescinded(amount)` -- Total issuance was decreased by `amount`, creating a debt to be balanced. +* `Locked(who, amount)` -- Some balance was locked. +* `Unlocked(who, amount)` -- Some balance was unlocked. +* `Frozen(who, amount)` -- Some balance was frozen. +* `Thawed(who, amount)` -- Some balance was thawed. +* `TotalIssuanceForced(old, new)` -- Total issuance was forcefully changed. + +## Errors + +* `VestingBalance` -- Vesting balance too high to send value. +* `LiquidityRestrictions` -- Account liquidity restrictions prevent withdrawal. +* `InsufficientBalance` -- Balance too low to send value. +* `ExistentialDeposit` -- Value too low to create an account due to existential deposit. +* `Expendability` -- Transfer/payment would kill the account. +* `ExistingVestingSchedule` -- A vesting schedule already exists for this account. +* `DeadAccount` -- Beneficiary account must pre-exist. +* `TooManyReserves` -- Number of named reserves exceed `MaxReserves`. +* `TooManyHolds` -- Number of holds exceeds `MaxHolds`. +* `TooManyFreezes` -- Number of freezes exceeds `MaxFreezes`. +* `IssuanceDeactivated` -- The issuance cannot be modified since it is already deactivated. +* `DeltaZero` -- The delta cannot be zero. + +## Dispatchables + +#### `transfer_allow_death` +```rust +pub fn transfer_allow_death( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, +) -> DispatchResult +``` +Transfers the `value` from `origin` to `dest`. + + +`allow_death` means, that if the account balance drops below the ExistentialDeposit limit, it might be reaped/deleted. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `dest: AccountIdLookupOf` -- recipient. +* `value: T::Balance` -- amount to transfer. + +#### `transfer_keep_alive` +```rust +pub fn transfer_keep_alive( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, +) -> DispatchResult +``` +Transfers the `value` from `origin` to `dest`. + + +`keep_alive` means, with a check that the transfer will not kill the origin account. + + + +99% of the time you want `transfer_allow_death` instead. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `dest: AccountIdLookupOf` -- recipient. +* `value: T::Balance` -- amount to transfer. + +#### `force_transfer` +```rust +pub fn force_transfer( + origin: OriginFor, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, +) -> DispatchResult +``` +Exactly as `transfer_allow_death`, except the origin must be root and the source account may be specified. + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, root) account. +* `source: AccountIdLookupOf` -- sender (forced by root). +* `dest: AccountIdLookupOf` -- recipient. +* `value: T::Balance` -- amount to transfer. + +#### `transfer_all` +```rust +pub fn transfer_all( + origin: OriginFor, + dest: AccountIdLookupOf, + keep_alive: bool, +) -> DispatchResult +``` +Transfer the entire transferable balance from the caller account. + + +This function only attempts to transfer _transferable_ balances. This means that any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be transferred by this function. + + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `dest: AccountIdLookupOf` -- recipient. +* `keep_alive: bool` -- A boolean to determine if the `transfer_all` operation should send all of the transferable funds (including existential deposits) the account has, causing the sender account to be killed (false), or transfer everything transferable, except at least the existential deposit, which will guarantee to keep the sender account alive (true). + +#### `force_unreserve` +```rust +pub fn force_unreserve( + origin: OriginFor, + who: AccountIdLookupOf, + amount: T::Balance, +) -> DispatchResult +``` +Unreserve some balance from a user by force. The caller (origin) must be root. + +***Params:*** + +* `origin: OriginFor` -- caller (and in this case, sender) account. +* `who: AccountIdLookupOf` -- the account for which the balance is to be unreserved. +* `amount: T::Balance` -- the amount of balance to be unreserved. + +#### `upgrade_accounts` +```rust +pub fn upgrade_accounts( + origin: OriginFor, + who: Vec, +) -> DispatchResultWithPostInfo +``` +Upgrade the specified account(s). + +***Params:*** + +* `origin: OriginFor` -- caller, must be `Signed`. +* `who: Vec` -- the account(s) to be upgraded. + + +This will waive the transaction fee if at least all but 10% of the accounts need to be upgraded. + + +#### `force_set_balance` +```rust +pub fn force_set_balance( + origin: OriginFor, + who: AccountIdLookupOf, + #[pallet::compact] new_free: T::Balance, +) -> DispatchResult +``` +Set the regular balance of a given account. The caller (origin) must be root. + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `who: AccountIdLookupOf` -- the account for which the balance will be set. +* `new_free: T::Balance` -- the amount of free balance that will be set to the given account. + +#### `force_adjust_total_issuance` +```rust +pub fn force_adjust_total_issuance( + origin: OriginFor, + direction: AdjustmentDirection, + #[pallet::compact] delta: T::Balance, +) -> DispatchResult +``` +Adjust the total issuance in a saturating way. + +Can only be called by root and always needs a positive delta. + +***Params:*** + +* `origin: OriginFor` -- caller, must be root. +* `direction: AdjustmentDirection` -- the direction of issuance change (increase or decrease). +* `delta: T::Balance` -- the amount of free balance that will be set to the given account. diff --git a/docs/content/substrate-runtimes/pallets/collator-selection.mdx b/docs/content/substrate-runtimes/pallets/collator-selection.mdx new file mode 100644 index 00000000..cb6b42cc --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/collator-selection.mdx @@ -0,0 +1,229 @@ +--- +title: collator_selection +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[View on GitHub](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/cumulus/pallets/collator-selection/src/lib.rs) + +## Purpose + +This pallet is needed to manage the set of [collators](/substrate-runtimes/glossary#collator) for each session and to provision the next session with the actual list of collators. + +## Config + +* Pallet-specific configs + * `UpdateOrigin` — defines the list of origins that are able to modify the settings of collators (e.g. set and remove list of [invulnerables](/substrate-runtimes/glossary#invulnerable), desired [candidates](/substrate-runtimes/glossary#candidate), [candidacy bond](/substrate-runtimes/glossary#candidacy-bond)). This type should implement the trait `EnsureOrigin`. + * `PotId` — id of account that will hold a [Pot](/substrate-runtimes/glossary#pot). + * `MaxCandidates` — maximum number of candidates + * `MinEligibleCollators` — minimum number of collators to collect for the session + * `MaxInvulnerables` — maximum number of invulnerables + * `KickThreshold` — number of blocks that should pass since the last block produced by a candidate collator for it to be removed from a candidates list and not participate in collation for the next session. + * `ValidatorId` — type for validator id + * `ValidatorIdOf` — type that allows to convert AccountId to ValidatorId + * `ValidatorRegistration` — type that checks that AccountId has its validator keys registered. +* Common configs: + * `RuntimeEvent` + * `Currency` + * `WeightInfo` + +## Dispatchables + +### `set_invulnerables` +```rust +pub fn set_invulnerables(new: Vec) +``` +Sets a new list of invulnerable collators. The call must be signed and origin of the call must fulfill the EnsureOrigin check. + + +This call does not maintain the mutual exclusiveness of candidates and invulnerables lists. + + +***Params:*** + +* `new: Vec` — a list of AccountIds of new invulnerables + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `TooFewEligibleCollators` — empty list of invulnerables was submitted and the number of candidates is smaller than `Config::MinEligibleCollators` +* `TooManyInvulnerables` — submitted list length is longer than `Config::MaxInvulnerables` + +***Events:*** + +* `InvalidInvulnerableSkipped(account_id)` — submitted invulnerable does not have validator key or it is not registered +* `NewInvulnerables(invulnerables)` — new invulnerable list was set + +### `set_desired_candidates` +```rust +pub fn set_desired_candidates(max: u32) +``` +Set a new maximum possible number of candidates. If it is higher than `Config::MaxCandidates`, you should consider to rerun the benchmarks. The caller’s origin should fulfill the `Config::EnsureOrigin` check. + +***Params:*** + +* `max: u32` — new desired candidates number + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. + +***Events:*** + +* `NewDesiredCandidates(desired_candidates)` + +### `set_candidacy_bond` +```rust +pub fn set_candidacy_bond(bond: BalanceOf) +``` +Set the amount for the deposit to be a candidate for collator. + +***Params:*** + +* `bond: BalanceOf` — new amount for candidate deposit + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. + +***Events:*** + +* `NewCandidacyBond(bond_amount)` + +### `register_as_candidate` +```rust +pub fn register_as_candidate() +``` +Register the caller as a collator candidate. Caller should be signed, have registered session keys and have amount needed for candidacy bond deposit. If successful, candidate will participate in collation process starting from the next session. + +***Errors:*** + +* `BadOrigin` — call is not signed +* `TooManyCandidates` — number of collators is already at its maximum (specified in `desired_candidates` getter) +* `AlreadyInvulnerable` — caller is already in invulnerable collators list, it does not need to be a candidate to become a collator +* `NoAssociatedValidatorId` — caller does not have a session key. +* `ValidatorNotRegistered` — caller session key is not registered +* `AlreadyCandidate` — caller is already in candidates list +* `InsufficientBalance` — candidate does not have enough funds for deposit for candidacy bond +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `CandidateAdded(account_id, deposit)` + +### `leave_intent` +```rust +pub fn leave_intent() +``` +Unregister the caller from being a collator candidate. If successful, deposit will be returned and during the next session change collator will no longer participate in collation process. This call must be signed. + +***Errors:*** + +* `BadOrigin` — call is not signed +* `TooFewEligibleCollators` — the number of collators for the next session will be less than `Config::MinEligibleCollators` in case of unregistration so the process is stopped. +* `NotCandidate` — caller is not on candidate list, nothing to unregister + +***Events:*** + +* `CandidateRemoved(account_id)` + +### `add_invulnerable` +```rust +pub fn add_invulnerable(who: T::AccountId) +``` +Add a new invulnerable. Call must be signed and caller pass `Config::EnsureOrigin` check. If a new invulnerable was previously a candidate, it will be removed from them. + +***Params:*** + +* `who: T::AccountId` — an account to add to invulnerables list + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `NoAssociatedValidatorId` — new invulnerable does not have a session key. +* `ValidatorNotRegistered` — new invulnerable session key is not registered +* `AlreadyInvulnerable` — caller is already in invulnerable collators list + +***Events:*** + +* `InvulnerableAdded(account_id)` + +### `remove_invulnerable` +```rust +pub fn remove_invulnerable(who: T::AccountId) +``` +Remove an invulnerable from the list. Call must be signed and caller pass `Config::EnsureOrigin` check. + +***Params:*** + +* `who: T::AccountId` — an account to remove from invulnerables list + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `TooFewEligibleCollators` — the number of invulnerable will become less than `Config::MinEligibleCollators` after the removal. +* `NotInvulnerable` — the `who` is not an invulnerable + +***Events:*** + +* `InvulnerableRemoved(account_id)` + +### `update_bond` +```rust +pub fn update_bond(new_deposit: BalanceOf) +``` +Update the candidacy bond of origin to the new value. + +***Params:*** + +* `new_deposit: BalanceOf` — new value for the candidacy bond + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `DepositTooLow` - new deposit is smaller than required candidacy bond. +* `NotCandidate` - caller’s origin is not a candidate +* `IdenticalDeposit` - deposit have not changed +* `InsufficientBalance` — candidate does not have enough funds for deposit for candidacy bond +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. +* `InvalidUnreserve` - after the unreserve the number of candidates becomes less than desired. +* `InsertToCandidateListFailed` - candidate list is at maximum capacity. Should not happen in usual scenarios. Chain is misconfigured. + +***Events:*** + +* `CandidateBondUpdated(account_id, deposit)` + +### `take_candidate_slot` +```rust +pub fn take_candidate_slot( + deposit: BalanceOf, + target: T::AccountId, +) +``` +Try to replace the target's candidacy by making a bigger candidacy bond. + +***Params:*** + +* `deposit: BalanceOf` — value for the candidacy bond +* `target: T::AccountId` - target candidate to replace + +***Errors:*** + +* `BadOrigin` — caller’s origin does not fulfill the `Config::EnsureOrigin` check. +* `AlreadyInvulnerable` — caller is already in invulnerable collators list. +* `InsufficientBond` - the deposit is less the candidacy bond or target’s deposit. +* `NoAssociatedValidatorId` — caller does not have a session key. +* `ValidatorNotRegistered` — caller session key is not registered. +* `AlreadyCandidate` — caller is already in candidates list. +* `TargetIsNotCandidate` - target is not in candidate list. +* `InsufficientBalance` — candidate does not have enough funds for deposit for candidacy bond +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `CandidateReplaced(old, new, deposit)` diff --git a/docs/content/substrate-runtimes/pallets/message-queue.mdx b/docs/content/substrate-runtimes/pallets/message-queue.mdx new file mode 100644 index 00000000..1c604b87 --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/message-queue.mdx @@ -0,0 +1,151 @@ +--- +title: pallet_message_queue +--- + +Branch/Release: `release-polkadot-v1.10.0` + +[Source Code](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/message-queue/src/lib.rs) + +## Purpose + +Flexible FRAME pallet for implementing message queues. This pallet can also initiate message processing using the `MessageProcessor` (see `Config`). + +## Config +* Pallet-specific configs: + * `MessageProcessor` -- Processor for messages + * `Size` -- Page/heap size type. + * `QueueChangeHandler` -- Code to be called when a message queue changes - either with items introduced or removed. + * `QueuePausedQuery` -- Queried by the pallet to check whether a queue can be serviced. + * `HeapSize` -- The size of the page; this also serves as the maximum message size which can be sent. + * `MaxStale` -- The maximum number of stale pages (i.e. of overweight messages) allowed before culling can happen. Once there are more stale pages than this, then historical pages may be dropped, even if they contain unprocessed overweight messages. + * `ServiceWeight` -- The amount of weight (if any) which should be provided to the message queue for servicing enqueued items `on_initialize`. This may be legitimately `None` in the case that you will call `ServiceQueues::service_queues` manually or set [`Self::IdleMaxServiceWeight`] to have it run in `on_idle`. + * `IdleMaxServiceWeight` -- The maximum amount of weight (if any) to be used from remaining weight `on_idle` which should be provided to the message queue for servicing enqueued items `on_idle`. Useful for parachains to process messages at the same block they are received. If `None`, it will not call `ServiceQueues::service_queues` in `on_idle`. +* Common configs: + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- Weight information for extrinsics in this pallet. + +## Dispatchables + +#### `execute_overweight` +```rust +pub fn execute_overweight( + origin: OriginFor, + message_origin: MessageOriginOf, + page: PageIndex, + index: T::Size, + weight_limit: Weight, +) -> DispatchResultWithPostInfo +``` +Execute an overweight message. + + +Temporary processing errors will be propagated whereas permanent errors are treated as success condition. + + + +The `weight_limit` passed to this function does not affect the `weight_limit` set in other parts of the pallet. + + +***Params:*** + +* `origin: OriginFor` -- Must be `Signed`. +* `message_origin: MessageOriginOf` -- indicates where the message to be executed arrived from (used for finding the respective queue that this message belongs to). +* `page: PageIndex` -- The page in the queue in which the message to be executed is sitting. +* `index: T::Size` -- The index into the queue of the message to be executed. +* `weight_limit: Weight` -- The maximum amount of weight allowed to be consumed in the execution +of the message. This weight limit does not affect other parts of the pallet, and it is only used for this call of `execute_overweight`. + +***Errors:*** + +* `QueuePaused` -- if the queue that overweight message to be executed belongs to is paused. +* `NoPage` -- if the page that overweight message to be executed belongs to does not exist. +* `NoMessage` -- if the overweight message could not be found. +* `Queued` -- if the overweight message is already scheduled for future execution. +For a message to be labeled as overweight, the pallet must have previously attempted execution and +encountered failure due to insufficient weight for processing. Once marked as overweight, the message +is excluded from the queue for future execution. +* `AlreadyProcessed` -- if the overweight message is already processed. +* `InsufficientWeight` -- if the `weight_limit` is not enough to execute the overweight message. +* `TemporarilyUnprocessable` -- if the message processor `Yield`s execution of this message. This means processing should be reattempted later. + +***Events:*** + +* `ProcessingFailed(id, origin, error)` +* `Processed(id, origin, weight_used, success)` + +#### `reap_page` +```rust +pub fn reap_page( + origin: OriginFor, + message_origin: MessageOriginOf, + page_index: PageIndex, +) -> DispatchResult +``` + +Remove a page which has no more messages remaining to be processed or is stale. + +***Params:*** + +* `origin: OriginFor` -- Must be `Signed`. +* `message_origin: MessageOriginOf` -- indicates where the messages arrived from (used for finding the respective queue that this page belongs to). +* `page_index: PageIndex` -- The page to be reaped + +***Errors:*** + +* `NotReapable` -- if the page is not stale yet. +* `NoPage` -- if the page does not exist. + +***Events:*** + +* `PageReaped(origin, index)` -- the queue (origin), and the index of the page + +## Important Mentions and FAQs + + +The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always stay within the required limit. This implies that the message processor hook can calculate the weight of a message without executing it. + + +#### How does this pallet work under the hood? + +* This pallet utilizes queues to store, enqueue, dequeue, and process messages. +* Queues are stored in `BookStateFor` storage, with their origin serving as the key (so, we can identify queues by their origins). +* Each message has an origin (message_origin), that defines into which queue the message will be stored. +* Messages are stored by being appended to the last `Page` of the Queue’s Book. A Queue is a book along with the MessageOrigin for that book. +* Each book keeps track of its pages, and the state (begin, end, count, etc.) +* Each page also keeps track of its messages, and the state (remaining, first, last, etc.) +* `ReadyRing` contains all ready queues as a double-linked list. A Queue is ready if it contains at least one Message which can be processed. +* `ServiceHead` is a pointer to the `ReadyRing`, pointing at the next `Queue` to be serviced. Service means: attempting to process the messages. + +**Execution:** + +* `service_queues` → returns the weight that is consumed by this function + * we will process a queue, till either: + * there is no more message left + * if there is no more message left in the queue, we won’t stop, service_head will proceed with the next queue + * or weight is insufficient + * if weight is insufficient for the next message in the queue, service_head will try to switch to next queue, and try to process message from that queue. This will go on, until it visits every queue, and no message can be processed. Only then, it will stop. + * each call to `service_queues`, we will bump the header, and start processing the next queue instead of the previous one to prevent starvation + * Example: + * service head is on queue 2 + * we called `service_queues`, which bumped the service head to queue 3 + * we processed messages from queue 3, + * but weight was insufficient for the next message in queue 3, + * so we switched to queue 4, (we don’t bump the service head for that) + * weight was insufficient for queue 4 and other queues as well, and we made a round trip across queues, till we reach queue 3, and we stopped. + * `service_queues` call finished + * service head is on queue 3 + * we called `service_queue` again, which bumped the service head to queue 4 (although there are still messages left in queue 3) + * we continue processing from queue 4. + * but, to preserve priority, if we made a switch to a new queue due to weight, we don’t bump the service head. So, the next call, will be starting on the queue where we left off. + * Example: + * service head is on queue 2 + * we called `service_queues`, which bumped the service head to queue 3 + * we processed messages from queue 3, + * but weight was insufficient for the next message in queue 3, + * so we switched to queue 4, (we don’t bump the service head for that) + * we processed a message from queue 4 + * weight was insufficient for queue 4 and other queues as well, and we made a round trip across queues, till we reach queue 3, and we stopped. + * `service_queues` call finished + * service head is on queue 3 (there are still messages in queue 3) + * we called `service_queue` again, which bumped the service head to queue 4 + * we continue processing from queue 4, although we were processing queue 4 in the last call diff --git a/docs/content/substrate-runtimes/pallets/multisig.mdx b/docs/content/substrate-runtimes/pallets/multisig.mdx new file mode 100644 index 00000000..08c0c93b --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/multisig.mdx @@ -0,0 +1,230 @@ +--- +title: pallet_multisig +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[GitHub Repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/multisig/src/lib.rs) + +## Purpose + +This module enables multi-signature operations in your runtime. It allows multiple signed origins (accounts) to coordinate and dispatch a call. For the call to execute, the threshold number of accounts from the set (signatories) must approve it. + +## Config + +* Pallet-specific configs + * `DepositBase` -- The base amount of currency needed to [reserve](/substrate-runtimes/glossary#reserve) for creating a multisig execution or to store a dispatch call for later. Recall: The deposit to be made by the account that creates the multisig is: `threshold * DepositFactor + DepositBase` + * `DepositFactor` -- The amount of currency needed per unit threshold when creating a multisig execution. Recall: The deposit to be made by the account that creates the multisig is: `threshold * DepositFactor + DepositBase` + * `MaxSignatories` -- The maximum amount of signatories allowed in the multisig. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `RuntimeCall` -- The overarching call type. + * `Currency` -- The currency mechanism. + * `WeightInfo` -- [Weight](/substrate-runtimes/glossary#weight) information for extrinsics in this pallet. + +## Dispatchables + +#### `approve_as_multi` +```rust +pub fn approve_as_multi( + threshold: u16, + other_signatories: Vec, + maybe_timepoint: Option>>, + call_hash: [u8; 32], + max_weight: Weight +) -> DispatchResultWithPostInfo +``` +Register approval for a dispatch to be made from a deterministic composite account. + +Since the first register (from origin) counts as a vote as well, only `threshold - 1` additional votes are necessary from `other_signatories`. + +Payment: `DepositBase` will be reserved if this is the first approval, plus `threshold` times `DepositFactor`. It is returned once this dispatch happens or is cancelled. + +The dispatch origin for this call must be **Signed**. + +The result is equivalent to the dispatched result if the threshold is exactly 1. Otherwise on success, the result is Ok and the result from the interior call, if it was executed, may be found in the deposited MultisigExecuted event. + + +If this is the final approval, you will want to use `as_multi` instead. `approve_as_multi` won’t trigger the dispatch, even if there are enough approvals. + + +The reason is: `as_multi` needs `call` parameter, whereas `approve_as_multi` needs `call_hash`. `call_hash` is enough to find the multisig operation entry in the storage, and increment the vote count. We don’t need the `call` itself to increment the vote count. Whereas, if `call` is supplied, and if we have enough approvals, the logic for execution will be triggered. This is a design choice. + +***Params:*** + +* `threshold: u16` -- The total number of approvals for this dispatch before it is executed. Cannot be 1. If you want the threshold to be 1, use as_multi_threshold_1 instead. +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `maybe_timepoint: Option>>` -- Refers to the timepoint of the creation/registration of this call to the multisig storage. If this is the first approval, then this must be None (business logic derives it automatically). If it is not the first approval, then it must be Some, with the timepoint (block number and transaction index) of the first approval transaction. +* `call_hash: [u8; 32]` -- The hash of the call to be executed. +* `max_weight: Weight` -- Maximum weight limit for the call’s execution. + +***Errors:*** + +* `MaxWeightTooLow` -- when the call requires more weight to be executed, the call won’t be executed and an error will be returned. +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `NoTimepoint` -- when this is not the first call, and no timepoint is given +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `UnexpectedTimepoint` -- when this is the first call, and a timepoint is given +* `AlreadyApproved` -- when a signatory tries to approve more than once + +***Events:*** + +* `MultisigApproved(approving, timepoint, multisig, call_hash)` -- when multisig call is approved. This also gives information on who was the last approver (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). +* `NewMultisig(approving, multisig, call_hash)` -- when a multisig call is created. This also gives information on who was the creator (also the first approver) (`approving`), id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +#### `as_multi` +```rust +pub fn as_multi( + threshold: u16, + other_signatories: Vec, + maybe_timepoint: Option>>, + call: Box<::RuntimeCall>, + max_weight: Weight +) -> DispatchResultWithPostInfo +``` + + +Unless this is the final approval, you will generally want to use `approve_as_multi` instead, since it only requires a hash of the call. + + +`call_hash` is enough to find the multisig operation entry in the storage, and increment the vote count. We don’t need the `call` itself to increment the vote count. + +Whereas, if `call` is supplied, and if we have enough approvals, the logic for execution will be triggered. + +`as_multi` is nearly identical to `approve_as_multi`, the only difference being `call` vs `call_hash`. + +Register approval for a dispatch to be made from a deterministic composite account if approved by a total of `threshold - 1` of `other_signatories`. + +If there are enough, then dispatch the call. + +Payment: `DepositBase` will be reserved if this is the first approval, plus `threshold` times `DepositFactor`. It is returned once this dispatch happens or is cancelled. + +The dispatch origin for this call must be **Signed**. + + +When as_multi is called, if it succeeds (dispatches the call), the multisig operation will be removed from the storage. Meaning, another person cannot trigger the same multisig call. They need to create the same one from scratch again. + + +***Params:*** + +* `threshold: u16` -- The total number of approvals for this dispatch before it is executed. Cannot be 1. If you want the threshold to be 1, use as_multi_threshold_1 instead. +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `maybe_timepoint: Option>>` -- Refers to the timepoint of the creation/registration of this call to the multisig storage. If this is the first approval, then this must be None (business logic derives it automatically). If it is not the first approval, then it must be Some, with the timepoint (block number and transaction index) of the first approval transaction. +* `call: Box<::RuntimeCall>` -- The call to be executed. +* `max_weight: Weight` -- Maximum weight limit for the call’s execution. + +***Errors:*** + +* `MaxWeightTooLow` -- when the call requires more weight to be executed, the call won’t be executed and an error will be returned. +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `NoTimepoint` -- when this is not the first call, and no timepoint is given +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `UnexpectedTimepoint` -- when this is the first call, and a timepoint is given +* `AlreadyApproved` -- when a signatory tries to approve more than once + +***Events:*** + +* `MultisigExecuted(approving, timepoint, multisig, call_hash, result)` -- when multisig call is executed. This also gives information on who was the last approver (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`), and the `result`. +* `MultisigApproved(approving, timepoint, multisig, call_hash)` -- when multisig call is approved. This also gives information on who was the last approver (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). +* `NewMultisig(approving, multisig, call_hash)` -- when a multisig call is created. This also gives information on who was the creator (also the first approver) (`approving`), id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +#### `cancel_as_multi` +```rust +pub fn cancel_as_multi( + threshold: u16, + other_signatories: Vec, + timepoint: Timepoint>, + call_hash: [u8; 32] +) -> DispatchResult +``` +Cancel a pre-existing, ongoing multisig transaction. Any deposit reserved previously for this operation will be unreserved on success. + + +Only the owner of the multisig operation can cancel it (not even other signatories). + + +Cancel operation does not require multi-signature. The owner calling this function is enough on its own to cancel this. + + +Multisig operations are stored in the storage with double keys, hence other_signatories and threshold are necessary for the identification of the multisig operation. + +***Params:*** + +* `threshold: u16` -- The total number of approvals for this dispatch before it is executed. Cannot be 1. If you want the threshold to be 1, use as_multi_threshold_1 instead. +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `timepoint: Timepoint>` -- Refers to the timepoint of the creation/registration of this call to the multisig storage. This is required for cancellation as it identifies the specific multisig operation. +* `call_hash: [u8; 32]` -- The hash of the call to be executed. + +***Errors:*** + +* `MinimumThreshold` -- when the threshold is not greater than 1 +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` +* `WrongTimepoint` -- when this is not the first call, and the wrong timepoint is given +* `NotFound` -- when the multisig call is not found in the storage +* `NotOwner` -- when someone who is not the owner tried to cancel + +***Events:*** + +* `MultisigCancelled(cancelling, timepoint, multisig, call_hash)` -- when multisig call is cancelled. This also gives information on who cancelled (`cancelling`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`). + +#### `as_multi_threshold_1` +```rust +pub fn as_multi_threshold_1( + other_signatories: Vec, + call: Box<::RuntimeCall> +) -> DispatchResultWithPostInfo +``` +Immediately dispatch a multi-signature call using a single approval from the caller. + +The dispatch origin for this call must be **Signed**. + +A real use case scenario could be for example a business that has a bank account and says "any one of the 3 founders can authorize payments from this account". + +***Params:*** + +* `other_signatories: Vec` -- The accounts (other than the sender) who can approve this dispatch. May not be empty. +* `call: Box<::RuntimeCall>` -- The call to be executed. + +***Errors:*** + +* `TooFewSignatories` -- when `other_signatories` list is empty +* `TooManySignatories` -- when `other_signatories` length is greater than `MaxSignatories` + +***Events:*** + +* `MultisigExecuted(approving, timepoint, multisig, call_hash, result)` -- when multisig call is executed immediately. This also gives information on who executed (`approving`), the `timepoint` of the call, id of the multisig call (`multisig`), hash of the multisig call (`call_hash`), and the `result`. + +## Important Mentions and FAQ's + +**Big Picture Examples** + +* funding the multisig account (same for `1-out-of-n multisig accounts`, and `m-out-of-n multisig accounts`) + * find the public key of the shared account + * use polkadot JS to create a multisig account + * or, use `multi_account_id` in the source code + * fund the account by simply sending some money to the derived public key + * 👉 check this documentation if you have questions about accounts and their creation [multisig_accounts](/substrate-runtimes/misc/multisig-accounts) +* `m-out-of-n multisig account` example: + * Alice, Bob, Charlie, and Dylan want to create a shared account, and the threshold for this shared account should be 3 (at least 3 people should approve the transactions for this account). + * One of the signatories should create the multisig operation by calling `as_multi`, and should provide the necessary arguments + * Others can approve this call using `approve_as_multi`, however, `approve_as_multi` will not dispatch the call. This will only increase the approval amount. + * If the approver wants to dispatch the call as well, they should use `as_multi` instead. + * **Niche Details:** + * If a signatory tries to call `approve_as_multi` after the threshold is surpassed, they will get an error: `AlreadyApproved`, because this action is meaningless. + * A signatory can call `as_multi` to dispatch the call, even if they approved the same multisig before. +* `1-out-of-n multisig account` example: + * Alice, Bob, and Charlie want to create a shared account, and the threshold for this shared account should be 1 (any person can spend from this account, without any approval). + * any of them can call `as_multi_threshold_1`, and spend the money without requiring approval from others + * **Niche Details:** + * `as_multi_threshold_1` does not store multisig operations in storage. Because there is no need to do so. + * Q: if we are not storing it, how can other signatories use this shared account in the future? + * A: the account’s balance is stored in blockchain. The account’s public key is derived from the public keys of the signatories, combined with the threshold. So, the caller has permission to spend the balance that belongs to the derived public key. + * we are not storing `1-out-of-n multisig operations`, but we are storing `m-out-of-n multisig operations`, since we have to keep track of the approvals. + * It does not make sense to cancel `1-out-of-n multisig operations`, because `as_multi_threshold_1` immediately dispatches the call, there is no state in which canceling is a viable option for `1-out-of-n multisig operations`. diff --git a/docs/content/substrate-runtimes/pallets/parachain-system.mdx b/docs/content/substrate-runtimes/pallets/parachain-system.mdx new file mode 100644 index 00000000..3ebd0105 --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/parachain-system.mdx @@ -0,0 +1,119 @@ +--- +title: parachain_system +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[GitHub Repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/cumulus/pallets/parachain-system/src/lib.rs) + +## Purpose + +This pallet is a core element of each parachain. It will: + +* Aggregate information about built blocks +* Process binary code upgrades +* Process incoming messages from both relay chain and other parachains (if a channel is established between them) +* Send outgoing messages to relay chain and other parachains +* Build collation info when requested by [collator](../glossary#collator) + +## Config + +* Pallet-specific configs: + * `OnSystemEvent` — a handler that will be called when new [validation data](../glossary#validation-data) will be set (once each block). New validation data will also be passed to it. Look to `trait OnSystemEvent` for more details. + * `SelfParaId` — getter for a parachain id of this chain + * `OutboundXcmpMessageSource` — source of outgoing XCMP messages. It is queried in `finalize_block` and later included into collation information + * `DmpQueue` — a handler for the incoming **downward** messages from relay chain + * `ReservedDmpWeight` — [weight](../glossary#weight) reserved for DMP message processing. This config seems to not be used as the function that processes these messages (`enqueue_inbound_downward_messages`) returns used weight. + * `XcmpMessageHandler` — a handler for the incoming _horizontal_ messages from other parachains + * `ReservedXcmpWeight` — default weight limit for the XCMP message processing. May be overridden by storage `ReservedXcmpWeightOverride`. If incoming messages in block will exceed the weight limit, they won’t be processed. + * `CheckAssociatedRelayNumber` — type that implements `trait CheckAssociatedRelayNumber` . Currently there are three implementations: no check (`AnyRelayNumber`), strict increase (`RelayNumberStrictlyIncreases`), monotonic increase (`RelayNumberMonotonicallyIncreases`). It is needed to maintain some order between blocks in relay chain and parachain. + * `ConsensusHook` — this is a feature-enabled config for the management of the [unincluded segment](../glossary#unincluded-segment). Requires the implementation of `trait ConsensusHook`. There are several implementations of it, in `parachain-system` crate (`FixedCapacityUnincludedSegment`) and in `aura-ext` crate (`FixedVelocityConsensusHook`). It is needed to maintain the logic of segment length handling. +* Common parameters for all pallets: + * `RuntimeEvent` + * `WeightInfo` + +## Dispatchables + +#### `set_validation_data` +```rust +pub fn set_validation_data( + data: ParachainInherentData, +) +``` +This call is an inherent, you can’t call this from another dispatchable or from client side. This call sets up validation data for collation, processes code upgrades and updates unincluded segments. + +#### `sudo_send_upward_message` +```rust +pub fn sudo_send_upward_message( + message: UpwardMessage, +) +``` +Send a message to relay as a sudo. + +**Params:** + +* `message` — a vec of bytes that represents a message that you send to the relay + +**Errors:** + +* `BadOrigin` — call was made not from a sudo + +#### `authorize_upgrade` +```rust +pub fn authorize_upgrade( + code_hash: T::Hash, + check_version: bool, +) +``` + +Authorize the upgrade. This call will put the hash and flag to the storage `AuthorizedUpgrade`. This call must be made as a sudo. + +**Params:** + +* `code_hash` — hash of the authorized runtime binary +* `check_version` — flag that indicates that the code should be checked for the possibility to upgrade. It will happen during the upgrade process itself. + +**Errors:** + +* `BadOrigin` — call was made not from a sudo + +**Events:** + +* `UpgradeAuthorized(code_hash)` + +#### `enact_authorized_upgrade` +```rust +pub fn enact_authorized_upgrade( + code: Vec, +) +``` + +Validate and perform the authorized upgrade. + +**Params:** + +* `code` — runtime binary for the upgrade + +**Errors:** + +* `NothingAuthorized` — there is no authorized upgrade, call `authorize_upgrade` in advance +* `Unauthorized` — there is another upgrade authorized + +## Important Mentions and FAQ's + +### Pallet's workflow + +* Block Initialization + * Remove already processed [validation code](../glossary#validation-code) + * Update `UnincludedSegment` with latest parent hash + * Cleans up `ValidationData` and other functions. + * Calculate weights for everything that was done in `on_finalize` hook +* Inherents — `set_validation_data` call + * Clean the included segments from `UnincludedSegment` and update the `AggregatedUnincludedSegment` + * Update `ValidationData`, `RelayStateProof` and other configs from relay. + * Process the `ValidationCode` upgrade +* Block Finalization + * Enqueue all received messages from relay chain and other parachains + * Update `UnincludedSegment` and `AggregatedUnincludedSegment` with the latest block data diff --git a/docs/content/substrate-runtimes/pallets/proxy.mdx b/docs/content/substrate-runtimes/pallets/proxy.mdx new file mode 100644 index 00000000..d0b8c74a --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/proxy.mdx @@ -0,0 +1,326 @@ +--- +title: pallet_proxy +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[GitHub Repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/proxy/src/lib.rs) + +## Purpose + +This pallet enables delegation of rights to execute certain call types from one origin to another. + +## Config + +* Pallet-specific configs: + * `ProxyType` -- a type that describes different variants of [proxy](/substrate-runtimes/glossary#proxy). It must implement `Default` trait and `InstanceFilter` trait. + * `ProxyDepositBase` -- a base amount of currency that defines a deposit for proxy creation. + * `ProxyDepositFactor` -- an amount of currency that will be frozen along with the `ProxyDepositBase` for each additional proxy. + * `MaxProxies` -- maximum number of proxies that single account can create. + * `MaxPending` -- maximum number of [announcements](/substrate-runtimes/glossary#announcement) that can be made per account. + * `CallHasher` -- a type implementing a `Hash` trait. Will be used to hash the executed call. + * `AnnouncementDepositBase` -- a base amount of currency that defines a deposit for announcement creation. + * `AnnouncementDepositFactor` -- an amount of currency that will be frozen along with the `AnnouncementDepositBase` for each additional announcement. +* Common configs: + * `RuntimeEvent` + * `RuntimeCall` + * `Currency` + +## Dispatchables + +#### `add_proxy` +```rust +pub fn add_proxy( + delegate: <::Lookup as StaticLookup>::Source, + proxy_type: T::ProxyType, + delay: BlockNumberFor +) +``` +Create a new `Proxy` that allows `delegate` to execute calls that fulfill `proxy_type` check on your origin’s behalf. + +The origin must be signed for this call. + +This call will take (or modify) a deposit based on number of proxies created by [delegator](/substrate-runtimes/glossary#delegator) and calculated by this formula: `ProxyDepositBase + ProxyDepositFactor * ` + +There may not be more proxies than `MaxProxies` + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — account that will become a proxy for the origin +* `proxy_type: T::ProxyType` — type of calls that will be allowed for the delegate +* `delay: BlockNumberFor` — number of blocks that needs to happen between announcement and call for this proxy + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — delegate not found +* `NoSelfProxy` — delegate and call origin is the same account +* `Duplicate` — proxy already exists +* `TooMany` — too many proxies created for this delegate with this type and the same [delay](/substrate-runtimes/glossary#delay) +* `InsufficientBalance` — delegator does not have enough funds for deposit for proxy creation +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `ProxyAdded(delegator, delegatee, proxy_type, delay)` + +#### `announce` +```rust +pub fn announce( + real: AccountIdLookupOf, + call_hash: CallHashOf, +) +``` +Announce a call that will be executed using a proxy. As a result announcement will be created. You must create an announcement if the proxy you are using specified a delay greater than zero. In that case you will be able to execute a call after the number of blocks specified by delay. + +The origin must be signed for this call. + +This call will take (or modify) a deposit calculated by this formula: `AnnouncementDepositBase + AnnouncementDepositFactor * ` + +There may not be more announcements than `MaxPending` + +***Params:*** + +* `real: AccountIdLookupOf` — the account on which behalf this call will be made +* `call_hash: CallHashOf` — hash of the call that is going to be made + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `real` account not found +* `NotProxy` — there is no proxy between the caller and real +* `TooMany` — there is more announcements for this sender than specified in `MaxPending` +* `InsufficientBalance` — caller does not have enough funds for deposit for announcement creation +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `Announced(real, proxy, call_hash)` + +#### `proxy` +```rust +pub fn proxy( + real: AccountIdLookupOf, + force_proxy_type: Option, + call: Box<::RuntimeCall>, +) +``` + +Dispatch a `call` on behalf of `real` account using a proxy that was created in advance. Proxy must be created for the call sender to execute the call. + +The origin must be signed for this call. + +If the proxy requires an announcement before the call, this dispatchable will fail. + +***Params:*** + +* `real: AccountIdLookupOf` — the account on which behalf this call will be made +* `force_proxy_type: Option` — specific [proxy type](/substrate-runtimes/glossary#proxy-type) to get proxy for. If not specified, first one found in the storage will be used. +* `call: Box<::RuntimeCall>` — a call to execute + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `real` account not found +* `NotProxy` — there is no proxy between the caller and real +* `Unannounced` — there was a delay specified but not fulfilled + +***Events:*** + +* `ProxyExecuted(result)` + +#### `proxy_announced` +```rust +pub fn proxy_announced( + delegate: <::Lookup as StaticLookup>::Source, + real: <::Lookup as StaticLookup>::Source, + force_proxy_type: Option, + call: Box<::RuntimeCall> +) +``` + +Execute previously announced call using a proxy and remove the announcement. Proxy must be created for the call sender to execute the call. + +The origin must be signed for this call. + +This call will fail if delay after announcement have not passed or call was not announced. + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — the account proxy was given to and who announced the call +* `real: <::Lookup as StaticLookup>::Source` — delegator of the proxy, on whose behalf call will be executed +* `force_proxy_type: Option` — specific proxy type to get proxy for. If not specified, first one found in the storage will be used. +* `call: Box<::RuntimeCall>` — a call to execute + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `real` or `delegate` account not found +* `NotProxy` — there is no proxy between the `delegate` and `real` +* `Unannounced` — there was a delay specified but not fulfilled or call was not announced + +***Events:*** + +* `ProxyExecuted(result)` + +#### `reject_announcement` +```rust +pub fn reject_announcement( + delegate: <::Lookup as StaticLookup>::Source, + call_hash: <::CallHasher as Hash>::Output +) +``` + +Remove the given announcement. Deposit is returned in case of success. + +May be called from delegator of the proxy to remove announcement made by [delegatee](/substrate-runtimes/glossary#delegatee). + +The origin must be signed for this call. + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — account that created an announcement +* `call_hash: <::CallHasher as Hash>::Output` — hash that was created for the announcement + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `delegate` account not found +* `NotFound` — proxy not found for this delegator and delegatee + +#### `remove_announcement` +```rust +pub fn remove_announcement( + real: <::Lookup as StaticLookup>::Source, + call_hash: <::CallHasher as Hash>::Output +) +``` + +Remove the given announcement. Deposit is returned in case of success. + +May be called from delegatee of the proxy to remove announcement made by them. + +The origin must be signed for this call. + +***Params:*** + +* `real: <::Lookup as StaticLookup>::Source` — delegator of the proxy for the announcement to remove +* `call_hash: <::CallHasher as Hash>::Output` — hash of announced call + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `delegate` account not found +* `NotFound` — proxy not found for this delegator and delegatee + +#### `remove_proxies` +```rust +pub fn remove_proxies() +``` + +Removes all proxies _issued to_ the caller. The origin must be signed for this call. + +***Errors:*** + +* `BadOrigin` — request not signed + +#### `remove_proxy` +```rust +pub fn remove_proxy( + delegate: <::Lookup as StaticLookup>::Source, + proxy_type: T::ProxyType, + delay: BlockNumberFor +) +``` + +Remove the proxy issued by the caller. Deposit is returned to the delegator. + +Origin must be signed for this call. + +***Params:*** + +* `delegate: <::Lookup as StaticLookup>::Source` — account to whom this proxy was issued +* `proxy_type: T::ProxyType` — type of the issued proxy +* `delay: BlockNumberFor` — delay of the issued proxy + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `delegate` account not found +* `NotFound` — no such proxy exists + +***Events:*** + +* `ProxyRemoved(delegator, delegatee, proxy_type, delay)` + +#### `create_pure` +```rust +pub fn create_pure( + proxy_type: T::ProxyType, + delay: BlockNumberFor, + index: u16 +) +``` + +This call creates a [pure account](/substrate-runtimes/glossary#pure-account) with a proxy issued to it from the call's origin. + +The origin must be signed for this call. + +***Params:*** + +* `proxy_type: T::ProxyType` — type of calls that will be allowed for the proxy +* `delay: BlockNumberFor` — number of blocks that needs to happen between announcement and call for this proxy +* `index: u16` — A disambiguation index, in case this is called multiple times in the same +transaction (e.g. with `utility::batch`). Unless you’re using `batch` you probably just +want to use `0`. + +***Errors:*** + +* `BadOrigin` — request not signed +* `Duplicate` — `create_pure` was called more than once with the same parameters in the same transaction +* `TooMany` — there is more announcements for this sender than specified in `MaxPending` +* `InsufficientBalance` — delegator does not have enough funds for deposit for proxy creation +* `LiquidityRestrictions` — account restrictions (like frozen funds or vesting) prevent from creating a deposit +* `Overflow` — reserved funds overflow the currency type. Should not happen in usual scenarios. + +***Events:*** + +* `PureCreated(pure, who, proxy_type, disambiguation_index)` + +#### `kill_pure` +```rust +pub fn kill_pure( + spawner: <::Lookup as StaticLookup>::Source, + proxy_type: T::ProxyType, + index: u16, + height: BlockNumberFor, + ext_index: u32 +) +``` + +Remove a previously created pure account. + +Requires a `Signed` origin, and the sender account must have been created by a call to +`pure` with corresponding parameters. + + +All access to this account will be lost. + + +***Params:*** + +* `spawner` — account who created a proxy and pure account +* `proxy_type` — type of proxy used for it +* `index` — the disambiguation index used for pure account creation +* `height` — the height of the chain when the call to `pure` was processed. +* `ext_index` — the extrinsic index in which the call to `pure` was processed. + +***Errors:*** + +* `BadOrigin` — request not signed +* `LookupError` — `spawner` account not found +* `NoPermission` — emitted when account tries to remove somebody but not itself diff --git a/docs/content/substrate-runtimes/pallets/transaction_payment.mdx b/docs/content/substrate-runtimes/pallets/transaction_payment.mdx new file mode 100644 index 00000000..74ce247f --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/transaction_payment.mdx @@ -0,0 +1,55 @@ +--- +title: pallet_transaction_payment +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[GitHub Repository](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/transaction-payment/src/lib.rs) + +## Purpose + +`pallet-transaction-payment` implements transaction fee logic. + +In substrate, every transaction has an associated `call`, and each `call` has its own [weight](/substrate-runtimes/glossary#weight) function. The weight function estimates the time it takes to execute the `call`. + +`Config::WeightToFee` is a mapping between the smallest unit of compute (**Weight**) and smallest unit of fee. + +This pallet also exposes +- how to update fees for the next block based on past fees (`Config::FeeMultiplierUpdate`) +- how fees are paid (`Config::OnChargeTransaction`) + +The base fee and adjusted [weight](/substrate-runtimes/glossary#weight-fee) and [length](/substrate-runtimes/glossary#length-fee) fees constitute the _inclusion fee_, which is the minimum fee for a transaction to be included in a block. The formula of final fee: +```rust +inclusion_fee = base_fee + length_fee + [fee_multiplier_update * weight_fee]; +final_fee = inclusion_fee + tip; +``` +The inputs are defined below in the glossary and config sections. + +## Config + +* Pallet-specific handlers: + * `OnChargeTransaction` -- Handler for withdrawing, refunding and depositing the transaction fee. Type must implement the trait `OnChargeTransaction`. + * `FeeMultiplierUpdate` -- Handler to define how base fees change over time (over blocks). Type must implement the trait `MultiplierUpdate`. Possible assignments include `ConstantFee`, `SlowAdjustingFee`, and `FastAdjustingFee`. +* Pallet-specific converters: + * `WeightToFee` -- Mapping between the smallest unit of weight and smallest unit of fee. Type must implement the trait `WeightToFee>`. + * `LengthToFee` -- Convert a length value into a deductible fee based on the currency type. Type must implement the trait `WeightToFee>`. +* Pallet-specific constants: + * `OperationalFeeMultiplier` -- A fee multiplier for `Operational` extrinsics to compute "virtual tip" to boost their `priority`. Type must implement the trait `Get`. +* Common configs: + * `RuntimeEvent` + +## Dispatchables + +There are no dispatchables (and no errors) in this pallet. This pallet is only intended to configure the transaction fee logic for a chain. + +### Events + +* `TransactionFeePaid(who, actual_fee, tip)` -- a transaction fee was paid by account `who` with total amount of `actual_fee + tip`. + +## More Reading + +* [Substrate Weight & Fees](https://www.shawntabrizi.com/blog/substrate/substrate-weight-and-fees/) by Shawn Tabrizi + * [Substrate Docs: Transaction Weight & Fees](https://docs.substrate.io/build/tx-weights-fees/) +* [Substrate Docs: How to Calculate Fees](https://docs.substrate.io/reference/how-to-guides/weights/calculate-fees/#:~:text=Weight%20fee%20%2D%20A%20fee%20calculated,change%20as%20the%20chain%20progresses) diff --git a/docs/content/substrate-runtimes/pallets/treasury.mdx b/docs/content/substrate-runtimes/pallets/treasury.mdx new file mode 100644 index 00000000..e2a75cc1 --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/treasury.mdx @@ -0,0 +1,224 @@ +--- +title: pallet_treasury +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[Treasury Pallet Source Code](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/substrate/frame/treasury/src/lib.rs) + +## Purpose + +The Treasury pallet provides a “pot” of funds that can be managed by stakeholders in the system and a structure for making spending proposals from this pot. + +## Config + +* Pallet-specific configs + * `SpendPeriod` -- Period between successive spends. "Spend" means, treasury spending money to a proposal. `SpendPeriod` determines how often the treasury pallet distributes the assets to the proposals. + * `Burn` -- Percentage of spare funds (if any) that are burnt per spend period. + * `BurnDestination` -- Handler for the unbalanced decrease when treasury funds are burned. + * `SpendFunds` -- Runtime hooks to external pallet using treasury to compute spend funds. + * `MaxApprovals` -- The maximum number of approvals that can wait in the spending queue. NOTE: This parameter is also used within the Bounties Pallet extension if enabled. + * `AssetKind` -- Type parameter representing the asset kinds to be spent from the treasury. + * `Beneficiary` -- Type parameter used to identify the beneficiaries eligible to receive treasury spends. + * `BeneficiaryLookup` -- Converting trait to take a source type and convert to [`Self::Beneficiary`]. + * `Paymaster` -- Type for processing spends of [Self::AssetKind] in favor of [`Self::Beneficiary`]. + * `BalanceConverter` -- Type for converting the balance of an [Self::AssetKind] to the balance of the native asset, solely for the purpose of asserting the result against the maximum allowed spend amount of the [`Self::SpendOrigin`]. + * `PayoutPeriod` -- The period during which an approved treasury spend has to be claimed by the beneficiary of the proposal. +* Pallet-specific origins: + * `RejectOrigin` -- Origin from which rejections must come. + * `SpendOrigin` -- The origin required for approving spends from the treasury outside of the proposal process. The `Success` value is the maximum amount in a native asset that this origin is allowed to spend at a time. +* Common configs + * `Currency` -- The staking balance. + * `RuntimeEvent` -- The overarching event type. + * `PalletId` -- The treasury’s pallet id, used for deriving its sovereign account ID. + * `WeightInfo` -- Weight information for extrinsics in this pallet. + * `BenchmarkHelper` -- Helper type for benchmarks. + +## Dispatchables + +#### `spend_local` +```rust +pub fn spend_local( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + beneficiary: AccountIdLookupOf, +) -> DispatchResult +``` +Propose and approve a spend of treasury funds, enables the creation of spends using the native currency of the chain, utilizing the funds stored in the pot. + + +For record-keeping purposes, the proposer is deemed to be equivalent to the beneficiary. + + + +The behavior and API of the old `spend` call capable of spending local DOT tokens remain unchanged, and is now under the name `spend_local`. The revised new `spend` call is able to spend any asset kind managed by the treasury. + + +***Params:*** + +* `origin: OriginFor` -- Must be [`Config::SpendOrigin`] with the `Success` value being at least `amount`. +* `value: BalanceOf` -- The amount to be transferred from the treasury to the `beneficiary`. +* `beneficiary: AccountIdLookupOf` -- The destination account for the transfer. + +***Errors:*** + +* `InsufficientPermission` -- if the amount to be spent is greater than what the dispatcher of this call is allowed to spend. +* `TooManyApprovals` -- when `MaxApprovals` limit is hit, and cannot add a new proposal to the storage. + +***Events:*** + +* `SpendApproved(proposal_index, amount, beneficiary)` + +#### `spend` +```rust +pub fn spend( + origin: OriginFor, + asset_kind: Box, + amount: AssetBalanceOf, + beneficiary: Box<<>::BeneficiaryLookup as StaticLookup>::Source>, + valid_from: Option> +) -> DispatchResult +``` + +Propose and approve a spend of treasury funds, allows spending any asset kind managed by the treasury. + + +The behavior and the API of the previous version of this function is kept the same and renamed to `spend_local`. The new feature `valid_from` is not added to `spend_local` for backward compatibility. + + +***Params:*** + +* `origin: OriginFor` -- Must be [`Config::SpendOrigin`] with the `Success` value being at least `amount`. +* `asset_kind: Box` -- An indicator of the specific asset class to be spent. +* `value: BalanceOf` -- The amount to be transferred from the treasury to the `beneficiary`. +* `beneficiary: AccountIdLookupOf` -- The destination account for the transfer. +* `valid_from: Option>` -- The block number from which the spend can be claimed. It can refer to the past if the resulting spend has not yet expired according to the [`Config::PayoutPeriod`]. If `None`, the spend can be claimed immediately after approval. + +***Errors:*** + +* `InsufficientPermission` -- if the amount to be spent is greater than what the dispatcher of this call is allowed to spend. +* `SpendExpired` -- if expiration date is older than now. +* `FailedToConvertBalance` -- when conversion between `asset_kind` and `native currency` fails. + +***Events:*** + +* `AssetSpendApproved(index, asset_kind, amount, beneficiary, valid_from, expire_at)` -- `index` is the index of the proposal. Rest is self-explanatory. + +#### `remove_approval` +```rust +pub fn remove_approval( + origin: OriginFor, + proposal_id: ProposalIndex +) -> DispatchResult +``` + +Force a previously approved proposal to be removed from the approval queue. + +***Params:*** + +* `origin: OriginFor` -- Must be [Config::RejectOrigin]. +* `proposal_id: ProposalIndex` -- The index of a proposal. + +***Errors:*** + +* `ProposalNotApproved` -- The proposal does not exist in the approved proposals queue. + +***Events:*** + +* `ProposalRejected(proposal_id)` + +#### `payout` +```rust +pub fn payout(origin: OriginFor, index: SpendIndex) -> DispatchResult +``` + +Claims a spend. + +Spends must be claimed within some temporal bounds. A spend may be claimed within one [`Config::PayoutPeriod`] from the `valid_from` block. +In case of a payout failure, the spend status must be updated with the `check_status` dispatchable before retrying with the current function. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `index: SpendIndex` -- The index of the spend. + +***Errors:*** + +* `InvalidIndex` -- The spend could not be found. +* `EarlyPayout` -- The spend tried to be claimed before it became valid (see `valid_from` field). +* `SpendExpired` -- The spend tried to be claimed after it expired. +* `AlreadyAttempted` -- The same spend tried to be claimed before. +* `PayoutError` -- An error occurred during the payment, related to `Paymaster::pay` function. + +***Events:*** + +* `Paid(index, payment_id)` + +#### `check_status` +```rust +pub fn check_status( + origin: OriginFor, + index: SpendIndex +) -> DispatchResultWithPostInfo +``` + +Check the status of the spend and remove it from the storage if processed. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `index: SpendIndex` -- The index of the spend. + +***Errors:*** + +* `InvalidIndex` -- The spend could not be found. +* `NotAttempted` -- The payout was not attempted. +* `Inconclusive` -- The spend is still in progress. + +***Events:*** + +* `SpendProcessed(index)` -- Spend is successfully processed. +* `PaymentFailed(index, payment_id)` -- The payout was failed, and can be retried again. This error also gives the `payment_id` info for further investigation. + +#### `void_spend` +```rust +pub fn void_spend(origin: OriginFor, index: SpendIndex) -> DispatchResult +``` + +Void previously approved spend. + +A spend void is only possible if the payout has not been attempted yet. + + +even if the payout is failed, it still counts towards an attempt, and cannot be voided at this point. + + +***Params:*** + +* `origin: OriginFor` -- Must be [Config::RejectOrigin]. +* `index: SpendIndex` -- The index of the spend. + +***Errors:*** + +* `InvalidIndex` -- The spend could not be found. +* `AlreadyAttempted` -- The same spend tried to be claimed before. + +***Events:*** + +* `AssetSpendVoided(index)` + +## Important Mentions and FAQ's + +You might have come across the below from official documentation or source-code: + +* `propose_spend` will be removed in February 2024. Use spend instead. +* `reject_proposal` will be removed in February 2024. Use spend instead. +* `approve_proposal` will be removed in February 2024. Use spend instead. + +The new `spend` dispatchable will not be able to solely `propose` or `approve` proposals separately, nor `reject` them. + +After the deprecation update, `treasury` pallet no longer tracks `unapproved` proposals, but only approved ones. + +The idea is to use the `treasury` pallet combined with some other pallet which will provide the functionality of tracking unapproved proposals (reject, approve, propose). For Polkadot it's OpenGov (referenda and conviction voting pallets). diff --git a/docs/content/substrate-runtimes/pallets/xcm.mdx b/docs/content/substrate-runtimes/pallets/xcm.mdx new file mode 100644 index 00000000..cb0d03e2 --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/xcm.mdx @@ -0,0 +1,451 @@ +--- +title: pallet_xcm +--- + +Branch/Release: `release-polkadot-v1.10.0` + +Source Code: [GitHub](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/polkadot/xcm/pallet-xcm/src/lib.rs) + +## Purpose + +`pallet-xcm` is responsible for filtering, routing, and executing incoming XCM. + +## Config + +* Pallet-specific origins: + * `AdminOrigin` -- The origin that is allowed to call privileged operations on the XCM pallet. Type must implement trait `EnsureOrigin`. + * `ExecuteXcmOrigin` -- Required origin for executing XCM messages, including the teleport functionality. If successful, it returns a `[MultiLocation](/substrate-runtimes/glossary#multilocation)` which exists as an interior location within this chain’s XCM context. Type must implement trait `EnsureOrigin`. + * `SendXcmOrigin` -- Required origin for sending XCM messages. If successful, it resolves to `MultiLocation`. Type must implement trait `EnsureOrigin`. +* Pallet-specific ID types: + * `RemoteLockConsumerIdentifier` -- The ID type for local consumers of remote locks. The type must implement `Copy`. +* Pallet-specific handlers: + * `Currency` -- Lockable currency used in the pallet. Type must implement the trait `LockableCurrency`. + * `Weigher` -- Measures [weight](/substrate-runtimes/glossary#weight) consumed by XCM local execution. Type must implement the trait `WeightBounds`. + * `XcmExecutor` -- Means of executing XCM. Type must implement the trait `ExecuteXcm`. + * `XcmRouter` -- The type used to dispatch an XCM to its destination. Type must implement the trait `SendXcm`. +* Pallet-specific filters: + * `XcmExecuteFilter` -- XCM filter for which messages to be executed using `XcmExecutor` must pass. Type must implement trait `Contains<(MultiLocation, Xcm<::RuntimeCall>)>`. + * `XcmReserveTransferFilter` -- XCM filter for which messages to be reserve-transferred using the dedicated extrinsic must pass. Type must implement the trait `Contains<(MultiLocation, Vec)>`. + * `XcmTeleportFilter` -- XCM filter for which messages to be teleported using the dedicated extrinsic must pass. Type must implement the trait `Contains<(MultiLocation, Vec)>`. + * `TrustedLockers` -- Returns whether or not the origin can be trusted for a specific asset. Type must implement `ContainsPair` where each pair is an `asset` and an `origin` thereby entrusting `origin` for operations relating to `asset`. +* Pallet-specific converters: + * `CurrencyMatcher` -- The `[MultiAsset](/substrate-runtimes/glossary#multiasset)` matcher for `Currency`. Type must implement the trait `MatchesFungible`. + * `SovereignAccountOf` -- How to get an `AccountId` value from a `MultiLocation`, useful for handling asset locks. Type must implement `ConvertLocation`. +* Pallet-specific constants: + * `VERSION_DISCOVERY_QUEUE_SIZE` -- `u32` measures the size of the version discovery queue. Typically set to `100`. + * `AdvertisedXcmVersion` -- The latest supported XCM version that this chain accepts. Type must implement the trait `Get`. Commonly set to `pallet_xcm::CurrentXcmVersion`. + * `MaxLockers` -- The maximum number of local XCM locks that a single account may have. Type must implement the trait `Get`. + * `MaxRemoteLockConsumers` -- The maximum number of consumers a single remote lock may have. Type must implement the trait `Get`. + * `UniversalLocation` -- This chain’s Universal Location. Type must implement the trait `Get`. +* Common configs: + * `RuntimeEvent` + * `RuntimeCall` + * `RuntimeOrigin` + * `WeightInfo` + +## Dispatchables + +#### send +```rust +pub fn send( + origin: OriginFor, + dest: Box, + message: Box>, +) +``` + + +DEPRECATED. `send` will be removed after June 2024. Use `send_blob` instead. + + +* Deprecation explanation: + * `pallet-xcm` has a new pair of extrinsics, `execute_blob` and `send_blob`. These are meant to be used instead of `execute` and `send`, which are now deprecated and will be removed eventually. These new extrinsics just require you to input the encoded XCM. + * There’s a new utility in PolkadotJS Apps for encoding XCMs you can use: https://polkadot.js.org/apps/#/utilities/xcm Just pass in the encoded XCM to the new extrinsics and you’re done. + * The migration from the old extrinsic to the new is very simple. If you have your message `xcm: VersionedXcm`, then instead of passing in `Box::new(xcm)` to both `execute` and `send`, you would pass in `xcm.encode().try_into()` and handle the potential error of its encoded length being bigger than `MAX_XCM_ENCODED_SIZE`. + * `pallet-contracts` takes the XCM encoded now as well. It follows the same API as `execute_blob` and `send_blob`. + +Send a versioned XCM `message` to the destination `dest`. + +The origin must be `SendXcmOrigin` for this call. + +***Params:*** + +* `dest: Box` — destination for the XCM +* `message: Box>` — versioned XCM to be sent to the multilocation `dest` + +***Errors:*** + +* `InvalidOrigin` — origin did not match `SendXcmOrigin` +* `BadVersion` — version for XCM not valid + +***Events:*** + +* `Sent(origin, destination, message, message_id)` -- The versioned XCM `message` was sent from the `origin` to the `destination`. + +#### execute +```rust +pub fn execute( + origin: OriginFor, + message: Box::RuntimeCall>>, + max_weight: Weight, +) +``` + + +DEPRECATED. `execute` will be removed after June 2024. Use `execute_blob` instead. + + +* Deprecation explanation: + * `pallet-xcm` has a new pair of extrinsics, `execute_blob` and `send_blob`. These are meant to be used instead of `execute` and `send`, which are now deprecated and will be removed eventually. These new extrinsics just require you to input the encoded XCM. + * There’s a new utility in PolkadotJS Apps for encoding XCMs you can use: https://polkadot.js.org/apps/#/utilities/xcm Just pass in the encoded XCM to the new extrinsics and you’re done. + * The migration from the old extrinsic to the new is very simple. If you have your message `xcm: VersionedXcm`, then instead of passing in `Box::new(xcm)` to both `execute` and `send`, you would pass in `xcm.encode().try_into()` and handle the potential error of its encoded length being bigger than `MAX_XCM_ENCODED_SIZE`. + * `pallet-contracts` takes the XCM encoded now as well. It follows the same API as `execute_blob` and `send_blob`. + +Execute an XCM message from a local, signed, origin. + +The origin must be `ExecuteXcmOrigin` for this call. + + +A successful return to this does NOT imply that the `msg` was executed successfully to completion; only that SOME of it was executed. + + +***Params:*** + +* `message: Box>` — versioned XCM to be executed +* `max_weight: Weight` -- No more than this amount of `Weight` will be consumed during this execution attempt. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin` +* `BadVersion` —- version for XCM not valid + +***Events:*** + +* `Attempted(outcome)` -- Indicates whether the `msg` was executed completely or only partially. + +#### force_xcm_version +```rust +pub fn force_xcm_version( + origin: OriginFor, + location: Box, + version: XcmVersion, +) +``` +Set that a particular destination can be communicated with through a particular version of XCM. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `location: Box` —- The destination that is being described. +* `version: XcmVersion` -- The latest version of XCM that `location` supports. + +***Errors:*** + +* `BadOrigin` — origin did not match `AdminOrigin` + +***Events:*** + +* `SupportedVersionChanged(location, version)` -- `location` was updated to support the latest version of XCM `version` + +#### force_default_xcm_version +```rust +pub fn force_default_xcm_version( + origin: OriginFor, + maybe_xcm_version: Option, +) +``` +Set a safe XCM version (the version that XCM should be encoded with if the most recent version a destination can accept is unknown). + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `maybe_xcm_version: Option` —- The default XCM encoding version, or `None` to disable. + +***Errors:*** + +* `BadOrigin` — origin did not match `AdminOrigin` + +***Events:*** + +None + +#### force_subscribe_version_notify +```rust +pub fn force_subscribe_version_notify( + origin: OriginFor, + location: Box, +) +``` +Ask a location to notify us regarding their XCM version and any changes to it. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `location: Box`: The location to which we should subscribe for XCM version notifications. + +***Errors:*** + +* `BadOrigin` — origin did not match `AdminOrigin` + +***Events:*** + +None + +#### force_unsubscribe_version_notify +```rust +pub fn force_unsubscribe_version_notify( + origin: OriginFor, + location: Box, +) +``` +Require that a particular destination should no longer notify us regarding any XCM version changes. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `location: Box`: The location from which we are but no longer wish to subscribe to XCM version notifications. + +***Errors:*** + +* `BadOrigin` —- origin did not match `AdminOrigin` +* `NoSubscription` -- subscription not found to `location` +* `BadLocation` -- location not found + +***Events:*** + +None + +#### limited_reserve_transfer_assets +```rust +pub fn limited_reserve_transfer_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, +) +``` +Transfer some assets from the local chain to the sovereign account of a destination chain and forward a notification XCM. + +The origin must be `ExecuteXcmOrigin` for this call. + +***Params:*** + +* `dest: Box` -- Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. +* `assets: Box` -- The assets to be withdrawn. This should include the assets used to pay the fee on the `dest` side. +* `fee_asset_item: u32` -- The index into `assets` of the item which should be used to pay fees. +* `weight_limit: WeightLimit` -- The remote-side weight limit, if any, for the XCM fee purchase. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcm` +* `BadVersion` -- `beneficiary` or `assets` have incorrect versioning +* `TooManyAssets` -- assets length exceeds MAX_ASSETS_FOR_TRANSFER which equals 2 in this code + +***Events:*** + +* `Attempted(outcome)` -- Attempted the reserve transfer with returned status `outcome` + +#### limited_teleport_assets +```rust +pub fn limited_teleport_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, +) +``` +Teleport some assets from the local chain to some destination chain. + +Fee payment on the destination side is made from the asset in the `assets` vector of index `fee_asset_item`, up to enough to pay for `weight_limit` of weight. If more weight is needed than `weight_limit`, then the operation will fail and the assets send may be at risk. + +The origin must be `ExecuteXcmOrigin` for this call. + +***Params:*** + +* `dest: Box` -- Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to teleport from parachain to parachain, or `X1(Parachain(..))` to teleport from relay to parachain. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. +* `assets: Box` -- The assets to be withdrawn. This should include the assets used to pay the fee on the `dest` side. +* `fee_asset_item: u32` -- The index into `assets` of the item which should be used to pay fees. +* `weight_limit: WeightLimit` -- The remote-side weight limit, if any, for the XCM fee purchase. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcm` +* `BadVersion` -- `beneficiary` or `assets` have incorrect versioning +* `TooManyAssets` -- assets length exceeds MAX_ASSETS_FOR_TRANSFER which equals 2 in this code + +***Events:*** + +* `Attempted(outcome)` -- Attempted the teleport with returned status `outcome` + +#### force_suspension +```rust +pub fn force_suspension( + origin: OriginFor, + suspended: bool, +) +``` +Set or unset the global suspension state of the XCM executor. + +The origin must be `AdminOrigin` for this call. + +***Params:*** + +* `suspended: bool` -- `true` to suspend, `false` to resume. + +***Errors:*** + +None + +***Events:*** + +None + +#### transfer_assets +```rust +pub fn transfer_assets( + origin: OriginFor, + dest: Box, + beneficiary: Box, + assets: Box, + fee_asset_item: u32, + weight_limit: WeightLimit, +) +``` +Transfer some assets from the local chain to the destination chain through their local, destination or remote reserve, or through teleports. + +***Params:*** + +* `dest: Box` -- Destination context for the assets. Will typically be `X2(Parent, Parachain(..))` to send from parachain to parachain, or `X1(Parachain(..))` to send from relay to parachain. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. +* `assets: Box` -- The assets to be withdrawn. This should include the assets used to pay the fee on the `dest` (and possibly reserve) chains. +* `fee_asset_item: u32` -- The index into `assets` of the item which should be used to pay fees. +* `weight_limit: WeightLimit` -- The remote-side weight limit, if any, for the XCM fee purchase. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin`. +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `TooManyAssets` -- `assets` contain more than `MAX_ASSETS_FOR_TRANSFER = 2` to transfer. +* `Empty` -- can be a number of different errors: + * `fee_asset_item` is not present in `assets`. + * some fungible asset in `assets` has a value of 0. + * fees or asset transfer type was not determined. +* `TooManyReserves` -- there are more than one transfer type for an asset. +* `InvalidAssetUnknownReserve` -- transfer type can not be determined for a given asset. +* `InvalidAssetUnsupportedReserve` -- asset or fees transfer type is remote reserve and asset and fees asset are different. +* `Filtered` -- can be a number of different errors: + * `XcmReserveTransferFilter` filtered the asset. + * `XcmTeleportFilter` filtered the asset +* `CannotReanchor` -- asset can’t be reanchored. +* `CannotCheckOutTeleport` -- asset can’t be teleported +* `UnweighableMessage` -- prepared XCM message had issues with weighing (i.e. more instructions than the limit). +* `LocalExecutionIncomplete` -- local execution of XCM message have failed. +* `FeesNotMet` -- unable to charge fees. See the error log of any node to see the details. + +***Events:*** + +* `Sent(origin, destination, message, message_id)` + +***Deprecated Extrinsics***: +- `teleport_assets` -- Use `limited_teleport_assets` instead. +- `reserve_transfer_assets` -- Use `limited_reserve_transfer_assets` instead. + +#### claim_assets +```rust +pub fn claim_assets( + origin: OriginFor, + assets: Box, + beneficiary: Box, +) -> DispatchResult +``` + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `assets: Box` -- The exact assets that were trapped. Use the version to specify what version was the latest when they were trapped. +* `beneficiary: Box` -- A beneficiary location for the assets in the context of `dest`. Will generally be an `AccountId32` value. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin`. +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `UnweighableMessage` -- prepared XCM message had issues with weighing (i.e. more instructions than the limit). +* `LocalExecutionIncomplete` -- local execution of XCM message have failed. + +***Events:*** + +None + +#### execute_blob +```rust +pub fn execute_blob( + origin: OriginFor, + encoded_message: BoundedVec, + max_weight: Weight, +) -> DispatchResultWithPostInfo +``` + +Execute an XCM from a local, signed, origin. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `encoded_message: BoundedVec` -- The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`]. +* `max_weight: Weight` -- No more than `max_weight` will be used in its attempted execution. If this is less than the maximum amount of weight that the message could take to be executed, then no execution attempt will be made. + +***Errors:*** + +* `BadOrigin` —- origin did not match `ExecuteXcmOrigin`. +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `Filtered` -- can be a number of different errors: +* `LocalExecutionIncomplete` -- local execution of XCM message have failed. +* `UnableToDecode` -- unable to decode the XCM. +* `XcmTooLarge` -- XCM encoded length is larger than `MaxXcmEncodedSize`. + +***Events:*** + +* `Attempted(outcome)` -- Indicates whether the `msg` was executed completely or only partially. + +#### send_blob +```rust +pub fn send_blob( + origin: OriginFor, + dest: Box, + encoded_message: BoundedVec, +) -> DispatchResult +``` + +Send an XCM from a local, signed, origin. + +***Params:*** + +* `origin: OriginFor` -- Must be signed. +* `dest: Box` -- The destination, `dest`, will receive this message with a `DescendOrigin` instruction that makes the origin of the message be the origin on this system. +* `encoded_message: BoundedVec` -- The message is passed in encoded. It needs to be decodable as a [`VersionedXcm`]. + +***Errors:*** + +* `InvalidOrigin` -- origin did not match `SendXcmOrigin` +* `BadVersion` -- v2/v3 conversion to v4 failed for `assets`, `dest`, or `beneficiary`. +* `UnableToDecode` -- unable to decode the XCM. +* `FeesNotMet` -- unable to charge fees. See the error log of any node to see the details. +* `Unreachable` -- The desired destination was unreachable, generally because there is a no way of routing to it. +* `SendFailure` -- There was some other issue (i.e. not to do with routing) in sending the message. Perhaps a lack of space for buffering the message. + +***Events:*** + +* `Sent(origin, destination, message, message_id)` -- The versioned XCM `message` was sent from the `origin` to the `destination`. + +## More Reading + +[Polkadot Wiki XCM Use Cases](https://wiki.polkadot.network/docs/learn-xcm-usecases) diff --git a/docs/content/substrate-runtimes/pallets/xcmp-queue.mdx b/docs/content/substrate-runtimes/pallets/xcmp-queue.mdx new file mode 100644 index 00000000..67e53997 --- /dev/null +++ b/docs/content/substrate-runtimes/pallets/xcmp-queue.mdx @@ -0,0 +1,135 @@ +--- +title: cumulus_pallet_xcmp_queue +--- + +Branch/Release: `release-polkadot-v1.10.0` + +## Source Code + +[GitHub - cumulus_pallet_xcmp_queue](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/cumulus/pallets/xcmp-queue/src/lib.rs) + +## Purpose + +Responsible for the Queues (both incoming and outgoing) for XCMP messages. This pallet does not actually receive or send messages. Its responsibility is to place the incoming and outgoing XCMP messages in their respective queues and manage these queues. + +## Config + +* Pallet-specific configs + * `ChannelInfo` -- Configures two functions `get_channel_status` and `get_channel_info` to provide information about channels (`ChannelStatus` and `ChannelInfo`). + * `VersionWrapper` -- Means of converting an `Xcm` into a `VersionedXcm`. XCMs should be versioned in order to pass the validation. + * `XcmpQueue` -- Defines max length of an XCMP message, and how `enqueue_message` should behave. + * `MaxInboundSuspended` -- The maximum number of inbound XCMP channels that can be suspended simultaneously. Any further channel suspensions will fail and messages may get dropped without further notice. Choosing a high value (1000) is okay. + * `ControllerOrigin` -- The origin that is allowed to resume or suspend the XCMP queue. + * `ControllerOriginConverter` -- The conversion function used to attempt to convert an XCM `[MultiLocation](/substrate-runtimes/glossary#multilocation)` origin to a superuser origin. This is used for allowing the superuser’s queue to send messages even to paused queues. + * `PriceForSiblingDelivery` -- The price for delivering an XCM to a sibling parachain destination. +* Common configs + * `RuntimeEvent` -- The overarching event type. + * `WeightInfo` -- The [weight](/substrate-runtimes/glossary#weight) information of this pallet. + +## Dispatchables + +#### `suspend_xcm_execution` +```rust +pub fn suspend_xcm_execution(origin: OriginFor) -> DispatchResult +``` +Suspends all XCM executions for the XCMP queue. + + +`origin` must pass the `ControllerOrigin` check. + + +This does not change the status of the in/out bound channels. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable + +***Errors:*** + +* `AlreadySuspended` -- description of conditions, when this error happens + +#### `resume_xcm_execution` +```rust +pub fn resume_xcm_execution(origin: OriginFor) -> DispatchResult +``` +Resumes all XCM executions for the XCMP queue. + + +`origin` must pass the `ControllerOrigin` check. + + +This does not change the status of the in/out bound channels. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable + +***Errors:*** + +* `AlreadyResumed` -- description of conditions, when this error happens + +#### `update_suspend_threshold` +```rust +pub fn update_suspend_threshold(origin: OriginFor, new: u32) -> DispatchResult +``` +Overwrites the number of pages which must be in the queue for the other side to be told to suspend their sending. + + +`origin` must be root. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable +* `new: u32` -- new page threshold for suspend + +#### `update_drop_threshold` +```rust +pub fn update_drop_threshold(origin: OriginFor, new: u32) -> DispatchResult +``` +Overwrites the number of pages which must be in the queue after which we drop any further messages from the channel. + + +`origin` must be root. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable +* `new: u32` -- new page threshold for drop + +#### `update_resume_threshold` +```rust +pub fn update_resume_threshold(origin: OriginFor, new: u32) -> DispatchResult +``` +Overwrites the number of pages which the queue must be reduced to before it signals that message sending may recommence after it has been suspended. + + +`origin` must be root. + + +***Params:*** + +* `origin: OriginFor` -- caller of the dispatchable +* `new: u32` -- new page threshold for resume + +## Important Mentions and FAQs + + +messages are not ordered when they are received, but they are ordered when they are sent. `Signal` messages are prioritized over `non-signal` messages. + + +Messages and signals are stored in different queues. When the messages to be sent are taken with `take_outbound_messages`, they will be ordered: + +* if there are signals present for outbound messages targeting a parachain, we will only send signals, not messages +* messages (that are not signals) won’t be ordered + + +polkadot/xcm/src/v3 has `SendXcm` trait, which has 2 blank methods validate and deliver. For each router struct, one can implement `SendXcm` for it. + + +1. `deliver` method takes `destination` as a parameter, and calls `send_fragment` function with the target parachain ID. +2. `send_fragment` puts the message to the queue of the given parachain ID. + - Unlike its name, `deliver` method does not actually deliver the message. It is calling `send_fragment`, which places a message fragment on the outgoing XCMP queue for recipient. So, `deliver` is only putting the message to the respective outgoing XCMP queue. diff --git a/docs/content/substrate-runtimes/runtime/xcm_executor.mdx b/docs/content/substrate-runtimes/runtime/xcm_executor.mdx new file mode 100644 index 00000000..976b115f --- /dev/null +++ b/docs/content/substrate-runtimes/runtime/xcm_executor.mdx @@ -0,0 +1,72 @@ +--- +title: XCM Executor +--- + +Branch/Release: `release-polkadot-v1.10.0` + +[Source Code](https://github.com/paritytech/polkadot-sdk/blob/release-polkadot-v1.10.0/polkadot/xcm/xcm-executor/src/config.rs) + +## Purpose + +`XcmExecutor` is responsible for executing XCM messages locally. + +`XcmExecutor` is usually the assignment for `pallet_xcm::Config::XcmExecutor` and is thereby used to execute XCM in that pallet. + +> **Note** +> `XcmExecutor` is not a pallet, but rather it is a `struct` type parameterized by a `Config` trait. The inner config is the `trait Config` which parameterizes the outer config `struct XcmExecutor`. Both the inner and outer configs are configured in the runtime. + +## Inner Config + +The inner `trait Config` used to parameterize `XcmExecutor` has the following associated types. + +* Handlers: + * `XcmSender` -- How to send an onward XCM message. Type must implement the trait `SendXcm`. + * `AssetTransactor` -- How to withdraw and deposit an asset. Type must implement the trait `TransactAsset`. + * `Trader` -- The means of purchasing [weight](/substrate-runtimes/glossary#weight) credit for XCM execution. Type must implement the trait `WeightTrader`. + * `ResponseHandler` -- What to do when a response of a query is found. Type must implement the trait `OnResponse`. + * `AssetTrap` -- The general asset trap handler for when assets are left in the Holding Register at the end of execution. Type must implement the trait `DropAssets`. + * `AssetLocker` -- Handler for asset locking. Type must implement the trait `AssetLock`. + * `AssetExchanger` -- Handler for exchanging assets. Type must implement the trait `AssetExchange`. + * `AssetClaims` -- The handler for when there is an instruction to claim assets. Type must implement the trait `ClaimAssets`. + * `SubscriptionService` -- The handler for version subscription requests. Type must implement the trait `VersionChangeNotifier`. + * `FeeManager` -- Configure the fees. Type must implement the trait `FeeManager`. + * `MessageExporter` -- The method of exporting a message. Type must implement the trait `ExportXcm`. + * `CallDispatcher` -- The call dispatcher used by XCM. Type must implement the trait `CallDispatcher`. + * `HrmpNewChannelOpenRequestHandler` -- Allows optional logic execution for the `HrmpNewChannelOpenRequest` XCM notification. + * `HrmpChannelAcceptedHandler` -- Allows optional logic execution for the `HrmpChannelAccepted` XCM notification. + * `HrmpChannelClosingHandler` -- Allows optional logic execution for the `HrmpChannelClosing` XCM notification. +* Filters: + * `IsReserve` -- Combinations of (Asset, Location) pairs which we trust as reserves. Type must implement the trait `ContainsPair`. + * `IsTeleporter` -- Combinations of (Asset, Location) pairs which we trust as teleporters. Type must implement the trait `ContainsPair`. + * `Aliasers` -- A list of (Origin, Target) pairs allowing a given Origin to be substituted with its corresponding Target pair. Type must implement the trait `ContainsPair`. + * `Barrier` -- Whether or not to execute the XCM at all. Type must implement `ShouldExecute`. + * `UniversalAliases` -- The origin locations and specific universal [junctions](/substrate-runtimes/glossary#junctions) to which they are allowed to elevate themselves. Type must implement the trait `Contains<(MultiLocation, Junction)>`. + * `SafeCallFilter` -- The safe call filter for `Transact`. Use this type to explicitly whitelist calls that cannot undergo recursion. Type must implement the trait `Contains`. +* Converters: + * `OriginConverter` -- How to get a call origin from a `OriginKind` value. Type must implement the trait `ConvertOrigin<::RuntimeOrigin>`. +* Accessors: + * `Weigher` -- The means of determining an XCM message's weight. Type must implement the trait `WeightBounds`. + * `PalletInstancesInfo` -- Information on all pallets. Type must implement the trait `PalletsInfoAccess`. +* Constants: + * `UniversalLocation` -- This chain's Universal Location. Type must implement the trait `Get`. + * `MaxAssetsIntoHolding` -- The maximum number of assets we target to have in the Holding Register at any one time. Type must implement the trait `Get`. +* Common configs: + * `RuntimeCall` + +## Outer Config + +The outer `struct XcmExecutor` configures the following fields: + +* `holding` -- Assets allowed in the holding register. Type must be `Assets`. +* `holding_limit` -- The maximum number of assets in the holding register. Type must be `usize`. +* `context` -- Type must be `XcmContext`. +* `trader` -- Type must be `Config::Trader` which must implement the trait `WeightTrader`. +* `error` -- The most recent error result and instruction index into the fragment in which it occurred, if any. Type must be `Option<(u32, XcmError)>`. +* `total_surplus` -- Type must be `Weight`. +* `total_refunded` -- Type must be `Weight`. +* `error_handler` -- Type must be `Xcm`. +* `error_handler_weight` -- Type must be `Weight`. +* `appendix` -- Type must be `Xcm`. +* `appendix_weight` -- Type must be `Weight`. +* `transact_status` -- Type must be `MaybeErrorCode`. +* `fees_mode` -- Type must be `FeesMode`. diff --git a/docs/content/substrate-runtimes/runtimes/evm.mdx b/docs/content/substrate-runtimes/runtimes/evm.mdx new file mode 100644 index 00000000..17484726 --- /dev/null +++ b/docs/content/substrate-runtimes/runtimes/evm.mdx @@ -0,0 +1,125 @@ +--- +title: EVM Runtime +--- + +## Purpose + +EVM Runtime Template is built on top of the [Generic Runtime Template](/substrate-runtimes/runtimes/generic). + +The purpose of this template is to provide EVM compatibilities embedded into the runtime. + +With this template, you can: +* Deploy Solidity Smart Contracts to your runtime. +* Interact with the deployed Smart Contracts. +* Deploy and interact with ERC20 tokens. +* Migrate your existing dAPP from Ethereum ecosystem to Polkadot ecosystem. + +The pallet list is constructed by the contributions of Parity, the community, and OpenZeppelin. + +To maximize the compatibility with EVM, we decided to use `AccountId20` type instead of `AccountId32`. +This choice allowed us to use the signature scheme compatible with Ethereum (ECDSA). +Which, in turn result in better compatibility with tools across the Ethereum ecosystem including wallets, developer tooling, etc. +Metamask integration for example is way simpler for chains with the Ethereum account and signature type. + +## Configuration + +### System Support +
+click to expand + + +* [frame_system](https://paritytech.github.io/polkadot-sdk/master/frame_system/index.html#) is responsible from creating the runtime, initializing the storage, and providing the base functionality for the runtime. +* [cumulus_pallet_parachain_system](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_parachain_system/index.html#) handles low-level details of being a parachain. +* [pallet_timestamp](https://paritytech.github.io/polkadot-sdk/master/pallet_timestamp/index.html#) provides a way for consensus systems to set and check the onchain time. +* [parachain_info](https://docs.rs/staging-parachain-info/latest/staging_parachain_info/index.html#) provides a way for parachains to report their parachain id and the relay chain block number. +* [pallet_proxy](https://docs.rs/pallet-proxy/latest/pallet_proxy/#) enables delegation of rights to execute certain call types from one origin to another. +* [pallet_utility](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/index.html#) contains two basic pieces of functionality: + * Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a single dispatch. This can be useful to amalgamate proposals, combining `set_code` with corresponding `set_storage`s, for efficient multiple payouts with just a single signature verify, or in combination with one of the other two dispatch functionality. + * [force_batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.force_batch): Sends a batch of dispatch calls. Errors are allowed and won’t interrupt + * [batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch): Sends a batch of dispatch calls. This will return `Ok` in all circumstances. To determine the success of the batch, an event is deposited. If a call failed and the batch was interrupted, then the `BatchInterrupted` event is deposited, along with the number of successful calls made and the error of the failed call. If all were successful, then the `BatchCompleted` event is deposited. + * [batch_all](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch_all): Send a batch of dispatch calls and atomically execute them. The whole transaction will rollback and fail if any of the calls failed. + * Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from an alternative signed origin. Each account has 2 * 2**16 possible “pseudonyms” (alternative account IDs) and these can be stacked. This can be useful as a key management tool, where you need multiple distinct accounts (e.g. as controllers for many staking accounts), but where it’s perfectly fine to have each of them controlled by the same underlying keypair. Derivative accounts are, for the purposes of proxy filtering considered exactly the same as the origin and are thus hampered with the origin’s filters. +* [pallet_multisig](https://docs.rs/pallet-multisig/latest/pallet_multisig/#) enables multi-signature operations in your runtime. This module allows multiple signed origins (accounts) to coordinate and dispatch a call. For the call to execute, the threshold number of accounts from the set (signatories) must approve it. +* [pallet_scheduler](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/#) schedules runtime calls. +* [preimage](https://docs.rs/pallet-preimage/latest/pallet_preimage/#) allows for the users and the runtime to store the preimage of a hash on chain. This can be used by other pallets for storing and managing large byte-blobs. + +
+ +### Monetary +
+click to expand + + +* [pallet_balances](https://docs.rs/pallet-balances/latest/pallet_balances/#) provides functions for: + * Getting and setting free balances. + * Retrieving total, reserved and unreserved balances. + * Repatriating a reserved balance to a beneficiary account that exists. + * Transferring a balance between accounts (when not reserved). + * Slashing an account balance. + * Account creation and removal. + * Managing total issuance. + * Setting and managing locks. +* [pallet_transaction_payment](https://docs.rs/pallet-transaction-payment/latest/pallet_transaction_payment/#) provides the basic logic needed to pay the absolute minimum amount needed for a transaction to be included. This includes: + * **base fee**: This is the minimum amount a user pays for a transaction. It is declared as a base **weight** in the runtime and converted to a fee using `WeightToFee`. + * **weight fee**: A fee proportional to amount of weight a transaction consumes. + * **length fee**: A fee proportional to the encoded length of the transaction. + * **tip**: An optional tip. Tip increases the priority of the transaction, giving it a higher chance to be included by the transaction queue. +* [pallet_assets](https://paritytech.github.io/polkadot-sdk/master/pallet_assets/index.html#) deals with sets of assets implementing fungible traits, via [fungibles] traits in a simple, secure manner. This pallet makes heavy use of concepts such as Holds and Freezes from the [frame_support::traits::fungible] traits, therefore you should read and understand those docs as a prerequisite to understanding this pallet. +* [pallet_treasury](https://docs.rs/pallet-treasury/latest/pallet_treasury/#) provides a “pot” of funds that can be managed by stakeholders in the system and a structure for making spending proposals from this pot. +* [#pallet_asset_manager](https://github.com/moonbeam-foundation/moonbeam/blob/master/pallets/asset-manager/src/lib.rs) allows to register new assets if certain conditions are met. + +
+ +### Governance +
+click to expand + + +* [pallet_sudo](https://docs.rs/pallet-sudo/latest/pallet_sudo/#) provides a way to execute privileged runtime calls using a specified sudo (“superuser do”) account. +* [pallet_conviction_voting](https://docs.rs/pallet-conviction-voting/latest/pallet_conviction_voting/#) manages actual voting in polls +* [pallet_referenda](https://docs.rs/pallet-referenda/latest/pallet_referenda/#) executes referenda. No voting logic is present here, and the Polling and PollStatus traits are used to allow the voting logic (likely in a pallet) to be utilized. +* [pallet_custom_origins](https://github.com/OpenZeppelin/polkadot-runtime-templates/blob/main/evm-template/runtime/src/configs/governance/origins.rs#) allows custom origins for governance. // TODO: double check this, is it really our own pallet, or just copy paste? +* [pallet_whitelist](https://docs.rs/pallet-whitelist/latest/pallet_whitelist/index.html#) allows some configurable origin: `Config::WhitelistOrigin` to whitelist some hash of a call, and allows another configurable origin: `Config::DispatchWhitelistedOrigin` to dispatch them with the root origin. + +
+ +### Collator support +
+click to expand + + +* [pallet_authorship](https://docs.rs/pallet-authorship/latest/pallet_authorship/#) provides authorship tracking for FRAME runtimes. This tracks the current author of the block and recent uncles. +* [pallet_collator_selection](https://paritytech.github.io/polkadot-sdk/master/pallet_collator_selection/index.html#) - manages the collators of a parachain. ***Collation is *not** a secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT safety assumptions of the chosen set. This pallet can: + * set invulnerable candidates (fixed candidates) + * set desired candidates (ideal number of non-fixed) + * set candidacy bond + * remove invulnerability (turn candidate into not fixed) + * and many more (all related to collators) +* [pallet_session](https://paritytech.github.io/polkadot-sdk/master/pallet_session/index.html#) allows validators to manage their session keys, provides a function for changing the session length, and handles session rotation. +* [pallet_aura](https://docs.rs/pallet-aura/latest/pallet_aura/#) extends Aura consensus by managing offline reporting. It can: + * get the current slot + * get the slot duration + * change and initialize authorities + * ensure the correctness of the state of this pallet +* [cumulus_pallet_aura_ext](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_aura_ext/index.html#) extends the Substrate AuRa pallet to make it compatible with parachains. It provides the Pallet, the Config and the GenesisConfig. + +
+ +### XCM Helpers +
+click to expand + + +* [cumulus_pallet_xcmp_queue](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/index.html#) Responsible for the Queues (both incoming and outgoing) for XCMP messages. This pallet does not actually receive or send messages. Its responsibility is to place the incoming and outgoing XCMP messages in their respective queues and manage these queues. +* [pallet_xcm](https://docs.rs/pallet-xcm/6.0.0/pallet_xcm/#) is responsible for filtering, routing, and executing incoming XCM. +* [cumulus_pallet_xcm](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcm/index.html#) is responsible from detecting and ensuring whether XCM’s are coming from **Relay** or **Sibling** chain. +* [MessageQueue](https://docs.rs/pallet-message-queue/latest/pallet_message_queue/#) provides generalized message queuing and processing capabilities on a per-queue basis for arbitrary use-cases. + +
+ +### EVM + +* [Ethereum](https://docs.rs/pallet-ethereum/latest/pallet_ethereum/#) works together with EVM pallet to provide full emulation for Ethereum block processing. +* [EVM](https://docs.rs/pallet-evm/5.0.0/pallet_evm/#) allows unmodified EVM code to be executed in a Substrate-based blockchain. +* [BaseFee](https://github.com/polkadot-evm/frontier/blob/master/frame/base-fee/src/lib.rs#) dynamically adjusts the `BaseFeePerGas` based on elasticity (zero elasticity means constant `BaseFeePerGas`). +* [EvmChainId](https://github.com/polkadot-evm/frontier/blob/master/frame/evm-chain-id/src/lib.rs#) stores the numeric Ethereum-style chain id in the runtime. This pallet can simplify setting up multiple networks with different chain ID by configuring the chain spec without requiring changes to the runtime config. diff --git a/docs/content/substrate-runtimes/runtimes/generic.mdx b/docs/content/substrate-runtimes/runtimes/generic.mdx new file mode 100644 index 00000000..351d8cae --- /dev/null +++ b/docs/content/substrate-runtimes/runtimes/generic.mdx @@ -0,0 +1,117 @@ +--- +title: Generic Runtime +--- + +## Purpose + +Generic Runtime Template is constructed with the purpose of providing the most generic template that is working out of the box. + +The pallet list is constructed by the contributions of Parity, the community, and OpenZeppelin. + +Our motivation for crafting the pallet list was to be as minimalistic as possible, +whilst preserving the most important pallets that are used in the Polkadot ecosystem. + +We designed this template to be generic, so that it can be used as a starting point for any other project/template, +and we aim to base our future templates off of this one. + +To demystify the hard concepts, we provided a documentation, in which you can find: + +* Explanation of the pallets that are used in this template (purpose, terminology, configuration, dispatchables, etc.). +* General guides regarding this template. +* Runtime related guides. +* And miscellaneous topics. + +## Configuration + +### System Support +
+click to expand + + +* [frame_system](https://paritytech.github.io/polkadot-sdk/master/frame_system/index.html#) is responsible from creating the runtime, initializing the storage, and providing the base functionality for the runtime. +* [cumulus_pallet_parachain_system](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_parachain_system/index.html#) handles low-level details of being a parachain. +* [pallet_timestamp](https://paritytech.github.io/polkadot-sdk/master/pallet_timestamp/index.html#) provides a way for consensus systems to set and check the onchain time. +* [parachain_info](https://docs.rs/staging-parachain-info/latest/staging_parachain_info/index.html#) provides a way for parachains to report their parachain id and the relay chain block number. +* [pallet_proxy](https://docs.rs/pallet-proxy/latest/pallet_proxy/#) enables delegation of rights to execute certain call types from one origin to another. +* [pallet_utility](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/index.html#) contains two basic pieces of functionality: + * Batch dispatch: A stateless operation, allowing any origin to execute multiple calls in a single dispatch. This can be useful to amalgamate proposals, combining `set_code` with corresponding `set_storage`s, for efficient multiple payouts with just a single signature verify, or in combination with one of the other two dispatch functionality. + * [force_batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.force_batch): Sends a batch of dispatch calls. Errors are allowed and won’t interrupt + * [batch](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch): Sends a batch of dispatch calls. This will return `Ok` in all circumstances. To determine the success of the batch, an event is deposited. If a call failed and the batch was interrupted, then the `BatchInterrupted` event is deposited, along with the number of successful calls made and the error of the failed call. If all were successful, then the `BatchCompleted` event is deposited. + * [batch_all](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/struct.Pallet.html#method.batch_all): Send a batch of dispatch calls and atomically execute them. The whole transaction will rollback and fail if any of the calls failed. + * Pseudonymal dispatch: A stateless operation, allowing a signed origin to execute a call from an alternative signed origin. Each account has 2 * 2**16 possible “pseudonyms” (alternative account IDs) and these can be stacked. This can be useful as a key management tool, where you need multiple distinct accounts (e.g. as controllers for many staking accounts), but where it’s perfectly fine to have each of them controlled by the same underlying keypair. Derivative accounts are, for the purposes of proxy filtering considered exactly the same as the origin and are thus hampered with the origin’s filters. +* [pallet_multisig](https://docs.rs/pallet-multisig/latest/pallet_multisig/#) enables multi-signature operations in your runtime. This module allows multiple signed origins (accounts) to coordinate and dispatch a call. For the call to execute, the threshold number of accounts from the set (signatories) must approve it. +* [pallet_scheduler](https://docs.rs/pallet-scheduler/latest/pallet_scheduler/#) schedules runtime calls. +* [preimage](https://docs.rs/pallet-preimage/latest/pallet_preimage/#) allows for the users and the runtime to store the preimage of a hash on chain. This can be used by other pallets for storing and managing large byte-blobs. + +
+ +### Monetary +
+click to expand + + +* [pallet_balances](https://docs.rs/pallet-balances/latest/pallet_balances/#) provides functions for: + * Getting and setting free balances. + * Retrieving total, reserved and unreserved balances. + * Repatriating a reserved balance to a beneficiary account that exists. + * Transferring a balance between accounts (when not reserved). + * Slashing an account balance. + * Account creation and removal. + * Managing total issuance. + * Setting and managing locks. +* [pallet_transaction_payment](https://docs.rs/pallet-transaction-payment/latest/pallet_transaction_payment/#) provides the basic logic needed to pay the absolute minimum amount needed for a transaction to be included. This includes: + * **base fee**: This is the minimum amount a user pays for a transaction. It is declared as a base **weight** in the runtime and converted to a fee using `WeightToFee`. + * **weight fee**: A fee proportional to amount of weight a transaction consumes. + * **length fee**: A fee proportional to the encoded length of the transaction. + * **tip**: An optional tip. Tip increases the priority of the transaction, giving it a higher chance to be included by the transaction queue. +* [pallet_assets](https://paritytech.github.io/polkadot-sdk/master/pallet_assets/index.html#) deals with sets of assets implementing fungible traits, via [fungibles] traits in a simple, secure manner. This pallet makes heavy use of concepts such as Holds and Freezes from the [frame_support::traits::fungible] traits, therefore you should read and understand those docs as a prerequisite to understanding this pallet. +* [pallet_treasury](https://docs.rs/pallet-treasury/latest/pallet_treasury/#) provides a “pot” of funds that can be managed by stakeholders in the system and a structure for making spending proposals from this pot. + +
+ +### Governance +
+click to expand + + +* [pallet_sudo](https://docs.rs/pallet-sudo/latest/pallet_sudo/#) provides a way to execute privileged runtime calls using a specified sudo (“superuser do”) account. +* [pallet_conviction_voting](https://docs.rs/pallet-conviction-voting/latest/pallet_conviction_voting/#) manages actual voting in polls +* [pallet_referenda](https://docs.rs/pallet-referenda/latest/pallet_referenda/#) executes referenda. No voting logic is present here, and the Polling and PollStatus traits are used to allow the voting logic (likely in a pallet) to be utilized. +* [pallet_custom_origins](https://github.com/OpenZeppelin/polkadot-runtime-templates/blob/main/evm-template/runtime/src/configs/governance/origins.rs#) allows custom origins for governance. // TODO: double check this, is it really our own pallet, or just copy paste? +* [pallet_whitelist](https://docs.rs/pallet-whitelist/latest/pallet_whitelist/index.html#) allows some configurable origin: `Config::WhitelistOrigin` to whitelist some hash of a call, and allows another configurable origin: `Config::DispatchWhitelistedOrigin` to dispatch them with the root origin. + +
+ +### Collator support +
+click to expand + + +* [pallet_authorship](https://docs.rs/pallet-authorship/latest/pallet_authorship/#) provides authorship tracking for FRAME runtimes. This tracks the current author of the block and recent uncles. +* [pallet_collator_selection](https://paritytech.github.io/polkadot-sdk/master/pallet_collator_selection/index.html#) - manages the collators of a parachain. ***Collation is *not** a secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT safety assumptions of the chosen set. This pallet can: + * set invulnerable candidates (fixed candidates) + * set desired candidates (ideal number of non-fixed) + * set candidacy bond + * remove invulnerability (turn candidate into not fixed) + * and many more (all related to collators) +* [pallet_session](https://paritytech.github.io/polkadot-sdk/master/pallet_session/index.html#) allows validators to manage their session keys, provides a function for changing the session length, and handles session rotation. +* [pallet_aura](https://docs.rs/pallet-aura/latest/pallet_aura/#) extends Aura consensus by managing offline reporting. It can: + * get the current slot + * get the slot duration + * change and initialize authorities + * ensure the correctness of the state of this pallet +* [cumulus_pallet_aura_ext](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_aura_ext/index.html#) extends the Substrate AuRa pallet to make it compatible with parachains. It provides the Pallet, the Config and the GenesisConfig. + +
+ +### XCM Helpers +
+click to expand + + +* [cumulus_pallet_xcmp_queue](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcmp_queue/index.html#) Responsible for the Queues (both incoming and outgoing) for XCMP messages. This pallet does not actually receive or send messages. Its responsibility is to place the incoming and outgoing XCMP messages in their respective queues and manage these queues. +* [pallet_xcm](https://docs.rs/pallet-xcm/6.0.0/pallet_xcm/#) is responsible for filtering, routing, and executing incoming XCM. +* [cumulus_pallet_xcm](https://paritytech.github.io/polkadot-sdk/master/cumulus_pallet_xcm/index.html#) is responsible from detecting and ensuring whether XCM’s are coming from **Relay** or **Sibling** chain. +* [MessageQueue](https://docs.rs/pallet-message-queue/latest/pallet_message_queue/#) provides generalized message queuing and processing capabilities on a per-queue basis for arbitrary use-cases. + +
diff --git a/docs/content/ui-builder/building-adapters.mdx b/docs/content/ui-builder/building-adapters.mdx new file mode 100644 index 00000000..457c3028 --- /dev/null +++ b/docs/content/ui-builder/building-adapters.mdx @@ -0,0 +1,264 @@ +--- +title: Building New Adapters +--- + +This document provides a comprehensive overview of the Contracts UI Builder’s architecture, with a focus on the patterns and requirements for creating new ecosystem adapters. It’s intended for developers tasked with extending the platform to support new blockchains. + +## Core Architecture: The Adapter Pattern + +The entire system is built around a **domain-driven adapter pattern**. The goal is to keep the main application (`packages/builder`) and the UI rendering library (`packages/renderer`) completely **chain-agnostic**. They have no direct knowledge of how to interact with a specific blockchain like EVM or Solana. + +This decoupling is achieved through the `ContractAdapter` interface, defined in `packages/types/src/adapters/base.ts`. Each supported blockchain ecosystem must have its own package (e.g., `packages/adapter-evm`) that exports a class implementing this interface. + +**Key Principles:** + +* **Separation of Concerns**: Chain-specific logic is entirely encapsulated within its adapter package. +* **Shared Type System**: `packages/types` serves as the single source of truth for all shared data structures, ensuring type safety across the entire monorepo. +* **Network-Aware Adapters**: An adapter instance is not generic; it is **instantiated with a specific `NetworkConfig` object**. This makes the instance aware of its target network (e.g., Ethereum Mainnet vs. Sepolia Testnet), including its RPC endpoints, explorer URLs, and chain identifiers. The adapter stores this `networkConfig` internally and uses it for all network-dependent operations. + +```mermaid +graph TB + subgraph "Chain-Agnostic Core" + Builder["Builder Package
(Main Application)"] + Renderer["Renderer Package
(UI Rendering)"] + Types["Types Package
(Shared Type System)"] + ReactCore["React-Core Package
(Context Providers & State)"] + end + + subgraph "UI & Design System" + UI["UI Package
(Shared React Components)"] + Styles["Styles Package
(Tailwind CSS & Theme)"] + end + + subgraph "Utilities & Storage" + Utils["Utils Package
(Config, Logging, Helpers)"] + Storage["Storage Package
(IndexedDB/Dexie Services)"] + end + + subgraph "Adapter Layer" + CA["ContractAdapter
Interface"] + EVM["EVM Adapter
(Ethereum, Polygon, etc.)"] + Solana["Solana Adapter"] + Midnight["Midnight Adapter"] + Stellar["Stellar Adapter"] + NewChain["Your New Chain
Adapter"] + end + + subgraph "Configuration & Services" + AppConfig["App Config
Service"] + UserConfig["User Config
Services"] + NetworkConfigs["Network
Configurations"] + end + + subgraph "Execution Layer" + EOA["EOA Strategy"] + Relayer["Relayer Strategy"] + Multisig["Multisig Strategy"] + Wallet["Wallet
Integration"] + end + + %% Core relationships + Builder --> CA + Builder --> ReactCore + Builder --> Storage + Renderer --> CA + Renderer --> UI + ReactCore --> Types + CA --> Types + + %% UI dependencies + UI --> Utils + UI --> Styles + Builder --> UI + Builder --> Styles + Renderer --> Styles + + %% Storage and utils dependencies + Storage --> Utils + Storage --> Types + ReactCore --> Utils + + %% Adapter implementations + CA -.-> EVM + CA -.-> Solana + CA -.-> Midnight + CA -.-> Stellar + CA -.-> NewChain + + %% Configuration flow + EVM --> NetworkConfigs + EVM --> AppConfig + EVM --> UserConfig + Utils --> AppConfig + + %% Execution strategies + EVM --> EOA + EVM --> Relayer + EVM --> Multisig + EVM --> Wallet + + %% Styling + classDef corePackage fill:#ffffff,stroke:#1976d2,stroke-width:3px,color:#000000 + classDef uiPackage fill:#f5f5f5,stroke:#9c27b0,stroke-width:2px,color:#000000 + classDef utilityPackage fill:#ffffff,stroke:#4caf50,stroke-width:2px,color:#000000 + classDef adapterPackage fill:#f5f5f5,stroke:#7b1fa2,stroke-width:2px,color:#000000 + classDef configPackage fill:#ffffff,stroke:#388e3c,stroke-width:2px,color:#000000 + classDef executionPackage fill:#f5f5f5,stroke:#f57c00,stroke-width:2px,color:#000000 + classDef interfacePackage fill:#ffffff,stroke:#d32f2f,stroke-width:4px,color:#000000 + + class Builder,Renderer,Types,ReactCore corePackage + class UI,Styles uiPackage + class Utils,Storage utilityPackage + class EVM,Solana,Midnight,Stellar,NewChain adapterPackage + class AppConfig,UserConfig,NetworkConfigs configPackage + class EOA,Relayer,Multisig,Wallet executionPackage + class CA interfacePackage +``` + +## The `ContractAdapter` Interface: Your Implementation Contract + +The `ContractAdapter` interface is the most critical file for an adapter developer. Your new adapter class **must** implement every method defined here. The methods can be grouped by functionality: + +### Contract Definition & Schema Handling + +These methods handle loading and interpreting a contract’s interface (like an ABI or IDL). + +* `getContractDefinitionInputs()`: Must return a `FormFieldType[]` array that defines the inputs your adapter needs to locate a contract (e.g., an address field, an optional ABI/IDL textarea). +* `loadContract(artifacts)`: Takes the form values from `getContractDefinitionInputs` and returns a `ContractSchema`. This is your main entry point for fetching and parsing the contract’s interface. You’ll handle logic here like: "If a manual ABI is provided, parse it. If not, use the address to fetch it from a block explorer." +* `loadContractWithMetadata(artifacts)`: An enhanced version of `loadContract` that also returns metadata about the source (`fetched` or `manual`), proxy information (`ProxyInfo`), and definition details (`ContractDefinitionMetadata`). This is used for advanced features like ABI comparison and proxy detection. +* `validateContractDefinition(definition)` & `hashContractDefinition(definition)`: (Optional) Implement these to support ABI/IDL validation and version comparison features. The EVM adapter provides a reference implementation in `abi/comparison.ts`. + +### Form Generation & Type Mapping + +These methods bridge the gap between blockchain-specific types and the generic form fields used by the UI. + +* `mapParameterTypeToFieldType(parameterType)`: A simple mapping from a blockchain type string (e.g., ’uint256'`) to a default `FieldType` (e.g., ’number'`). +* `getCompatibleFieldTypes(parameterType)`: Returns an array of all `FieldType`s that could be used for a given blockchain type. For example, a ’bool'` might be compatible with ’checkbox'`, ’select'`, and ’radio'`. +* `generateDefaultField(parameter)`: Creates a complete `FormFieldType` object for a given `FunctionParameter`, including a default label, placeholder, and validation rules. + +### Transaction Execution + +This is the core of state-changing interactions. The system uses an **Execution Strategy Pattern** to support multiple ways of sending a transaction (e.g., EOA, Relayer). + +* `getSupportedExecutionMethods()`: Returns a list of supported methods (`ExecutionMethodDetail`) for your ecosystem (e.g., EOA, Multisig). +* `validateExecutionConfig(config)`: Validates a user’s `ExecutionConfig` for a specific method. For example, for an EOA with a specific address, it checks that the connected wallet matches. +* `formatTransactionData(...)`: This crucial method takes the raw `submittedInputs` from the UI form and transforms them into the data payload your blockchain’s client library expects. The EVM adapter’s implementation in `transaction/formatter.ts` shows how to parse strings into `BigInt`, checksum addresses, and structure complex tuples. +* `signAndBroadcast(...)`: The main function to execute a transaction. It receives the data from `formatTransactionData` and an `ExecutionConfig`. Your implementation should use a factory pattern to select the correct **Execution Strategy** (e.g., `EoaExecutionStrategy`, `RelayerExecutionStrategy`) based on `executionConfig.method`. +* `waitForTransactionConfirmation?(txHash)`: (Optional) Implement this to allow the UI to wait for a transaction to be finalized. + +### Read-Only Queries + +These methods handle fetching data from the blockchain without creating a transaction. + +* `isViewFunction(functionDetails)`: A simple utility to check if a function is read-only. +* `queryViewFunction(...)`: Fetches data from a read-only function. It should use the `networkConfig` to connect to the correct RPC endpoint. The EVM adapter implementation in `query/handler.ts` provides a robust example of creating a public client and handling various scenarios (e.g., using the wallet’s provider vs. a fallback provider). +* `formatFunctionResult(...)`: Formats the raw data returned from `queryViewFunction` into a human-readable string for display in the UI. This should handle `BigInt`, arrays, and complex objects. + +### Wallet Interaction & UI Facilitation + +These methods allow the adapter to provide a rich, ecosystem-specific wallet experience. The EVM adapter has a very sophisticated implementation for this, designed to be extensible with UI kits like RainbowKit. **For simpler ecosystems, your implementation can be much more direct.** + +* `supportsWalletConnection()`: Return `true` if your ecosystem has wallets. +* `getAvailableConnectors()`: Return a list of wallet connectors the user can choose from. +* `connectWallet(connectorId)`: Logic to initiate a connection with the chosen wallet. +* `disconnectWallet()`: Logic to disconnect. +* `getWalletConnectionStatus()`: Return the current status (`isConnected`, `address`, `chainId`). +* `onWalletConnectionChange?(callback)`: (Optional) An event listener for status changes. + +**Advanced UI Facilitation (The "UI Kit" Pattern):** + +This optional but powerful pattern allows an adapter to provide React components and hooks that integrate with its ecosystem’s native wallet libraries (e.g., `wagmi` for EVM). + +* `configureUiKit(config, options)`: The entry point for the application to tell the adapter which UI kit to use (e.g., ’rainbowkit'`, ’custom'`) and provide configuration. +* `getEcosystemReactUiContextProvider()`: Must return a stable React Component that provides the necessary context for your wallet library (e.g., for EVM, this is `` and ``). See `EvmWalletUiRoot.tsx` for a reference implementation that avoids UI flicker. +* `getEcosystemReactHooks()`: Must return an object of facade hooks (e.g., `useAccount`, `useSwitchChain`). These hooks should wrap the native library’s hooks. **Crucially, your implementation must map the return values to a conventional format** to ensure UI components remain chain-agnostic (e.g., map the underlying library’s `isLoading` property to `isPending`). +* `getEcosystemWalletComponents()`: Must return an object of standardized UI components (`ConnectButton`, `AccountDisplay`, etc.) for the configured UI kit. + +For a deep dive into this advanced pattern, study the EVM adapter’s wallet module (`packages/adapter-evm/src/wallet/`) and the `ADDING_NEW_UI_KITS.md` guide. + +## Standardized Adapter Package Structure + +To ensure consistency, new adapter packages (`packages/adapter-`) should follow this directory structure: + +``` +adapter-/ +└── src/ + ├── adapter.ts // Main Adapter class implementing ContractAdapter + ├── networks/ // Network configurations for your chain + │ ├── mainnet.ts + │ ├── testnet.ts + │ └── index.ts + ├── [chain-def]/ // e.g., idl/ (Solana), abi/ (EVM) - For loading contracts + │ ├── loader.ts + │ └── transformer.ts + ├── mapping/ // Type mapping and default field generation + │ ├── type-mapper.ts + │ └── field-generator.ts + ├── transform/ // Data serialization/deserialization + │ ├── input-parser.ts + │ └── output-formatter.ts + ├── transaction/ // Transaction execution (with strategy pattern) + │ ├── execution-strategy.ts + │ ├── eoa.ts // Example strategy + │ └── ... + ├── query/ // View function querying + │ ├── handler.ts + │ └── view-checker.ts + ├── wallet/ // Wallet connection & UI facilitation logic + ├── configuration/ // Explorer URLs, execution method validation + ├── types.ts // Internal types specific to this adapter + ├── utils/ // Adapter-specific utilities + └── index.ts // Main package export +``` + +## Configuration Management + +A robust adapter must handle runtime configuration overrides. The project provides utility services for this: `appConfigService`, `userRpcConfigService`, and `userExplorerConfigService`. + +Your adapter logic should follow a clear priority order when resolving settings like RPC URLs or explorer API keys: + +1. **User-Provided Config**: Check the `user...ConfigService` first. This is for settings configured by the end-user in the UI and stored in `localStorage`. +2. **Application Config**: If no user config is found, check `appConfigService`. This is for settings provided by the application deployer in `app.config.json` or environment variables. +3. **Default Config**: If neither of the above is present, fall back to the default value in the `NetworkConfig` object your adapter was instantiated with. + +The functions `resolveRpcUrl` and `resolveExplorerConfig` in the EVM adapter provide excellent reference implementations of this layered pattern. + +## Step-by-Step Guide to Creating a New Adapter + +1. **Create Package**: In the `packages/` directory, create `adapter-`. +2. **Define `package.json`**: Add dependencies on `@openzeppelin/contracts-ui-builder-types` and any chain-specific SDKs. Set up standard build scripts (copy from an existing adapter). +3. **Implement `NetworkConfig`**: In `src/networks/`, define your chain’s specific `...NetworkConfig` interface (extending `BaseNetworkConfig`) and create configuration objects for its mainnets and testnets. Export them and a combined list (e.g., `export const myChainNetworks = [...]`). +4. **Implement `ContractAdapter`**: Create `src/adapter.ts` and begin implementing the `ContractAdapter` interface, delegating logic to the modular subdirectories as described above. Start with the core methods and tackle the optional UI/wallet methods last. +5. **Register in Builder**: Open `packages/builder/src/core/ecosystemManager.ts` and: + * Add your ecosystem to the `Ecosystem` type. + * Add an entry to the `ecosystemRegistry`, providing the `networksExportName` (e.g., ’myChainNetworks'`) and the `AdapterClass`. + * Add a case to `loadAdapterPackageModule` to enable dynamic importing of your new package. +6. **Build and Test**: Build your new package and the main builder app. Add unit and integration tests to ensure your adapter functions correctly within the larger system. + +```mermaid +graph TD + A[Start: Add Support for New Chain] --> B[Create New Package: packages/adapter-name] + B --> C[Define NetworkConfig Objects in src/networks] + C --> D[Implement the ContractAdapter Interface in src/adapter.ts] + + subgraph "Implement Adapter Methods" + D1[Schema Handling: loadContract and getContractDefinitionInputs] + D2[Type Mapping: mapParameterTypeToFieldType and generateDefaultField] + D3[Transaction Logic: formatTransactionData and signAndBroadcast using Strategy Pattern] + D4[Read/Query Logic: isViewFunction and queryViewFunction] + D5[Wallet and UI Optional: connectWallet and getEcosystemReactHooks and getEcosystemWalletComponents] + end + + D --> D1 + D --> D2 + D --> D3 + D --> D4 + D --> D5 + + D5 --> E[Register Adapter in Builder: packages/builder/src/core/ecosystemManager.ts] + E --> F[Add to ecosystemRegistry] + E --> G[Add dynamic import to loadAdapterPackageModule] + + G --> H[Build and Test] + H --> I[End: New Adapter Integrated] +``` diff --git a/docs/content/ui-builder/changelog.mdx b/docs/content/ui-builder/changelog.mdx new file mode 100644 index 00000000..8dcef82f --- /dev/null +++ b/docs/content/ui-builder/changelog.mdx @@ -0,0 +1,2828 @@ +--- +title: Changelog +--- + + +# [@openzeppelin/ui-builder-utils@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-utils@0.10.1) - 2025-09-24 + +### Patch Changes + +- [#181](https://github.com/OpenZeppelin/ui-builder/pull/181) [`47ee098`](https://github.com/OpenZeppelin/ui-builder/commit/47ee098b9d17241cb9323e0b644c3e36957ec358) Thanks [@pasevin](https://github.com/pasevin)! - Disable logs in staging/production by honoring VITE_EXPORT_ENV in logger defaults. + Also keep logs enabled only for development/test. +- Updated dependencies \[[`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98)]: + - @openzeppelin/ui-builder-types@0.10.1 + + +[Changes][@openzeppelin/ui-builder-utils@0.10.1] + + + +# [@openzeppelin/ui-builder-utils@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-utils@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + + +[Changes][@openzeppelin/ui-builder-utils@0.10.0] + + + +# [@openzeppelin/ui-builder-ui@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-ui@0.10.1) - 2025-09-24 + +### Patch Changes + +- Updated dependencies \[[`47ee098`](https://github.com/OpenZeppelin/ui-builder/commit/47ee098b9d17241cb9323e0b644c3e36957ec358), [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98)]: + - @openzeppelin/ui-builder-utils@0.10.1 + - @openzeppelin/ui-builder-types@0.10.1 + + +[Changes][@openzeppelin/ui-builder-ui@0.10.1] + + + +# [@openzeppelin/ui-builder-ui@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-ui@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-ui@0.10.0] + + + +# [@openzeppelin/ui-builder-types@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-types@0.10.1) - 2025-09-24 + +### Patch Changes + +- [#183](https://github.com/OpenZeppelin/ui-builder/pull/183) [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98) Thanks [@pasevin](https://github.com/pasevin)! - Fix BytesN parameter handling in Stellar forms so base64 inputs reuse the bytes field, propagate max byte hints, and convert to ScVal correctly. + + +[Changes][@openzeppelin/ui-builder-types@0.10.1] + + + +# [@openzeppelin/ui-builder-types@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-types@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + + +[Changes][@openzeppelin/ui-builder-types@0.10.0] + + + +# [@openzeppelin/ui-builder-styles@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-styles@0.10.0) - 2025-09-24 + +### Patch Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + + +[Changes][@openzeppelin/ui-builder-styles@0.10.0] + + + +# [@openzeppelin/ui-builder-storage@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-storage@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-storage@0.10.0] + + + +# [@openzeppelin/ui-builder-renderer@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-renderer@0.10.1) - 2025-09-24 + +### Patch Changes + +- Updated dependencies \[[`47ee098`](https://github.com/OpenZeppelin/ui-builder/commit/47ee098b9d17241cb9323e0b644c3e36957ec358), [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98)]: + - @openzeppelin/ui-builder-utils@0.10.1 + - @openzeppelin/ui-builder-types@0.10.1 + - @openzeppelin/ui-builder-ui@0.10.1 + + +[Changes][@openzeppelin/ui-builder-renderer@0.10.1] + + + +# [@openzeppelin/ui-builder-renderer@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-renderer@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-ui@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-renderer@0.10.0] + + + +# [@openzeppelin/ui-builder-react-core@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-react-core@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-ui@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-react-core@0.10.0] + + + +# [@openzeppelin/ui-builder-adapter-stellar@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-stellar@0.10.1) - 2025-09-24 + +### Patch Changes + +- [#183](https://github.com/OpenZeppelin/ui-builder/pull/183) [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98) Thanks [@pasevin](https://github.com/pasevin)! - Fix BytesN parameter handling in Stellar forms so base64 inputs reuse the bytes field, propagate max byte hints, and convert to ScVal correctly. + +- Updated dependencies \[[`47ee098`](https://github.com/OpenZeppelin/ui-builder/commit/47ee098b9d17241cb9323e0b644c3e36957ec358), [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98)]: + - @openzeppelin/ui-builder-utils@0.10.1 + - @openzeppelin/ui-builder-types@0.10.1 + - @openzeppelin/ui-builder-ui@0.10.1 + + +[Changes][@openzeppelin/ui-builder-adapter-stellar@0.10.1] + + + +# [@openzeppelin/ui-builder-adapter-stellar@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-stellar@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-ui@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-adapter-stellar@0.10.0] + + + +# [@openzeppelin/ui-builder-adapter-solana@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-solana@0.10.1) - 2025-09-24 + +### Patch Changes + +- Updated dependencies \[[`47ee098`](https://github.com/OpenZeppelin/ui-builder/commit/47ee098b9d17241cb9323e0b644c3e36957ec358), [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98)]: + - @openzeppelin/ui-builder-utils@0.10.1 + - @openzeppelin/ui-builder-types@0.10.1 + + +[Changes][@openzeppelin/ui-builder-adapter-solana@0.10.1] + + + +# [@openzeppelin/ui-builder-adapter-solana@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-solana@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-adapter-solana@0.10.0] + + + +# [@openzeppelin/ui-builder-adapter-midnight@0.10.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-midnight@0.10.1) - 2025-09-24 + +### Patch Changes + +- Updated dependencies \[[`47ee098`](https://github.com/OpenZeppelin/ui-builder/commit/47ee098b9d17241cb9323e0b644c3e36957ec358), [`702ca91`](https://github.com/OpenZeppelin/ui-builder/commit/702ca91f01a35057e6d1c1809aa00bfd926bcd98)]: + - @openzeppelin/ui-builder-utils@0.10.1 + - @openzeppelin/ui-builder-types@0.10.1 + - @openzeppelin/ui-builder-ui@0.10.1 + + +[Changes][@openzeppelin/ui-builder-adapter-midnight@0.10.1] + + + +# [@openzeppelin/ui-builder-adapter-midnight@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-midnight@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-react-core@0.10.0 + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-ui@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-adapter-midnight@0.10.0] + + + +# [@openzeppelin/ui-builder-adapter-evm@0.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/ui-builder-adapter-evm@0.10.0) - 2025-09-24 + +### Minor Changes + +- [#172](https://github.com/OpenZeppelin/ui-builder/pull/172) [`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d) Thanks [@pasevin](https://github.com/pasevin)! - Rename packages from "@openzeppelin/contracts-ui-builder-_" to "@openzeppelin/ui-builder-_" and update imports across the monorepo. Legacy packages will be deprecated on npm with guidance to the new names. + +### Patch Changes + +- Updated dependencies \[[`5bf6ceb`](https://github.com/OpenZeppelin/ui-builder/commit/5bf6ceb81dacbe013eed92d6a0aee05d00c1863d)]: + - @openzeppelin/ui-builder-react-core@0.10.0 + - @openzeppelin/ui-builder-types@0.10.0 + - @openzeppelin/ui-builder-ui@0.10.0 + - @openzeppelin/ui-builder-utils@0.10.0 + + +[Changes][@openzeppelin/ui-builder-adapter-evm@0.10.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.9.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.9.0) - 2025-09-23 + +### Minor Changes + +- [#162](https://github.com/OpenZeppelin/contracts-ui-builder/pull/162) [`dca7f1c`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/dca7f1c4eb93be062c687186b85bd6f61eca8b93) Thanks [@pasevin](https://github.com/pasevin)! - Add Sourcify fallback, deep-link orchestration, provider settings UI, RouterService/deepLink utils, and ContractAdapter/app-config extensions. + +### Patch Changes + +- [#130](https://github.com/OpenZeppelin/contracts-ui-builder/pull/130) [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47) Thanks [@pasevin](https://github.com/pasevin)! - Refactor wallet interface architecture for consistency + - Move `EvmWalletConnectionStatus` interface to `wallet/types.ts` for better organization + - Add `convertWagmiToEvmStatus` utility function to eliminate code duplication + - Fix chainId type conversion in execution config validation to handle both string and number types + - Update wallet barrel exports to include new types and utilities + - Maintain structural compatibility with base `WalletConnectionStatus` interface + +- [#130](https://github.com/OpenZeppelin/contracts-ui-builder/pull/130) [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47) Thanks [@pasevin](https://github.com/pasevin)! - remove broken address validation from field configs + +- [#130](https://github.com/OpenZeppelin/contracts-ui-builder/pull/130) [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47) Thanks [@pasevin](https://github.com/pasevin)! - Update EVM adapter execution strategy interface to use TxStatus instead of string + +- [#130](https://github.com/OpenZeppelin/contracts-ui-builder/pull/130) [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47) Thanks [@pasevin](https://github.com/pasevin)! - clean up redundant ternary in array field validation + +- Updated dependencies \[[`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47), [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47), [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47), [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47), [`dca7f1c`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/dca7f1c4eb93be062c687186b85bd6f61eca8b93), [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47), [`9ed15f4`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/9ed15f4b0460d5fd8c4e94d5392dbbbeda082c47)]: + - @openzeppelin/contracts-ui-builder-types@0.9.0 + - @openzeppelin/contracts-ui-builder-utils@0.9.0 + - @openzeppelin/contracts-ui-builder-ui@0.9.0 + - @openzeppelin/contracts-ui-builder-react-core@0.9.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.9.0] + + + +# [@openzeppelin/contracts-ui-builder-utils@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-utils@0.8.0) - 2025-09-01 + +### Minor Changes + +- [#145](https://github.com/OpenZeppelin/contracts-ui-builder/pull/145) [`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9) Thanks [@pasevin](https://github.com/pasevin)! - support for new BytesField component with validation + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-utils@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.8.0) - 2025-09-01 + +### Minor Changes + +- [#145](https://github.com/OpenZeppelin/contracts-ui-builder/pull/145) [`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9) Thanks [@pasevin](https://github.com/pasevin)! - support for new BytesField component with validation + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-types@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-types@0.8.0) - 2025-09-01 + +### Minor Changes + +- [#145](https://github.com/OpenZeppelin/contracts-ui-builder/pull/145) [`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9) Thanks [@pasevin](https://github.com/pasevin)! - support for new BytesField component with validation + + +[Changes][@openzeppelin/contracts-ui-builder-types@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-storage@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-storage@0.8.0) - 2025-09-01 + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-storage@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.8.0) - 2025-09-01 + +### Minor Changes + +- [#145](https://github.com/OpenZeppelin/contracts-ui-builder/pull/145) [`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9) Thanks [@pasevin](https://github.com/pasevin)! - support for new BytesField component with validation + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + - @openzeppelin/contracts-ui-builder-ui@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-react-core@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-react-core@0.8.0) - 2025-09-01 + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + - @openzeppelin/contracts-ui-builder-ui@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-react-core@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-stellar@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-stellar@0.8.0) - 2025-09-01 + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-stellar@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-solana@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-solana@0.8.0) - 2025-09-01 + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-solana@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.8.0) - 2025-09-01 + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + - @openzeppelin/contracts-ui-builder-ui@0.8.0 + - @openzeppelin/contracts-ui-builder-react-core@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.8.0) - 2025-09-01 + +### Patch Changes + +- Updated dependencies \[[`011123e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/011123ed8345f0a1ef11f0796bcb2422504763b9)]: + - @openzeppelin/contracts-ui-builder-types@0.8.0 + - @openzeppelin/contracts-ui-builder-utils@0.8.0 + - @openzeppelin/contracts-ui-builder-ui@0.8.0 + - @openzeppelin/contracts-ui-builder-react-core@0.8.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.8.0] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.7.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.7.2) - 2025-08-29 + +### Patch Changes + +- [#143](https://github.com/OpenZeppelin/contracts-ui-builder/pull/143) [`f344326`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/f344326aab505e16468ec1b45708fc28a53df192) Thanks [@pasevin](https://github.com/pasevin)! - stabilize array ops across contexts + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.7.2] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.7.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.7.2) - 2025-08-29 + +### Patch Changes + +- Updated dependencies \[[`f344326`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/f344326aab505e16468ec1b45708fc28a53df192)]: + - @openzeppelin/contracts-ui-builder-ui@0.7.2 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.7.2] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.2) - 2025-08-29 + +### Patch Changes + +- Updated dependencies \[[`f344326`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/f344326aab505e16468ec1b45708fc28a53df192)]: + - @openzeppelin/contracts-ui-builder-ui@0.7.2 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.2] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.7.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.7.1) - 2025-08-28 + +### Patch Changes + +- [#137](https://github.com/OpenZeppelin/contracts-ui-builder/pull/137) [`73db143`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/73db1436f5c6f44062a39f262bad9a542fb85bb9) Thanks [@pasevin](https://github.com/pasevin)! - allow zero values in ArrayField required validation + +- [#139](https://github.com/OpenZeppelin/contracts-ui-builder/pull/139) [`49d7d6c`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/49d7d6c38d1890a67dfbf514161e71f46849a123) Thanks [@pasevin](https://github.com/pasevin)! - snapshot pre-append and fallback setValue to fix add-item with default 0 in ArrayField + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.7.1] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.7.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.7.1) - 2025-08-28 + +### Patch Changes + +- Updated dependencies \[[`73db143`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/73db1436f5c6f44062a39f262bad9a542fb85bb9), [`49d7d6c`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/49d7d6c38d1890a67dfbf514161e71f46849a123)]: + - @openzeppelin/contracts-ui-builder-ui@0.7.1 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.7.1] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.1) - 2025-08-28 + +### Patch Changes + +- Updated dependencies \[[`73db143`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/73db1436f5c6f44062a39f262bad9a542fb85bb9), [`49d7d6c`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/49d7d6c38d1890a67dfbf514161e71f46849a123)]: + - @openzeppelin/contracts-ui-builder-ui@0.7.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.1] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.7.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.7.1) - 2025-08-28 + +### Patch Changes + +- [#137](https://github.com/OpenZeppelin/contracts-ui-builder/pull/137) [`73db143`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/73db1436f5c6f44062a39f262bad9a542fb85bb9) Thanks [@pasevin](https://github.com/pasevin)! - clean up redundant ternary in array field validation + +- Updated dependencies \[[`73db143`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/73db1436f5c6f44062a39f262bad9a542fb85bb9), [`49d7d6c`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/49d7d6c38d1890a67dfbf514161e71f46849a123)]: + - @openzeppelin/contracts-ui-builder-ui@0.7.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.7.1] + + + +# [@openzeppelin/contracts-ui-builder-utils@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-utils@0.7.0) - 2025-08-22 + +### Minor Changes + +- [#131](https://github.com/OpenZeppelin/contracts-ui-builder/pull/131) [`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec) Thanks [@pasevin](https://github.com/pasevin)! - implements ecosystem-namespaced wallet UI configuration to support different wallet UI kits for different blockchain ecosystems + + +[Changes][@openzeppelin/contracts-ui-builder-utils@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-storage@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-storage@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-storage@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + - @openzeppelin/contracts-ui-builder-ui@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-react-core@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-react-core@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + - @openzeppelin/contracts-ui-builder-ui@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-react-core@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-stellar@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-stellar@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-stellar@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-solana@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-solana@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-solana@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.0) - 2025-08-22 + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + - @openzeppelin/contracts-ui-builder-react-core@0.7.0 + - @openzeppelin/contracts-ui-builder-ui@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.7.0) - 2025-08-22 + +### Minor Changes + +- [#131](https://github.com/OpenZeppelin/contracts-ui-builder/pull/131) [`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec) Thanks [@pasevin](https://github.com/pasevin)! - implements ecosystem-namespaced wallet UI configuration to support different wallet UI kits for different blockchain ecosystems + +### Patch Changes + +- Updated dependencies \[[`b566f80`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/b566f804b8fbc439f66fc3459c211ae4e96b75ec)]: + - @openzeppelin/contracts-ui-builder-utils@0.7.0 + - @openzeppelin/contracts-ui-builder-react-core@0.7.0 + - @openzeppelin/contracts-ui-builder-ui@0.7.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.7.0] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.6.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.6.0) - 2025-08-21 + +### Minor Changes + +- [#129](https://github.com/OpenZeppelin/contracts-ui-builder/pull/129) [`a4236e9`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/a4236e95ddda6530dfd2a87c4bc8a0915e9ff332) Thanks [@stevedylandev](https://github.com/stevedylandev)! - Patched Monad testnet config + +- [#122](https://github.com/OpenZeppelin/contracts-ui-builder/pull/122) [`3a85c72`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3a85c7296ca05c2edb9931966089f4bfd04e105a) Thanks [@stevedylandev](https://github.com/stevedylandev)! - Added Monad Testnet support + +### Patch Changes + +- [#127](https://github.com/OpenZeppelin/contracts-ui-builder/pull/127) [`acc7037`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/acc70372e7b815026331ed76f77a5d55633ec547) Thanks [@pasevin](https://github.com/pasevin)! - feat(adapter-evm): change cystom ui kit name + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.6.0] + + + +# [@openzeppelin/contracts-ui-builder-utils@0.4.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-utils@0.4.1) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + + +[Changes][@openzeppelin/contracts-ui-builder-utils@0.4.1] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.5.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.5.1) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.5.1] + + + +# [@openzeppelin/contracts-ui-builder-types@0.4.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-types@0.4.0) - 2025-08-14 + +### Minor Changes + +- [#106](https://github.com/OpenZeppelin/contracts-ui-builder/pull/106) [`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b) Thanks [@pasevin](https://github.com/pasevin)! - Extend ProxyInfo with optional adminAddress; add optional adapter method filterAutoQueryableFunctions for chain-specific auto-query filtering. + + +[Changes][@openzeppelin/contracts-ui-builder-types@0.4.0] + + + +# [@openzeppelin/contracts-ui-builder-storage@0.3.4](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-storage@0.3.4) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-storage@0.3.4] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.4.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.4.0) - 2025-08-14 + +### Minor Changes + +- [#106](https://github.com/OpenZeppelin/contracts-ui-builder/pull/106) [`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b) Thanks [@pasevin](https://github.com/pasevin)! - Use adapter-provided filtering for safe auto-queries to prevent calling admin-only getters; improve FunctionResult header layout to avoid overflow. + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-ui@0.5.1 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.4.0] + + + +# [@openzeppelin/contracts-ui-builder-react-core@0.2.5](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-react-core@0.2.5) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-ui@0.5.1 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-react-core@0.2.5] + + + +# [@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.9](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.9) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.9] + + + +# [@openzeppelin/contracts-ui-builder-adapter-solana@0.0.9](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-solana@0.0.9) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-solana@0.0.9] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.1.4](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.1.4) - 2025-08-14 + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-react-core@0.2.5 + - @openzeppelin/contracts-ui-builder-ui@0.5.1 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.1.4] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.5.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.5.0) - 2025-08-14 + +### Minor Changes + +- [#106](https://github.com/OpenZeppelin/contracts-ui-builder/pull/106) [`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b) Thanks [@pasevin](https://github.com/pasevin)! - Resolve legacy OpenZeppelin proxy implementation/admin via storage slots; expose adminAddress in proxy info; delegate auto-query filtering to adapter to avoid admin-only getters; add storage-slot debug logs. + +### Patch Changes + +- Updated dependencies \[[`ce96c10`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ce96c104e9e5df22ba335a8746cda740a70dbd0b)]: + - @openzeppelin/contracts-ui-builder-types@0.4.0 + - @openzeppelin/contracts-ui-builder-react-core@0.2.5 + - @openzeppelin/contracts-ui-builder-ui@0.5.1 + - @openzeppelin/contracts-ui-builder-utils@0.4.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.5.0] + + + +# [Trigger prod deploy (deploy-2025-08-12-1835)](https://github.com/OpenZeppelin/ui-builder/releases/tag/deploy-2025-08-12-1835) - 2025-08-12 + + + +[Changes][deploy-2025-08-12-1835] + + + +# [Trigger prod deploy (deploy-2025-08-11-1522)](https://github.com/OpenZeppelin/ui-builder/releases/tag/deploy-2025-08-11-1522) - 2025-08-11 + + + +[Changes][deploy-2025-08-11-1522] + + + +# [Trigger prod deploy (deploy-2025-08-11-1352)](https://github.com/OpenZeppelin/ui-builder/releases/tag/deploy-2025-08-11-1352) - 2025-08-11 + + + +[Changes][deploy-2025-08-11-1352] + + + +# [Trigger prod deploy (deploy-2025-08-08-1508)](https://github.com/OpenZeppelin/ui-builder/releases/tag/deploy-2025-08-08-1508) - 2025-08-08 + + + +[Changes][deploy-2025-08-08-1508] + + + +# [@openzeppelin/contracts-ui-builder-storage@0.3.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-storage@0.3.3) - 2025-08-07 + +# @openzeppelin/contracts-ui-builder-storage + +## 0.3.2 + +### Patch Changes + +- Updated dependencies \[[`521dc09`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/521dc092e2394501affc9f3f37144ba8c735591c)]: + - @openzeppelin/contracts-ui-builder-utils@0.4.0 + +## 0.3.1 + +### Patch Changes + +- [#72](https://github.com/OpenZeppelin/contracts-ui-builder/pull/72) [`ba62702`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ba62702eea64cc2a1989f2d1f568f22ff414a4ca) Thanks [@pasevin](https://github.com/pasevin)! - refactor: Updated the `ContractUIStorage` service to include the `contractDefinitionOriginal` field, allowing the original ABI of a contract to be preserved for comparison purposes. + +- Updated dependencies \[[`ba62702`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ba62702eea64cc2a1989f2d1f568f22ff414a4ca), [`ba62702`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/ba62702eea64cc2a1989f2d1f568f22ff414a4ca)]: + - @openzeppelin/contracts-ui-builder-utils@0.3.1 + - @openzeppelin/contracts-ui-builder-types@0.3.0 + +## 0.3.0 + +### Minor Changes + +- [#66](https://github.com/OpenZeppelin/contracts-ui-builder/pull/66) [`60fd645`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/60fd6457fef301f87303fd22b03e12df10c26103) Thanks [@pasevin](https://github.com/pasevin)! - initial release + +### Patch Changes + +- Updated dependencies \[[`60fd645`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/60fd6457fef301f87303fd22b03e12df10c26103)]: + - @openzeppelin/contracts-ui-builder-utils@0.3.0 + + +[Changes][@openzeppelin/contracts-ui-builder-storage@0.3.3] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.2.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.2.0) - 2025-07-18 + +### Minor Changes + +- [#56](https://github.com/OpenZeppelin/contracts-ui-builder/pull/56) [`83c430e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/83c430e86f47733bde89b560b70a7a922eebfe81) Thanks [@pasevin](https://github.com/pasevin)! - New Etherscan V2 API Client. Etherscan V2 has been enabled by default for mainnet and testnet networks, with example configurations provided to guide users. + +### Patch Changes + +- Updated dependencies \[[`83c430e`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/83c430e86f47733bde89b560b70a7a922eebfe81)]: + - @openzeppelin/contracts-ui-builder-types@0.2.0 + - @openzeppelin/contracts-ui-builder-utils@0.2.0 + - @openzeppelin/contracts-ui-builder-ui@0.2.0 + - @openzeppelin/contracts-ui-builder-react-core@0.1.3 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.2.0] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.1.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.1.3) - 2025-07-16 + +### Patch Changes + +- [#54](https://github.com/OpenZeppelin/contracts-ui-builder/pull/54) [`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6) Thanks [@pasevin](https://github.com/pasevin)! - pre-release clean up and improvements + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.1.3] + + + +# [@openzeppelin/contracts-ui-builder-styles@0.1.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-styles@0.1.2) - 2025-07-16 + +### Patch Changes + +- [#54](https://github.com/OpenZeppelin/contracts-ui-builder/pull/54) [`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6) Thanks [@pasevin](https://github.com/pasevin)! - pre-release clean up and improvements + + +[Changes][@openzeppelin/contracts-ui-builder-styles@0.1.2] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.1.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.1.3) - 2025-07-16 + +### Patch Changes + +- [#54](https://github.com/OpenZeppelin/contracts-ui-builder/pull/54) [`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6) Thanks [@pasevin](https://github.com/pasevin)! - pre-release clean up and improvements + +- Updated dependencies \[[`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6)]: + - @openzeppelin/contracts-ui-builder-ui@0.1.3 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.1.3] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.3) - 2025-07-16 + +### Patch Changes + +- [`6d74481`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/6d7448140936f5c8dfadac3bca05dde54d468167) Thanks [@pasevin](https://github.com/pasevin)! - fix test script to handle no test files + +- Updated dependencies \[[`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6)]: + - @openzeppelin/contracts-ui-builder-ui@0.1.3 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.3] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.1.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.1.3) - 2025-07-16 + +### Patch Changes + +- [#54](https://github.com/OpenZeppelin/contracts-ui-builder/pull/54) [`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6) Thanks [@pasevin](https://github.com/pasevin)! - pre-release clean up and improvements + +- Updated dependencies \[[`63fca98`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/63fca981f56bf9b2bb7c43c720bea3cbbd53d6f6)]: + - @openzeppelin/contracts-ui-builder-ui@0.1.3 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.1.3] + + + +# [@openzeppelin/contracts-ui-builder-utils@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-utils@0.1.1) - 2025-07-15 + +# @openzeppelin/transaction-form-utils + +## 1.17.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/contracts-ui-builder-types@1.17.0 + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/contracts-ui-builder-types@1.16.0 + + +[Changes][@openzeppelin/contracts-ui-builder-utils@0.1.1] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.1.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.1.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-types@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.1.2] + + + +# [@openzeppelin/contracts-ui-builder-ui@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-ui@0.1.1) - 2025-07-15 + +# @openzeppelin/transaction-form-ui + +## 1.18.0 + +### Minor Changes + +- [`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88) Thanks [@pasevin](https://github.com/pasevin)! - deduplicates code + +## 1.17.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/contracts-ui-builder-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/contracts-ui-builder-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + +## 1.15.1 + +### Patch Changes + +- [`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d) Thanks [@pasevin](https://github.com/pasevin)! - Improve size responsiveness across UI components + + Enhanced responsive layout and sizing for RelayerConfiguration components including RelayerCredentialsCard, RelayerGasConfigurationCard, RelayerHeader, RelayerSelectionCard, and RelayerDetailsCard. Improved address display component responsiveness. + + +[Changes][@openzeppelin/contracts-ui-builder-ui@0.1.1] + + + +# [@openzeppelin/contracts-ui-builder-types@0.1.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-types@0.1.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + + +[Changes][@openzeppelin/contracts-ui-builder-types@0.1.2] + + + +# [@openzeppelin/contracts-ui-builder-types@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-types@0.1.1) - 2025-07-15 + +# @openzeppelin/contracts-ui-builder-types + +## 1.17.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + + +[Changes][@openzeppelin/contracts-ui-builder-types@0.1.1] + + + +# [@openzeppelin/contracts-ui-builder-styles@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-styles@0.1.1) - 2025-07-15 + +# @openzeppelin/transaction-form-styles + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + + +[Changes][@openzeppelin/contracts-ui-builder-styles@0.1.1] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.1.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.1.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-types@0.1.2 + - @openzeppelin/contracts-ui-builder-ui@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.1.2] + + + +# [@openzeppelin/contracts-ui-builder-renderer@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-renderer@0.1.1) - 2025-07-15 + +# @openzeppelin/transaction-form-renderer + +## 1.17.1 + +### Patch Changes + +- Updated dependencies \[[`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88)]: + - @openzeppelin/transaction-form-ui@1.18.0 + +## 1.17.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/contracts-ui-builder-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + - @openzeppelin/transaction-form-ui@1.17.0 + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/contracts-ui-builder-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + +## 1.15.1 + +### Patch Changes + +- [`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d) Thanks [@pasevin](https://github.com/pasevin)! - Respect isHidden field property to hide fields from UI + + Fixed DynamicFormField component to properly respect the isHidden field property, ensuring that fields marked as hidden are not displayed in the form UI. + +- Updated dependencies \[[`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d)]: + - @openzeppelin/transaction-form-ui@1.15.1 + + +[Changes][@openzeppelin/contracts-ui-builder-renderer@0.1.1] + + + +# [@openzeppelin/contracts-ui-builder-react-core@0.1.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-react-core@0.1.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-types@0.1.2 + - @openzeppelin/contracts-ui-builder-ui@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-react-core@0.1.2] + + + +# [@openzeppelin/contracts-ui-builder-react-core@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-react-core@0.1.1) - 2025-07-15 + +# @openzeppelin/transaction-form-react-core + +## 1.17.0 + +### Minor Changes + +- [`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88) Thanks [@pasevin](https://github.com/pasevin)! - deduplicates code + +### Patch Changes + +- Updated dependencies \[[`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88)]: + - @openzeppelin/transaction-form-ui@1.18.0 + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/contracts-ui-builder-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + + +[Changes][@openzeppelin/contracts-ui-builder-react-core@0.1.1] + + + +# [@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.2) - 2025-07-15 + +### Patch Changes + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-types@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.2] + + + +# [@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.1) - 2025-07-15 + +# @openzeppelin/transaction-form-adapter-stellar + +## 0.3.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/contracts-ui-builder-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + +## 0.2.1 + +### Patch Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/contracts-ui-builder-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.1] + + + +# [@openzeppelin/contracts-ui-builder-adapter-solana@0.0.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-solana@0.0.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-types@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-solana@0.0.2] + + + +# [@openzeppelin/contracts-ui-builder-adapter-solana@0.0.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-solana@0.0.1) - 2025-07-15 + +# @openzeppelin/transaction-form-adapter-solana + +## 0.3.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/contracts-ui-builder-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + +## 0.2.1 + +### Patch Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/contracts-ui-builder-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-solana@0.0.1] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-react-core@0.1.2 + - @openzeppelin/contracts-ui-builder-types@0.1.2 + - @openzeppelin/contracts-ui-builder-ui@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.2] + + + +# [@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.1) - 2025-07-15 + +# @openzeppelin/transaction-form-adapter-midnight + +## 0.3.1 + +### Patch Changes + +- Updated dependencies \[[`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88)]: + - @openzeppelin/transaction-form-react-core@1.17.0 + - @openzeppelin/transaction-form-ui@1.18.0 + +## 0.3.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/contracts-ui-builder-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + - @openzeppelin/transaction-form-ui@1.17.0 + +## 0.2.2 + +### Patch Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-react-core@1.16.0 + - @openzeppelin/contracts-ui-builder-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies \[[`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d)]: + - @openzeppelin/transaction-form-ui@1.15.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.1] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.1.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.1.2) - 2025-07-15 + +### Patch Changes + +- [#52](https://github.com/OpenZeppelin/contracts-ui-builder/pull/52) [`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037) Thanks [@pasevin](https://github.com/pasevin)! - resolves clean build issues due to missing packages + +- Updated dependencies \[[`3cb6dd7`](https://github.com/OpenZeppelin/contracts-ui-builder/commit/3cb6dd7e4f2bdf51860ae6abe51432bba0828037)]: + - @openzeppelin/contracts-ui-builder-react-core@0.1.2 + - @openzeppelin/contracts-ui-builder-types@0.1.2 + - @openzeppelin/contracts-ui-builder-ui@0.1.2 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.1.2] + + + +# [@openzeppelin/contracts-ui-builder-adapter-evm@0.1.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/contracts-ui-builder-adapter-evm@0.1.1) - 2025-07-15 + +# @openzeppelin/transaction-form-adapter-evm + +## 1.18.0 + +### Minor Changes + +- [#46](https://github.com/OpenZeppelin/transaction-form-builder/pull/46) [`777a246`](https://github.com/OpenZeppelin/transaction-form-builder/commit/777a246fa3c4112ee91fd2a0279e86267d0574e5) Thanks [@pasevin](https://github.com/pasevin)! - Add support for 9 major EVM networks + +## 1.17.1 + +### Patch Changes + +- [#42](https://github.com/OpenZeppelin/transaction-form-builder/pull/42) [`2d9a867`](https://github.com/OpenZeppelin/transaction-form-builder/commit/2d9a86741f1b7cd71ca8e45f36e26ceef9d5b809) Thanks [@pasevin](https://github.com/pasevin)! - sync package versions + +## 1.17.0 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + - @openzeppelin/transaction-form-ui@1.17.0 + +## 1.16.0 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-react-core@1.16.0 + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + +## 1.15.1 + +### Patch Changes + +- [`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d) Thanks [@pasevin](https://github.com/pasevin)! - Fix default speed configuration not being applied on initial mount + + Resolves bug where UI showed "Fast Speed Preset Active" but exported configuration used fallback gasPrice (20 gwei) instead of speed: 'fast'. Now ensures the default speed preset is properly communicated to the parent component and included in exported configurations. + +- Updated dependencies \[[`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d)]: + - @openzeppelin/transaction-form-ui@1.15.1 + + +[Changes][@openzeppelin/contracts-ui-builder-adapter-evm@0.1.1] + + + +# [@openzeppelin/transaction-form-adapter-evm@1.18.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-evm@1.18.0) - 2025-07-14 + +### Minor Changes + +- [#46](https://github.com/OpenZeppelin/transaction-form-builder/pull/46) [`777a246`](https://github.com/OpenZeppelin/transaction-form-builder/commit/777a246fa3c4112ee91fd2a0279e86267d0574e5) Thanks [@pasevin](https://github.com/pasevin)! - Add support for 9 major EVM networks + + +[Changes][@openzeppelin/transaction-form-adapter-evm@1.18.0] + + + +# [@openzeppelin/transaction-form-ui@1.18.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-ui@1.18.0) - 2025-07-11 + +### Minor Changes + +- [`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88) Thanks [@pasevin](https://github.com/pasevin)! - deduplicates code + + +[Changes][@openzeppelin/transaction-form-ui@1.18.0] + + + +# [@openzeppelin/transaction-form-renderer@1.17.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-renderer@1.17.1) - 2025-07-11 + +### Patch Changes + +- Updated dependencies \[[`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88)]: + - @openzeppelin/transaction-form-ui@1.18.0 + + +[Changes][@openzeppelin/transaction-form-renderer@1.17.1] + + + +# [@openzeppelin/transaction-form-react-core@1.17.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-react-core@1.17.0) - 2025-07-11 + +### Minor Changes + +- [`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88) Thanks [@pasevin](https://github.com/pasevin)! - deduplicates code + +### Patch Changes + +- Updated dependencies \[[`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88)]: + - @openzeppelin/transaction-form-ui@1.18.0 + + +[Changes][@openzeppelin/transaction-form-react-core@1.17.0] + + + +# [@openzeppelin/transaction-form-adapter-midnight@0.3.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-midnight@0.3.1) - 2025-07-11 + +### Patch Changes + +- Updated dependencies \[[`ac72bfd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac72bfddf5e16b75b82a9d33713b37b97dc71f88)]: + - @openzeppelin/transaction-form-react-core@1.17.0 + - @openzeppelin/transaction-form-ui@1.18.0 + + +[Changes][@openzeppelin/transaction-form-adapter-midnight@0.3.1] + + + +# [@openzeppelin/transaction-form-adapter-evm@1.17.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-evm@1.17.1) - 2025-07-11 + +### Patch Changes + +- [#42](https://github.com/OpenZeppelin/transaction-form-builder/pull/42) [`2d9a867`](https://github.com/OpenZeppelin/transaction-form-builder/commit/2d9a86741f1b7cd71ca8e45f36e26ceef9d5b809) Thanks [@pasevin](https://github.com/pasevin)! - sync package versions + + +[Changes][@openzeppelin/transaction-form-adapter-evm@1.17.1] + + + +# [@openzeppelin/transaction-form-utils@1.17.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-utils@1.17.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + + +[Changes][@openzeppelin/transaction-form-utils@1.17.0] + + + +# [@openzeppelin/transaction-form-ui@1.17.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-ui@1.17.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + + +[Changes][@openzeppelin/transaction-form-ui@1.17.0] + + + +# [@openzeppelin/transaction-form-types@1.17.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-types@1.17.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + + +[Changes][@openzeppelin/transaction-form-types@1.17.0] + + + +# [@openzeppelin/transaction-form-renderer@1.17.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-renderer@1.17.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + - @openzeppelin/transaction-form-ui@1.17.0 + + +[Changes][@openzeppelin/transaction-form-renderer@1.17.0] + + + +# [@openzeppelin/transaction-form-adapter-stellar@0.3.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-stellar@0.3.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + + +[Changes][@openzeppelin/transaction-form-adapter-stellar@0.3.0] + + + +# [@openzeppelin/transaction-form-adapter-solana@0.3.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-solana@0.3.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + + +[Changes][@openzeppelin/transaction-form-adapter-solana@0.3.0] + + + +# [@openzeppelin/transaction-form-adapter-midnight@0.3.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-midnight@0.3.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + - @openzeppelin/transaction-form-ui@1.17.0 + + +[Changes][@openzeppelin/transaction-form-adapter-midnight@0.3.0] + + + +# [@openzeppelin/transaction-form-adapter-evm@1.17.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-evm@1.17.0) - 2025-07-10 + +### Minor Changes + +- [#39](https://github.com/OpenZeppelin/transaction-form-builder/pull/39) [`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a) Thanks [@pasevin](https://github.com/pasevin)! - Supports block explorer configuration in the UI + +### Patch Changes + +- Updated dependencies \[[`f507dcd`](https://github.com/OpenZeppelin/transaction-form-builder/commit/f507dcdc6cab173c812f9111c9c57d523d20740a)]: + - @openzeppelin/transaction-form-types@1.17.0 + - @openzeppelin/transaction-form-utils@1.17.0 + - @openzeppelin/transaction-form-ui@1.17.0 + + +[Changes][@openzeppelin/transaction-form-adapter-evm@1.17.0] + + + +# [@openzeppelin/transaction-form-utils@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-utils@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-types@1.16.0 + + +[Changes][@openzeppelin/transaction-form-utils@1.16.0] + + + +# [@openzeppelin/transaction-form-ui@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-ui@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + + +[Changes][@openzeppelin/transaction-form-ui@1.16.0] + + + +# [@openzeppelin/transaction-form-types@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-types@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + + +[Changes][@openzeppelin/transaction-form-types@1.16.0] + + + +# [@openzeppelin/transaction-form-styles@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-styles@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + + +[Changes][@openzeppelin/transaction-form-styles@1.16.0] + + + +# [@openzeppelin/transaction-form-renderer@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-renderer@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + + +[Changes][@openzeppelin/transaction-form-renderer@1.16.0] + + + +# [@openzeppelin/transaction-form-react-core@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-react-core@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + + +[Changes][@openzeppelin/transaction-form-react-core@1.16.0] + + + +# [@openzeppelin/transaction-form-adapter-stellar@0.2.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-stellar@0.2.1) - 2025-07-09 + +### Patch Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + + +[Changes][@openzeppelin/transaction-form-adapter-stellar@0.2.1] + + + +# [@openzeppelin/transaction-form-adapter-solana@0.2.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-solana@0.2.1) - 2025-07-09 + +### Patch Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + + +[Changes][@openzeppelin/transaction-form-adapter-solana@0.2.1] + + + +# [@openzeppelin/transaction-form-adapter-midnight@0.2.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-midnight@0.2.2) - 2025-07-09 + +### Patch Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-react-core@1.16.0 + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + + +[Changes][@openzeppelin/transaction-form-adapter-midnight@0.2.2] + + + +# [@openzeppelin/transaction-form-adapter-evm@1.16.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-adapter-evm@1.16.0) - 2025-07-09 + +### Minor Changes + +- [#37](https://github.com/OpenZeppelin/transaction-form-builder/pull/37) [`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe) Thanks [@pasevin](https://github.com/pasevin)! - Introduces RPC configuration UI in the core and exported apps + +### Patch Changes + +- Updated dependencies \[[`6b20ff8`](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b20ff82cab748db41797dff0891890e35a24bfe)]: + - @openzeppelin/transaction-form-react-core@1.16.0 + - @openzeppelin/transaction-form-types@1.16.0 + - @openzeppelin/transaction-form-utils@1.16.0 + - @openzeppelin/transaction-form-ui@1.16.0 + + +[Changes][@openzeppelin/transaction-form-adapter-evm@1.16.0] + + + +# [@openzeppelin/transaction-form-renderer@1.15.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/@openzeppelin/transaction-form-renderer@1.15.1) - 2025-07-07 + +### Patch Changes + +- [`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d) Thanks [@pasevin](https://github.com/pasevin)! - Respect isHidden field property to hide fields from UI + + Fixed DynamicFormField component to properly respect the isHidden field property, ensuring that fields marked as hidden are not displayed in the form UI. + +- Updated dependencies \[[`39b196c`](https://github.com/OpenZeppelin/transaction-form-builder/commit/39b196cdea737678676f3da262e460201335d40d)]: + - @openzeppelin/transaction-form-ui@1.15.1 + + +[Changes][@openzeppelin/transaction-form-renderer@1.15.1] + + + +# [v1.15.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.15.0) - 2025-07-06 + +# [1.15.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.14.3...v1.15.0) (2025-07-06) + + +### Bug Fixes + +* **config:** resolve pnpm --filter build issues with tsup configuration ([18bcea3](https://github.com/OpenZeppelin/transaction-form-builder/commit/18bcea32a1bee8981085bad2d161bf091bc6278f)) +* **config:** tsconfig errors ([985b1ed](https://github.com/OpenZeppelin/transaction-form-builder/commit/985b1ed3394e135116c5fadaf700cb933c5598ce)) +* **core:** execution method not rendering on mount ([0d19975](https://github.com/OpenZeppelin/transaction-form-builder/commit/0d1997573402f05fa1d7a8c62d9576f18db69dbe)) +* **core:** preselect EOA as a default execution method in UI ([dc20318](https://github.com/OpenZeppelin/transaction-form-builder/commit/dc20318f20809faa49a048928085b4b4e82351bf)) +* **core:** tests ([6f7d94e](https://github.com/OpenZeppelin/transaction-form-builder/commit/6f7d94e2a53a5102e362500e7e4a0c2b6d8e6d94)) +* **core:** tests ([24a2f04](https://github.com/OpenZeppelin/transaction-form-builder/commit/24a2f042be2ba53e6bf89ce6f5590859983bdee8)) + + +### Features + +* **adapter:** implement relayer execution logic ([0a98565](https://github.com/OpenZeppelin/transaction-form-builder/commit/0a98565edf33323df77900b77ac6ce595eae4dd1)) +* **core:** complete basic relayers integration ([f4e9dda](https://github.com/OpenZeppelin/transaction-form-builder/commit/f4e9ddae6b952e73c27f788b53d3aba5286adc64)) +* **core:** complete relayer integration ([8980288](https://github.com/OpenZeppelin/transaction-form-builder/commit/89802882b260aa9c8925942c9e3faec289bda31b)) +* **core:** lay groundwork for relayer execution method ([dfba652](https://github.com/OpenZeppelin/transaction-form-builder/commit/dfba6526961749373da222903ff99a245a6e3877)) +* **core:** show relayer details card in form builder relayer configuration step ([8ba9cd4](https://github.com/OpenZeppelin/transaction-form-builder/commit/8ba9cd45b01155f3aab9e2948fdf4d9560ff6eb6)) +* **form:** make the invalid execution method trigger more prominent ([3788256](https://github.com/OpenZeppelin/transaction-form-builder/commit/3788256a02c1b670f62f5c4a9d33f7e3ebddecd2)) +* **ui:** add relayer details card with balance and other data ([77b32bc](https://github.com/OpenZeppelin/transaction-form-builder/commit/77b32bc7a601d9b3906d964ba2ad21c5db451816)) +* **ui:** adjust radio field size ([695e0e4](https://github.com/OpenZeppelin/transaction-form-builder/commit/695e0e4d434c7b89da00facc24ce74ded55633b0)) +* **ui:** improve radio field looks ([5f26caf](https://github.com/OpenZeppelin/transaction-form-builder/commit/5f26caf9d180310f6d02055e86b7e36e0c5b4cec)) + + + + + +[Changes][v1.15.0] + + + +# [v1.14.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.14.3) - 2025-06-30 + +## [1.14.3](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.14.2...v1.14.3) (2025-06-30) + + +### Bug Fixes + +* **export:** exclude uiKitConfig from the form renderer schema ([44980ea](https://github.com/OpenZeppelin/transaction-form-builder/commit/44980ea1545dec5b7287f73ba5fbf1e00e5cf6d2)) + + + + + +[Changes][v1.14.3] + + + +# [v1.14.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.14.2) - 2025-06-27 + +## [1.14.2](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.14.1...v1.14.2) (2025-06-27) + + +### Bug Fixes + +* **core:** prevent duplicate carets in package versions ([a64c0c7](https://github.com/OpenZeppelin/transaction-form-builder/commit/a64c0c7efc59cec02d3507de238390b513d6f2db)) +* **export:** apply latest dependency versions to exported app ([2f38de2](https://github.com/OpenZeppelin/transaction-form-builder/commit/2f38de2f4385628a3a645b59019479a0077f4ebd)) +* **export:** update snapshot tests ([141f561](https://github.com/OpenZeppelin/transaction-form-builder/commit/141f561927d51ee9af51a11b4ff5d9073d3230dc)) + + + + + +[Changes][v1.14.2] + + + +# [v1.14.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.14.1) - 2025-06-27 + +## [1.14.1](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.14.0...v1.14.1) (2025-06-27) + + +### Bug Fixes + +* **core:** remove template exclusion from vite build config ([5394f25](https://github.com/OpenZeppelin/transaction-form-builder/commit/5394f25cc06b2423e5c04c8a6e408aea8a7741ba)) + + + + + +[Changes][v1.14.1] + + + +# [v1.14.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.14.0) - 2025-06-27 + +# [1.14.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.13.2...v1.14.0) (2025-06-27) + + +### Bug Fixes + +* **export:** use eager loading for template files to fix production export ([8a1a2cb](https://github.com/OpenZeppelin/transaction-form-builder/commit/8a1a2cbffc8e4f7102450ea05f9b6d81aa9aaca5)) + + +### Features + +* **export:** implement code splitting for export functionality ([8a4ecdc](https://github.com/OpenZeppelin/transaction-form-builder/commit/8a4ecdcd9ebd17c836a702d43ec011dfe5a02394)) + + + + + +[Changes][v1.14.0] + + + +# [v1.13.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.13.2) - 2025-06-27 + +## [1.13.2](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.13.1...v1.13.2) (2025-06-27) + + +### Bug Fixes + +* **config:** resolve build hanging and style issues ([e5c9ef7](https://github.com/OpenZeppelin/transaction-form-builder/commit/e5c9ef7d3281ef118e203f053d2c42ea984687fd)) +* **config:** resolve build hanging and style issues ([8d21a8f](https://github.com/OpenZeppelin/transaction-form-builder/commit/8d21a8f706545cd04ec34b7a9f63559a1106ee43)) + + + + + +[Changes][v1.13.2] + + + +# [v1.13.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.13.1) - 2025-06-26 + +## [1.13.1](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.13.0...v1.13.1) (2025-06-26) + + +### Bug Fixes + +* **adapter:** missing ui dep ([d46fe93](https://github.com/OpenZeppelin/transaction-form-builder/commit/d46fe9376db353c5e78f953cf877a575f24aeab1)) +* **config:** attempt to simplify shared vite config ([9ea878e](https://github.com/OpenZeppelin/transaction-form-builder/commit/9ea878ec547c294d93e2fcc9f1fda0ae7ed7e6d6)) +* **config:** attempt two ([e141c75](https://github.com/OpenZeppelin/transaction-form-builder/commit/e141c752f2b2db18f3080112d07b66334525023b)) +* **config:** correct vite config for dev server ([4728c71](https://github.com/OpenZeppelin/transaction-form-builder/commit/4728c71392fd097f15934dbd58d762155db60648)) +* **config:** ensure clean build every time ([c92eba4](https://github.com/OpenZeppelin/transaction-form-builder/commit/c92eba477db39d8f92a9fa32ed0f20a114cc4551)) +* **config:** migrate all packages to Vite for ES module compatibility ([55fcc07](https://github.com/OpenZeppelin/transaction-form-builder/commit/55fcc073659589c6297f83381e98c1afd86d0074)) +* **config:** resolve ES module imports and type exports ([92e74ac](https://github.com/OpenZeppelin/transaction-form-builder/commit/92e74aca3e9bdf00cd20418172b0e0e8bf32399a)) +* **config:** unify node types package version ([3eb17ba](https://github.com/OpenZeppelin/transaction-form-builder/commit/3eb17ba0618be1197f0fe086f7b1d86bbeb9e980)) +* **export:** template typing issues breaking builds ([e33c5ad](https://github.com/OpenZeppelin/transaction-form-builder/commit/e33c5adfe0101ca682c9c89d8436230c9bb926a9)) +* **tests:** remove unused variables ([d29bc16](https://github.com/OpenZeppelin/transaction-form-builder/commit/d29bc16cd53df43a868be69cb2dd6f26f57eaa57)) +* **tests:** update export tests to match new component architecture ([23063bc](https://github.com/OpenZeppelin/transaction-form-builder/commit/23063bcde8e8a542fcd1b9b61924da71542787f7)) + + + + + +[Changes][v1.13.1] + + + +# [v1.13.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.13.0) - 2025-06-25 + +# [1.13.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.12.0...v1.13.0) (2025-06-25) + + +### Features + +* **release:** configure packages for GitHub registry ([dcb6b15](https://github.com/OpenZeppelin/transaction-form-builder/commit/dcb6b15aeaed638d8f115294341d46743e4e1581)) + + + + + +[Changes][v1.13.0] + + + +# [v1.12.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.12.0) - 2025-06-25 + +# [1.12.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.11.0...v1.12.0) (2025-06-25) + + +### Bug Fixes + +* **core:** regenerate pnpm-lock.yaml to sync with package.json dependencies ([93d54d2](https://github.com/OpenZeppelin/transaction-form-builder/commit/93d54d23a8790dfb915de5d546cf27d793cdd07b)) + + +### Features + +* **form:** remove externalToggleMode from contract state widget ([b546f8c](https://github.com/OpenZeppelin/transaction-form-builder/commit/b546f8c7d3783e86dab5898b6c4e8b0c8f41393e)) +* **ui:** polish exported app UI to match core form builder ([36dff14](https://github.com/OpenZeppelin/transaction-form-builder/commit/36dff147fd658d829553a9e7ad3fb85959824055)) + + + + + +[Changes][v1.12.0] + + + +# [v1.11.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.11.0) - 2025-06-25 + +# [1.11.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.10.0...v1.11.0) (2025-06-25) + + +### Bug Fixes + +* **core:** check for none condition too in uikit ([df5f496](https://github.com/OpenZeppelin/transaction-form-builder/commit/df5f496d8df7f6907a32633ade3103e34d49e9ac)) +* **core:** merge branch 'main' into feature/plat-6737-ui-kit-selector-in-the-form-builder-wizard ([325d7cf](https://github.com/OpenZeppelin/transaction-form-builder/commit/325d7cfe5f551910aaf7dad25ff59e71b9ae8741)) +* **core:** resolve build issues ([ed53f14](https://github.com/OpenZeppelin/transaction-form-builder/commit/ed53f149e00b95c8f4d37ac5837707e40f216729)) +* **core:** set default execution method to unblock step validation ([539fc9d](https://github.com/OpenZeppelin/transaction-form-builder/commit/539fc9dd3b4be4446911342f5a113c085a48c9c3)) + + +### Features + +* **core:** add ui kit selection in customize step ([0bf4f60](https://github.com/OpenZeppelin/transaction-form-builder/commit/0bf4f601e22de6360506b41096081414da5552ab)) +* **core:** add view docs for ui kit ([b1515b2](https://github.com/OpenZeppelin/transaction-form-builder/commit/b1515b2f20ee48946078ee549843fdea07e37bcd)) +* **core:** simplify rainbowkit config ui ([017de49](https://github.com/OpenZeppelin/transaction-form-builder/commit/017de491bdd0adc44642d6f82da43b0fd08f95cb)) +* **export:** finalize uikit selector logic to work e2e ([3801273](https://github.com/OpenZeppelin/transaction-form-builder/commit/3801273cc66b01fe3da9da1b5ab97ec76b432e54)) + + + + + +[Changes][v1.11.0] + + + +# [v1.10.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.10.0) - 2025-06-13 + +# [1.10.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.9.0...v1.10.0) (2025-06-13) + + +### Bug Fixes + +* **adapter:** add getContractDefinitionInputs to rest of the adapters ([250bc8e](https://github.com/OpenZeppelin/transaction-form-builder/commit/250bc8ef37b5aafec59141ee0ddb1ca7b689068f)) +* **core:** reset to avoid stale inputs ([363bec6](https://github.com/OpenZeppelin/transaction-form-builder/commit/363bec6e725ffd4ec04afd9cdb987cc9298acc4d)) + + +### Features + +* **adapter:** implement schema-driven contract definition UI ([d8a686c](https://github.com/OpenZeppelin/transaction-form-builder/commit/d8a686cbf9dafabfa8bcf4f410c64f480e92a44a)) + + + + + +[Changes][v1.10.0] + + + +# [v1.9.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.9.0) - 2025-06-11 + +# [1.9.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.8.1...v1.9.0) (2025-06-11) + + +### Features + +* **adapter:** implement wallet connection feature for midnight lace wallet ([c8a2670](https://github.com/OpenZeppelin/transaction-form-builder/commit/c8a2670e330c808b8f1f04017023174ddb082670)) + + + + + +[Changes][v1.9.0] + + + +# [v1.8.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.8.1) - 2025-06-07 + +## [1.8.1](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.8.0...v1.8.1) (2025-06-07) + + +### Bug Fixes + +* **core:** remove unreachable adapter warning ([ea1b01e](https://github.com/OpenZeppelin/transaction-form-builder/commit/ea1b01efce14ea80a947b7f2d47906265c98be65)) + + + + + +[Changes][v1.8.1] + + + +# [v1.8.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.8.0) - 2025-06-07 + +# [1.8.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.7.0...v1.8.0) (2025-06-07) + + +### Bug Fixes + +* **tests:** update EVM adapter tests for correct field type expectations ([aa433ad](https://github.com/OpenZeppelin/transaction-form-builder/commit/aa433ad028d103fcd7c6c1ac00a0e3907e68ddc1)) +* **ui:** ensure that the Input component is always treated as a controlled component ([b6cb92f](https://github.com/OpenZeppelin/transaction-form-builder/commit/b6cb92fc84923f6accfb49d62d4c1db77f42dc66)) +* **ui:** explicitly prevent input of non-digits in the number field ([3b3b1f8](https://github.com/OpenZeppelin/transaction-form-builder/commit/3b3b1f880ada507dc2c2a2763c590ad64414fc2e)) +* **ui:** required boolean field should accept true of false ([a2237cc](https://github.com/OpenZeppelin/transaction-form-builder/commit/a2237cc7235872409eef6f58fd288796348a0fe4)) + + +### Features + +* **core:** change the rainbowkit config ([ef2f3be](https://github.com/OpenZeppelin/transaction-form-builder/commit/ef2f3be800efd6fed961caeb2edcf128d42e31f1)) +* **form:** implement full support for complex nested field types ([2353ed0](https://github.com/OpenZeppelin/transaction-form-builder/commit/2353ed0a4b71e1035519869084f24d35dfa4bf4d)) + + + + + +[Changes][v1.8.0] + + + +# [v1.7.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.7.0) - 2025-06-06 + +# [1.7.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.6.2...v1.7.0) (2025-06-06) + + +### Features + +* **adapter:** add native prop-based ConnectButton customizations ([ca35f99](https://github.com/OpenZeppelin/transaction-form-builder/commit/ca35f992af82989e9d8350d7d84bdad36bb2f00d)) + + + + + +[Changes][v1.7.0] + + + +# [v1.6.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.6.2) - 2025-06-03 + +## [1.6.2](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.6.1...v1.6.2) (2025-06-03) + + +### Bug Fixes + +* **core:** dynamic prod build dynamic config imports ([7a0e017](https://github.com/OpenZeppelin/transaction-form-builder/commit/7a0e0172add305fe44453ff4f1b094a5590c90ed)) + + + + + +[Changes][v1.6.2] + + + +# [v1.6.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.6.1) - 2025-06-03 + +## [1.6.1](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.6.0...v1.6.1) (2025-06-03) + + +### Bug Fixes + +* **core:** resolve JavaScript heap out of memory in CI builds ([f45f9a3](https://github.com/OpenZeppelin/transaction-form-builder/commit/f45f9a31b5edce95e987308457c9f9b8afffd7c2)) +* **export:** allow exporting forms without fields ([ab98f90](https://github.com/OpenZeppelin/transaction-form-builder/commit/ab98f90686606d4f8cf79ff0c1491b8708d9eccc)) + + + + + +[Changes][v1.6.1] + + + +# [v1.6.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.6.0) - 2025-06-02 + +# [1.6.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.5.0...v1.6.0) (2025-06-02) + + +### Bug Fixes + +* **core:** trigger wallet network switch on initial load based on the network config ([24e4240](https://github.com/OpenZeppelin/transaction-form-builder/commit/24e4240c11936213f34657c4ae8518a1988aa46c)) +* **export:** handle Tailwind v4 config in export process to prevent content path warning ([2b3d927](https://github.com/OpenZeppelin/transaction-form-builder/commit/2b3d9272e5d00b07a2d1ce2659da1ed951846bbc)) + + +### Features + +* **adapter:** implement full rainbowkit integration with extensibility ([cb88cf6](https://github.com/OpenZeppelin/transaction-form-builder/commit/cb88cf6424b2d7ab1f0e089083ce8d0c750d0d78)) +* **adapter:** implement RainbowKit UI provider and component integration ([3bc2eaa](https://github.com/OpenZeppelin/transaction-form-builder/commit/3bc2eaa5ba16c2864ed1696bc977da00959c8509)) + + + + + +[Changes][v1.6.0] + + + +# [v1.5.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.5.0) - 2025-05-22 + +# [1.5.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.4.0...v1.5.0) (2025-05-22) + + +### Bug Fixes + +* **adapter:** ensure WagmiProvider initializes synchronously ([6b2a29c](https://github.com/OpenZeppelin/transaction-form-builder/commit/6b2a29c2ec7f14f7e8853053c09545dd1b9ead94)) +* **adapter:** improve connect button loading state tracking ([a91f171](https://github.com/OpenZeppelin/transaction-form-builder/commit/a91f1710b1490a296f91e3446ef4c5d9ce624c37)) +* **adapter:** linting issues ([eb113a1](https://github.com/OpenZeppelin/transaction-form-builder/commit/eb113a1d7488f289ae33e1afaa357deebf893db5)) +* **adapter:** prevent connector dialog from opening on disconnect ([b8209ec](https://github.com/OpenZeppelin/transaction-form-builder/commit/b8209ecaf7558a172cd35e30913f693e98d7d5ff)) +* **core:** build issues ([263d32e](https://github.com/OpenZeppelin/transaction-form-builder/commit/263d32e3303951ee3ecb6f4f4754abd7905e73cb)) +* **core:** build issues ([8f049a5](https://github.com/OpenZeppelin/transaction-form-builder/commit/8f049a5374fa1875b93913528fa947b416cd361d)) +* **core:** dynamic adapter loading via getNetworkById ([6ec72b1](https://github.com/OpenZeppelin/transaction-form-builder/commit/6ec72b1330503545952dc3b070e87e03fa67a1e2)) +* **core:** missing null ([d73760e](https://github.com/OpenZeppelin/transaction-form-builder/commit/d73760e0d8fea8d752ca7c16e3578600d8b0fe4d)) +* **export:** update test snapshots ([b7c2023](https://github.com/OpenZeppelin/transaction-form-builder/commit/b7c20233101aa06ce03c2ab49127b2e4712baf7f)) + + +### Features + +* **adapter:** add component exclusion to UiKitConfiguration and update docs ([5790413](https://github.com/OpenZeppelin/transaction-form-builder/commit/5790413385aa0381af645f7bdd31319bd96169cd)) +* **adapter:** add wagmi UI context provider and facade hooks framework ([53acaf1](https://github.com/OpenZeppelin/transaction-form-builder/commit/53acaf1d37169790500f1cfca0e22b364f398c89)) +* **adapter:** implement custom wallet UI components for EVM adapter ([259569f](https://github.com/OpenZeppelin/transaction-form-builder/commit/259569ff2fe08129d0934b5adfe46e37337a0109)) +* **adapter:** implement Wagmi integration with shared adapter context ([06fe072](https://github.com/OpenZeppelin/transaction-form-builder/commit/06fe072f7436f238b6bb8b2d413a7be646efb1f5)) +* **adapter:** improve wallet components UI for compact spaces ([f363278](https://github.com/OpenZeppelin/transaction-form-builder/commit/f363278be52f3339af8e7c45fb31253533c35e65)) +* **adapter:** support uikit app config ([4902a9e](https://github.com/OpenZeppelin/transaction-form-builder/commit/4902a9e3648d864a0d0c700f8cb1ade581f6618a)) +* **core:** auto select first network in ecosystem ([7747023](https://github.com/OpenZeppelin/transaction-form-builder/commit/7747023e01c1e66f6b53ab081142357cd65659d5)) +* **core:** handle execution config fully ([0a1d66f](https://github.com/OpenZeppelin/transaction-form-builder/commit/0a1d66f75ade018e37f696f4bba9df091bf7f595)) +* **export:** align export system with react-core providers and UI kit config ([e441bbe](https://github.com/OpenZeppelin/transaction-form-builder/commit/e441bbe25d4f0c75357e2938d75164f26bda159d)) +* **utils:** add suport for nested objects in app configs ([ab53bd6](https://github.com/OpenZeppelin/transaction-form-builder/commit/ab53bd650a43ebf3ef02a38096e8e9ebc67b99db)) + + + + + +[Changes][v1.5.0] + + + +# [v1.4.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.4.0) - 2025-05-12 + +# [1.4.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.3.0...v1.4.0) (2025-05-12) + + +### Features + +* **ui:** add OpenZeppelin logo and improve header design ([a12305b](https://github.com/OpenZeppelin/transaction-form-builder/commit/a12305bcdb97636473a771c0cde705522f542376)) + + + + + +[Changes][v1.4.0] + + + +# [v1.3.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.3.0) - 2025-05-11 + +# [1.3.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.2.0...v1.3.0) (2025-05-11) + + +### Bug Fixes + +* **core:** merge branch 'main' into feature/plat-6614-common-configuration-architecture ([a82bf13](https://github.com/OpenZeppelin/transaction-form-builder/commit/a82bf1396e86769009458ec5b5e22a584e7a747c)) + + +### Features + +* **config:** implement runtime app cfg (phases 1 & 2) ([9aaaaa7](https://github.com/OpenZeppelin/transaction-form-builder/commit/9aaaaa75ce1aa8a90501c96676d7fd3a24c3d20a)) +* **config:** implement runtime app configuration ([c52007e](https://github.com/OpenZeppelin/transaction-form-builder/commit/c52007eaf4367dd159bee9f7fa77371f2e3d215b)) + + + + + +[Changes][v1.3.0] + + + +# [v1.2.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.2.0) - 2025-05-09 + +# [1.2.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.1.0...v1.2.0) (2025-05-09) + + +### Bug Fixes + +* **adapter:** ensure transformer includes only standard properties ([d00a1c8](https://github.com/OpenZeppelin/transaction-form-builder/commit/d00a1c8816749d5670cf6db995f087ef1325d117)) +* **adapter:** eslint rules path ([e5424f6](https://github.com/OpenZeppelin/transaction-form-builder/commit/e5424f6386a45e3ccfbee718599c6b63a545367d)) +* **config:** add build step to export-testing workflow ([baee487](https://github.com/OpenZeppelin/transaction-form-builder/commit/baee4876a5b6647c12aa855965d0f7589a6ead68)) +* **config:** add optimizeDeps to shared vitest config ([94c9e09](https://github.com/OpenZeppelin/transaction-form-builder/commit/94c9e091c0a357ff36e63e64fb4e6a537ef9de33)) +* **config:** add ssr.noExternal to shared vitest config ([50f090b](https://github.com/OpenZeppelin/transaction-form-builder/commit/50f090be6436a8fd82b70170610b17e3d374fde4)) +* **config:** ensure build runs before coverage tests ([97b5266](https://github.com/OpenZeppelin/transaction-form-builder/commit/97b5266c2eb1e8de69b9e94a2101605422137857)) +* **config:** lint adapters ([8d78d8b](https://github.com/OpenZeppelin/transaction-form-builder/commit/8d78d8ba2e0d1172d6b07c390dae788f587f188b)) +* **core:** add optimizeDeps and ssr.noExternal to vitest config ([5558df5](https://github.com/OpenZeppelin/transaction-form-builder/commit/5558df5ddd09f3b65c97e70041085578b265092a)) +* **core:** add resolve.dedupe to vitest config ([1c35a73](https://github.com/OpenZeppelin/transaction-form-builder/commit/1c35a73ad863e36b16e766b98e3662167f3a5694)) +* **core:** build issues ([497002f](https://github.com/OpenZeppelin/transaction-form-builder/commit/497002f4ab5b3566435e777ebc42df6da2617389)) +* **core:** contract loader infinite loop if wrong network ([48f5253](https://github.com/OpenZeppelin/transaction-form-builder/commit/48f5253f7f8f3d5496ec973e34db2a2426b0cc29)) +* **core:** make placeholder input full width in field customization ([b05b149](https://github.com/OpenZeppelin/transaction-form-builder/commit/b05b14905d038c41250a2550f09b0d7a12745034)) +* **core:** persist contract data when navigating back in wizard ([1c031b1](https://github.com/OpenZeppelin/transaction-form-builder/commit/1c031b1bb6a4a9761a491acc9800992eaef6feed)) +* **core:** set default EOA execution method on customization step load ([e44d4d4](https://github.com/OpenZeppelin/transaction-form-builder/commit/e44d4d4fa1db40a8d3833f6ff12b1cf79bb504c6)) +* **core:** update snapshot ([182330e](https://github.com/OpenZeppelin/transaction-form-builder/commit/182330e205b3a07c918219bf53917c7fa15f51a2)) +* **export:** exported package issues ([de5071f](https://github.com/OpenZeppelin/transaction-form-builder/commit/de5071fd29ce0638ebfb1723e8af7d02eca23319)) +* **export:** include logger file and missing type exports in adapter export manager ([25c11a1](https://github.com/OpenZeppelin/transaction-form-builder/commit/25c11a1b53afebe13fd3f7f84dbbc2f6102b21d6)) +* **export:** regression issue with adapter configs not being included in the export ([0028372](https://github.com/OpenZeppelin/transaction-form-builder/commit/0028372d537f2740dd1affd5dafb7942de3f5885)) +* **export:** tests ([5bb3c47](https://github.com/OpenZeppelin/transaction-form-builder/commit/5bb3c471b7b45345191ad920c262c073c4708411)) +* **export:** update onSubmit and omit contractSchema ([a830d5a](https://github.com/OpenZeppelin/transaction-form-builder/commit/a830d5a95b08886f54bdbf95bc5e8b166b2ee7e1)) +* **export:** update snapshots ([87fd076](https://github.com/OpenZeppelin/transaction-form-builder/commit/87fd076dea8e69be3baca62083d5216de6208afd)) +* **export:** wallet provider state issues and improved templates ([3764217](https://github.com/OpenZeppelin/transaction-form-builder/commit/3764217bed44add744dea67b0a102448110dd235)) +* **form:** lint issues ([886bcca](https://github.com/OpenZeppelin/transaction-form-builder/commit/886bcca0d7a2c5cce170937e16214d9ce1f5aa66)) +* **form:** prefil default values to avoid controled components error ([59c1fdd](https://github.com/OpenZeppelin/transaction-form-builder/commit/59c1fdd08909dfc1202413be6301851d2384485b)) +* **form:** wallet disconnect state issues ([5cfb706](https://github.com/OpenZeppelin/transaction-form-builder/commit/5cfb70615e8334273550bc25ad8e129b573e5304)) +* **ui:** improve button spacing and visual distinction in ContractStateWidget ([0d5c6ed](https://github.com/OpenZeppelin/transaction-form-builder/commit/0d5c6edc43fb0fe27f4ea1b0a44f977d7ea883fe)) +* **ui:** prevent overflow in contract preview JSON display ([b99e14d](https://github.com/OpenZeppelin/transaction-form-builder/commit/b99e14d0581c4b0b639427d4d6f90a025a41355b)) +* **ui:** reduce padding in ContractStateWidget card for better space efficiency ([b988ef2](https://github.com/OpenZeppelin/transaction-form-builder/commit/b988ef263587f7e932c22f0e4a62fed3875119a7)) +* **ui:** remove error message for contracts with no view functions ([e44884a](https://github.com/OpenZeppelin/transaction-form-builder/commit/e44884a35eb94e4dfe74a37c2516ff241d47b07b)) +* **ui:** resolve ESLint warning by adding missing dependency to useEffect ([d63eb39](https://github.com/OpenZeppelin/transaction-form-builder/commit/d63eb39e6b7eeb719680f0e736351b5172a173d8)) + + +### Features + +* **adapter:** implement new network configurations ([4b37a83](https://github.com/OpenZeppelin/transaction-form-builder/commit/4b37a83838fd58ec0cf068ac0d990d383e49af68)) +* **adapter:** implement proactive network switching on connect and pre-tx ([add7abc](https://github.com/OpenZeppelin/transaction-form-builder/commit/add7abc02a4a9e687a3d39575f4c6398c168f846)) +* **adapter:** implement robust serialization for inputs/outputs for evm ([c072339](https://github.com/OpenZeppelin/transaction-form-builder/commit/c07233921890f734388e348dc0b840df6775b374)) +* **config:** add adapter into commit scope ([a8fdead](https://github.com/OpenZeppelin/transaction-form-builder/commit/a8fdead877dd909c6dd90c1b7dc794efe38b53ad)) +* **core:** change execution method UI to use left rail navigation ([69a7bba](https://github.com/OpenZeppelin/transaction-form-builder/commit/69a7bba55979a35b341fc2bb38a048106e7cda5a)) +* **core:** enhance wizard header and footer styling for better visual distinction ([3e0a41a](https://github.com/OpenZeppelin/transaction-form-builder/commit/3e0a41a98b0a6e6117b66912b787402ecc49cbdf)) +* **core:** font size consistency ([a413db9](https://github.com/OpenZeppelin/transaction-form-builder/commit/a413db956a39470875f1ac136136e50d8fccab4d)) +* **core:** hide contract state widget on the first step ([f8c811f](https://github.com/OpenZeppelin/transaction-form-builder/commit/f8c811f60a40b55da26e4145ce517e679dba58f2)) +* **core:** improve wizard validation with step-based requirements ([bb42afa](https://github.com/OpenZeppelin/transaction-form-builder/commit/bb42afa3439b236241cf97bb8af91094700f1ab4)) +* **core:** pass network config accross adapter and core app ([c2671eb](https://github.com/OpenZeppelin/transaction-form-builder/commit/c2671ebb60f4e6bd8ba42de8ba84bd754627d341)) +* **core:** refactor export step to 'Complete', add loader, and use shared LoadingButton ([d6d22e0](https://github.com/OpenZeppelin/transaction-form-builder/commit/d6d22e0909a6e18b7fd0d50bb3dc0eb78b19c247)) +* **core:** remove contract mocks and moking UI ([d230eb1](https://github.com/OpenZeppelin/transaction-form-builder/commit/d230eb1578d73a62348063997eee17d71deb7665)) +* **core:** remove Field Width option from customization ([6eb6b4a](https://github.com/OpenZeppelin/transaction-form-builder/commit/6eb6b4ac97573559ca26ff31c48f91b15cfc938d)) +* **core:** remove layout customization functionality ([cf33cca](https://github.com/OpenZeppelin/transaction-form-builder/commit/cf33cca3d8f97a58a5a3628df35a058811f45201)) +* **core:** select first field in field customization tab ([0cc2f0f](https://github.com/OpenZeppelin/transaction-form-builder/commit/0cc2f0fef2353b9fbe9b2f7833b67998a50a928f)) +* **core:** wallet connect in the core app ([9e267ce](https://github.com/OpenZeppelin/transaction-form-builder/commit/9e267ce5c385c283f5f55ad8441cae14984ac38a)) +* **core:** wip - implement etherscan abi loading mvp ([e01ff2c](https://github.com/OpenZeppelin/transaction-form-builder/commit/e01ff2cc6d2b6cceb5f43c0e6c689d4115ec42c9)) +* **core:** wip contract state widget ([baa8647](https://github.com/OpenZeppelin/transaction-form-builder/commit/baa864784fab99ff5188519530cfa54f84be5576)) +* **core:** wip ecosystem model ([fb617fb](https://github.com/OpenZeppelin/transaction-form-builder/commit/fb617fb67924fa89f147842e22fd2268f66fce8d)) +* **export:** inject the network config to exported app and handle it ([4998eab](https://github.com/OpenZeppelin/transaction-form-builder/commit/4998eabe7da2cdd02d64ab3bdca494dd4305b508)) +* **form:** add reset button ([57e114f](https://github.com/OpenZeppelin/transaction-form-builder/commit/57e114fa41c19fe5ef53430bf1de8cb3117982ec)) +* **form:** add transaction confirmation waiting step ([7865454](https://github.com/OpenZeppelin/transaction-form-builder/commit/7865454b8d1042e09a1611bddeedf7c60e8c0128)) +* **form:** improve transaction status display ([2fb011a](https://github.com/OpenZeppelin/transaction-form-builder/commit/2fb011a6a8de5f0e4c8c4268b77b369eabce6581)) +* **form:** include the contract state widget in the exported form ([9f86289](https://github.com/OpenZeppelin/transaction-form-builder/commit/9f86289db175baa25936a69f5f5aa1e300ce075b)) +* **form:** show loading indicator in transction execute button ([739e577](https://github.com/OpenZeppelin/transaction-form-builder/commit/739e577b53ccd697df1589ffc4314848556637a8)) +* **form:** show loading indicator in wallet button ([3768883](https://github.com/OpenZeppelin/transaction-form-builder/commit/3768883f1e434d096c53a4e6bd50f2b2befcaba0)) +* **form:** transaction execution ([aad7248](https://github.com/OpenZeppelin/transaction-form-builder/commit/aad7248ba6efef54befb99790370728dce95c51a)) +* **form:** wip add wallet connection and transaction execution ui skeleton ([5fe2f81](https://github.com/OpenZeppelin/transaction-form-builder/commit/5fe2f810baacd9781cfd7d1bf418221b28f512ec)) +* **types:** implement shared types package for core and form-renderer ([cc3a556](https://github.com/OpenZeppelin/transaction-form-builder/commit/cc3a5564b5693f835f748b3a73c2c6f4956c3ac3)) +* **ui:** adjust badge size ([b2fdef6](https://github.com/OpenZeppelin/transaction-form-builder/commit/b2fdef65064bbed300a4585999076a2d27e3536a)) +* **ui:** auto-query simple view functions in ContractStateWidget ([3bd7175](https://github.com/OpenZeppelin/transaction-form-builder/commit/3bd7175728aef1fffb776c425fcb727e98380b1d)) +* **ui:** enhance form preview with visual indicators ([de8de2c](https://github.com/OpenZeppelin/transaction-form-builder/commit/de8de2caf2c8a772ae1b73c92a6c45448df435b4)) +* **ui:** enhance Preview Form button with icons for better UX ([e14d338](https://github.com/OpenZeppelin/transaction-form-builder/commit/e14d33844dde9d02ee5053c40ed4d04d281b09c8)) +* **ui:** hide next button on the last page ([ab185ec](https://github.com/OpenZeppelin/transaction-form-builder/commit/ab185ecba20393d579b366f8d13fa3d00cdd6511)) +* **ui:** improve action bar and contract state UI components ([bb24a7c](https://github.com/OpenZeppelin/transaction-form-builder/commit/bb24a7cc8568d62fcb8e506ae200e5c2b01eb8dc)) +* **ui:** improve action bar with export button and refined styling ([d9b1fb8](https://github.com/OpenZeppelin/transaction-form-builder/commit/d9b1fb814b00c2dc69c59f4e30a7355e3d16b9a7)) +* **ui:** improve ContractStateWidget layout and create StepTitleWithDescription component ([cae1b43](https://github.com/OpenZeppelin/transaction-form-builder/commit/cae1b4326d67032c1679c01b415a88c4fc5662ab)) +* **ui:** make ContractStateWidget dynamically resize to available space ([0b8ffc5](https://github.com/OpenZeppelin/transaction-form-builder/commit/0b8ffc5f6137cea7ea1f5aa7ca73fd0c0415422f)) +* **ui:** make the export button more prominent ([b50839e](https://github.com/OpenZeppelin/transaction-form-builder/commit/b50839e634fa83ea4690b3ecdc78dcbe48cf78d7)) +* **ui:** move contract state widget to the left ([982586e](https://github.com/OpenZeppelin/transaction-form-builder/commit/982586e471a89c895c9fb41e85a90a27892acede)) +* **ui:** remove load contract button ([a62013e](https://github.com/OpenZeppelin/transaction-form-builder/commit/a62013e77c8511b1ef763e1f0d6009c5007ec5cc)) +* **ui:** remove read-only functions display functionality ([a70e83d](https://github.com/OpenZeppelin/transaction-form-builder/commit/a70e83ddb3c44d0cc0c91f03f42120cd6c59f318)) +* **ui:** remove unecessary element ([60cf2d3](https://github.com/OpenZeppelin/transaction-form-builder/commit/60cf2d30faf981f6fc846a7df4337328b2a947d0)) + + + + + +[Changes][v1.2.0] + + + +# [v1.1.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.1.0) - 2025-04-14 + +# [1.1.0](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.0.4...v1.1.0) (2025-04-14) + + +### Bug Fixes + +* **config:** add structuredClone polyfill for ESLint compatibility ([0e144b8](https://github.com/OpenZeppelin/transaction-form-builder/commit/0e144b8032c105af3b28c8fb1ae6cc622a947ec8)) +* **config:** add structuredClone polyfill for ESLint compatibility with Node.js 16 ([08a15a7](https://github.com/OpenZeppelin/transaction-form-builder/commit/08a15a7f15be78c7c8d9fd5532dc0096ba2e7bfb)) +* **config:** align pre-commit hook with fix-all command for consistent import sorting ([763de7d](https://github.com/OpenZeppelin/transaction-form-builder/commit/763de7dfce7c9f70922755fd9469f18b4c03521f)) +* **config:** allow empty commits in pre-commit hook ([b0e634e](https://github.com/OpenZeppelin/transaction-form-builder/commit/b0e634ebd52b9e474e9e060c190820f84dfc2084)) +* **config:** centralized symlinked monorepo config system ([0a6b358](https://github.com/OpenZeppelin/transaction-form-builder/commit/0a6b3588e23c71bbe35a249ac19c8c58a691fc93)) +* **config:** fix TypeScript configuration for monorepo packages ([9344405](https://github.com/OpenZeppelin/transaction-form-builder/commit/93444058957bcf52b26abaf376affcc916a8d416)) +* **config:** import sorting conflicts ([7ef9216](https://github.com/OpenZeppelin/transaction-form-builder/commit/7ef9216e80b730d08e0544bf7498a122d4a8aa5e)) +* **config:** improve pre-push hook Node.js version handling ([e434a0c](https://github.com/OpenZeppelin/transaction-form-builder/commit/e434a0c623020362dfa0875527bc99924185024f)) +* **config:** prevent custom ESLint plugin from loading multiple times ([1e8a161](https://github.com/OpenZeppelin/transaction-form-builder/commit/1e8a161eaee634604e25c412a17ac20b71429985)) +* **config:** prevent custom ESLint plugin from loading multiple times ([d69303e](https://github.com/OpenZeppelin/transaction-form-builder/commit/d69303eef0859cd76e73fc9dfdaff7f9ca66ff58)) +* **config:** resolve ESLint issues across packages ([78a2216](https://github.com/OpenZeppelin/transaction-form-builder/commit/78a2216cc5579ce5782b3268c2dd77a794fa932d)) +* **config:** resolve monorepo build and CLI export issues ([45b3a25](https://github.com/OpenZeppelin/transaction-form-builder/commit/45b3a2578d6e2145f5d6f10508df6e55782e5f1b)) +* **config:** resolve TS build errors and remove [@form-renderer](https://github.com/form-renderer) alias ([fc5642f](https://github.com/OpenZeppelin/transaction-form-builder/commit/fc5642f786a1c7fc635933fa7a2967d82b846def)) +* **config:** resolve unused variable handling across packages ([24c504c](https://github.com/OpenZeppelin/transaction-form-builder/commit/24c504ca21e0d1d802bdcac6e466575b7e35f47f)) +* **config:** set emitDeclarationOnly to true in core package tsconfig ([61308bd](https://github.com/OpenZeppelin/transaction-form-builder/commit/61308bddb76bd1293e610dae5b98e3a26ebfcae9)) +* **config:** simplify lint-staged configuration for consistent import sorting ([688c587](https://github.com/OpenZeppelin/transaction-form-builder/commit/688c587ccfe41732fc863ef776a825434d8b8191)) +* **config:** simplify pre-commit flow to use fix-all command directly ([6e44a24](https://github.com/OpenZeppelin/transaction-form-builder/commit/6e44a244d766ca677a638309e8a99b572e061e60)) +* **config:** simplify pre-commit hook to align with import sorting ([44e0d82](https://github.com/OpenZeppelin/transaction-form-builder/commit/44e0d827375684c626c64a65ba29f1558ad5fada)) +* **config:** unify vite versions to prevent build failures ([7c0dfb9](https://github.com/OpenZeppelin/transaction-form-builder/commit/7c0dfb91b2b9ff1106e661a3a66d8ffc3909d52d)) +* **config:** update build script to use emptyOutDir flag ([ecc6aeb](https://github.com/OpenZeppelin/transaction-form-builder/commit/ecc6aeb42bacdc92c5dc5d318323d5da7fa60feb)) +* **config:** update ESLint config for v9 compatibility ([dfdadfc](https://github.com/OpenZeppelin/transaction-form-builder/commit/dfdadfc9bc6114c9aeb2f08d956066b0e9bf8d7c)) +* **config:** update pre-push hook to use Node.js 20 ([6bffac9](https://github.com/OpenZeppelin/transaction-form-builder/commit/6bffac966124f2d981458a32fa91c8e28a40b586)) +* **config:** update Tailwind CSS v4 import syntax in form-renderer demo ([edf6818](https://github.com/OpenZeppelin/transaction-form-builder/commit/edf6818d4f7d4c884b6251d0b5581dfc5f06c61c)) +* **config:** update TypeScript configuration to support JSON imports and TS extensions ([b01ae52](https://github.com/OpenZeppelin/transaction-form-builder/commit/b01ae522c144aa93d474a5a664e92ab9d7b022bb)) +* **config:** use NVM-managed Node.js in pre-push hook if available ([a3d1dfa](https://github.com/OpenZeppelin/transaction-form-builder/commit/a3d1dfab173efc59f588ad8185a1010653ac05f8)) +* **core:** add basic test and fix package exports order ([c439522](https://github.com/OpenZeppelin/transaction-form-builder/commit/c4395226c1623d64ee92961731432c6eb8b12592)) +* **core:** add tsconfig to export template dir ([9ddef53](https://github.com/OpenZeppelin/transaction-form-builder/commit/9ddef53d00822ce470c1b1db0e9ea60255fcbf54)) +* **core:** build issues ([f999082](https://github.com/OpenZeppelin/transaction-form-builder/commit/f999082577f80b6b2f1ef53537ff6ee7a43895cf)) +* **core:** comprehensive ESLint config fix to handle all file types properly ([46be8ca](https://github.com/OpenZeppelin/transaction-form-builder/commit/46be8ca050907988b913887e4c5625cbad10d4a6)) +* **core:** disable read-only checkbox when hardcoded value invalid ([d923264](https://github.com/OpenZeppelin/transaction-form-builder/commit/d92326430505b59fca2a7f47f36fc660ec49714c)) +* **core:** failing tests ([e566e73](https://github.com/OpenZeppelin/transaction-form-builder/commit/e566e730c4c5ad2cd138e700862b88d8d79ef769)) +* **core:** form render config import ([8118094](https://github.com/OpenZeppelin/transaction-form-builder/commit/811809412b869877faf58cc5cf1e5b51b6e50be8)) +* **core:** format check failure ([7e0f9db](https://github.com/OpenZeppelin/transaction-form-builder/commit/7e0f9dba023629532af2d1dfa2610069f0d57759)) +* **core:** imports ([99f3361](https://github.com/OpenZeppelin/transaction-form-builder/commit/99f33611e9f030d3c9344c306757b3782622f122)) +* **core:** improve adapter export ([a5f1d66](https://github.com/OpenZeppelin/transaction-form-builder/commit/a5f1d6675c0a7f4abe2e7af030ae42b42879a5c2)) +* **core:** improve template comment removal to preserve intended spacing ([8c1d4ca](https://github.com/OpenZeppelin/transaction-form-builder/commit/8c1d4cabceffcbdf1c36175c5495dc60825d60ae)) +* **core:** improve the export location and the CLI ([ac7e026](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac7e026037b5dfc151cfe57066aba65aff534aed)) +* **core:** lint issue ([06cb52a](https://github.com/OpenZeppelin/transaction-form-builder/commit/06cb52a1671678dcbf4cfb896150bcd3886e1e79)) +* **core:** lint issues ([95c606e](https://github.com/OpenZeppelin/transaction-form-builder/commit/95c606e4845a72f9bf02d89e3e80bbf554bae523)) +* **core:** move missing files to internal template dir ([84a6494](https://github.com/OpenZeppelin/transaction-form-builder/commit/84a64943cf5a5cb62361ab2d6935ce388a4db401)) +* **core:** postcss config in template ([ced3bdd](https://github.com/OpenZeppelin/transaction-form-builder/commit/ced3bddd990ce0f3bf4df4de69ba3f99aa4c39f3)) +* **core:** properly map array types to textarea field type in EVMAdapter ([ac135a7](https://github.com/OpenZeppelin/transaction-form-builder/commit/ac135a7a10c43d08fba2061eaf068fb94ece101d)) +* **core:** remove adapter files when includeAdapters is false ([087194b](https://github.com/OpenZeppelin/transaction-form-builder/commit/087194b1826284ae4b6300f168f7805ca21201fc)) +* **core:** remove unused imports ([f1dcf35](https://github.com/OpenZeppelin/transaction-form-builder/commit/f1dcf354770d0690d9e6485d74620c226d6b0b2d)) +* **core:** resolve build errors in execution method step integration ([b3978b8](https://github.com/OpenZeppelin/transaction-form-builder/commit/b3978b83236fd5fff57c2255511935ce1e602175)) +* **core:** resolve build issues with TypeScript compilation ([9dba80c](https://github.com/OpenZeppelin/transaction-form-builder/commit/9dba80cc03aa6e909bf3601d6e04c86a15d930a2)) +* **core:** resolve linter warnings in logger and tests ([8a81bc4](https://github.com/OpenZeppelin/transaction-form-builder/commit/8a81bc4fe4915af0cc28d7ce196aabf49487ef67)) +* **core:** resolve TypeScript errors in FormPreview component ([991da5f](https://github.com/OpenZeppelin/transaction-form-builder/commit/991da5f462a1feda5297b9ddbb385ddb67a7fe29)) +* **core:** resolve TypeScript naming conflicts and lint errors ([cfe554c](https://github.com/OpenZeppelin/transaction-form-builder/commit/cfe554cd5e337824eef9db4df27905531d26255b)) +* **core:** resolve unhandled promise errors in components ([b8e0e66](https://github.com/OpenZeppelin/transaction-form-builder/commit/b8e0e6699f9e251f78187482cb8922b845abd5da)) +* **core:** resolve Vite dynamic import warning in MockContractService ([2da4f9c](https://github.com/OpenZeppelin/transaction-form-builder/commit/2da4f9c3438eddaa32a3e2d3363d900bf896b2f7)) +* **core:** tailwind postcss plugin in template ([36059d2](https://github.com/OpenZeppelin/transaction-form-builder/commit/36059d24b830295d998056adeb1cdcf74355e326)) +* **core:** tests ([40fb79c](https://github.com/OpenZeppelin/transaction-form-builder/commit/40fb79c8a89b3e212e6fc519d091fb31026b8820)) +* **core:** update ESLint config to handle test and story files without project refs ([edcf66e](https://github.com/OpenZeppelin/transaction-form-builder/commit/edcf66e7647c3a0dc676d4754bf8df35e7ecb16c)) +* **core:** update ESLint config to use negated patterns instead of excludedFiles ([bf0a0d2](https://github.com/OpenZeppelin/transaction-form-builder/commit/bf0a0d2bdbe7a39b355dda45bac4e5202eb07828)) +* **core:** virtual module use in tests and extensibility ([403f5b9](https://github.com/OpenZeppelin/transaction-form-builder/commit/403f5b94cda6a9fe9adb35ce9e873183eb2e2a08)) +* **export:** add conditional tailwind source for cli exports ([a4954ef](https://github.com/OpenZeppelin/transaction-form-builder/commit/a4954ef353bb075c1bae18bea9f13d1799e77784)) +* **export:** correct CLI path resolution and dependency versioning ([b5fcac1](https://github.com/OpenZeppelin/transaction-form-builder/commit/b5fcac1c33a8123b42941214a7ccc52e59ca4d54)) +* **export:** correct global.css import path in template ([779a287](https://github.com/OpenZeppelin/transaction-form-builder/commit/779a287aa05869eb92622f7a0a1f27361ce0f3b6)) +* **export:** ensure all styles apply in exported apps & core dev ([96ad1f7](https://github.com/OpenZeppelin/transaction-form-builder/commit/96ad1f7b0e17b721579f67edfb0692441408e27c)) +* **export:** export testing actions ([bade0dc](https://github.com/OpenZeppelin/transaction-form-builder/commit/bade0dc20a5b8168f16b276b61e19c9dacaa7217)) +* **export:** prevent tailwind classes purge ([44b7629](https://github.com/OpenZeppelin/transaction-form-builder/commit/44b7629aae5a3c9725eff2d1817d641749a0dc56)) +* **export:** remove ransaction-form-renderer css import ([f6c7fb4](https://github.com/OpenZeppelin/transaction-form-builder/commit/f6c7fb4a9fe4d22830befd694cb4d437ecb36a2c)) +* **export:** use production flag in ui export ([1b90377](https://github.com/OpenZeppelin/transaction-form-builder/commit/1b90377deedc54d6cfb09001428743fb45ecba9b)) +* **form-renderer:** improve build system and enable CI/CD workflow ([553ddc9](https://github.com/OpenZeppelin/transaction-form-builder/commit/553ddc91f3723f7db4ab2551ee2ad09ed709c33b)) +* **form:** add explicit return types to resolve typescript warnings ([b182979](https://github.com/OpenZeppelin/transaction-form-builder/commit/b182979b250a76cbfec9f3b32939b59bc65049bb)) +* **form:** add proper type annotations in components ([b2b26ee](https://github.com/OpenZeppelin/transaction-form-builder/commit/b2b26ee79194efc642c97452eca43b141b83b298)) +* **form:** add return type to SelectField handleValueChange function ([f9cb35a](https://github.com/OpenZeppelin/transaction-form-builder/commit/f9cb35ad17a14f27d0e561630c57245429c66117)) +* **form:** ensure tsc emits files in build script ([2b23bc5](https://github.com/OpenZeppelin/transaction-form-builder/commit/2b23bc5fb3124cee738d91c2d21890e33e5cad8a)) +* **form:** fix form validation display and field mapping ([e38f494](https://github.com/OpenZeppelin/transaction-form-builder/commit/e38f494d0fa603090b99198a009a22d25d235f2a)) +* **form:** fix import sorting in field components ([fb13924](https://github.com/OpenZeppelin/transaction-form-builder/commit/fb13924c1ac2dcdbe844e7a26582df83e102df8f)) +* **form:** missing dependencie ([4cd06c4](https://github.com/OpenZeppelin/transaction-form-builder/commit/4cd06c4c98338ad78da69171b42ea55a4a3ea511)) +* **form:** resolve linting warnings in button components ([ce49b66](https://github.com/OpenZeppelin/transaction-form-builder/commit/ce49b66a0db8a3ba78d36bd305162711921a7a38)) +* **form:** sort imports ([329e19a](https://github.com/OpenZeppelin/transaction-form-builder/commit/329e19a99a8e91f9fc2275694c6a83dd50d62a45)) +* **form:** unify label spacing ([4ac9292](https://github.com/OpenZeppelin/transaction-form-builder/commit/4ac9292be15952d566cbf81926fdf436f58ee812)) +* **form:** update form field components ([a5d4d0a](https://github.com/OpenZeppelin/transaction-form-builder/commit/a5d4d0ab10511837d6a7e52c23d2b1534cd673e3)) +* resolve linting issues in adapter files and components ([c94764d](https://github.com/OpenZeppelin/transaction-form-builder/commit/c94764d7910f5e83e0b29ae4ac000994d37a77af)) +* **tests:** adjust tests for new logger and formatting ([dd7e968](https://github.com/OpenZeppelin/transaction-form-builder/commit/dd7e968d92158b854b73e618ca8b58fa95eb0580)) +* **ui:** fix chain selection tracking without changing styling ([4e26dd0](https://github.com/OpenZeppelin/transaction-form-builder/commit/4e26dd028070fc1ed0fba8a82f3271da18c75324)) +* **ui:** imports in css should be above all ([823c8ea](https://github.com/OpenZeppelin/transaction-form-builder/commit/823c8ea2d51517fcdf79066db177ae7296042888)) +* **ui:** reduce steps title size to fit ([3889a13](https://github.com/OpenZeppelin/transaction-form-builder/commit/3889a1346ecc241679d73948dc32314d07273a26)) +* **ui:** remove peer utilities for Tailwind v4 compatibility ([accd60a](https://github.com/OpenZeppelin/transaction-form-builder/commit/accd60aa22adf9d3454a06094b235a7123fddfda)) +* **ui:** remove timestamp from generated data-slot styles ([4d6e8be](https://github.com/OpenZeppelin/transaction-form-builder/commit/4d6e8be4ba7eea2a8e33f387d7eb638912ee6822)) +* **ui:** replace deprecated React.ElementRef with HTML element types ([4d51532](https://github.com/OpenZeppelin/transaction-form-builder/commit/4d51532d43a61b25a3df195a2f5c88c2e727e32f)) +* **ui:** restructure monorepo CSS architecture to prevent duplication ([795f933](https://github.com/OpenZeppelin/transaction-form-builder/commit/795f933eeff44c86fd2fe207af8a1c970605b7b8)) +* **ui:** storybook and add select group field ([ee0122d](https://github.com/OpenZeppelin/transaction-form-builder/commit/ee0122da6c1ee9fb027bff42423be0a74d5f7ab5)) +* **ui:** update FieldEditor state on prop change ([95bc7ca](https://github.com/OpenZeppelin/transaction-form-builder/commit/95bc7cacbdb1d6e21ea1aac2f7cf650583c39f9b)) +* **ui:** update StepChainSelect to use useEffect for tracking selections ([504827c](https://github.com/OpenZeppelin/transaction-form-builder/commit/504827c5327d9a6fe27df816439f63be8b5d92a6)) +* **ui:** update styles tsconfig for TypeScript references ([1e05623](https://github.com/OpenZeppelin/transaction-form-builder/commit/1e0562331c2c497a6a9f6efd074018d776b0f67f)) +* **ui:** use data-slot to extract size-3.5 in checkbox ([3c4f8f4](https://github.com/OpenZeppelin/transaction-form-builder/commit/3c4f8f4507b3795290df5103d3c61b6fb7156a8c)) +* **utils:** replace any type with unknown in formUtils ([5a98841](https://github.com/OpenZeppelin/transaction-form-builder/commit/5a988416abf78b163c41dffab54dd8b0be62d8b6)) +* **utils:** replace any type with unknown in transforms.ts ([7b5f342](https://github.com/OpenZeppelin/transaction-form-builder/commit/7b5f342ceb263e0db0d475d935c3cffaddb7ddcf)) + + +### Features + +* **config:** add ESLint config and dev environment for form-renderer package ([232cf61](https://github.com/OpenZeppelin/transaction-form-builder/commit/232cf61075b5c75b5becb273b5ad88677aa976bf)) +* **config:** add export to commit scope ([cf83ddc](https://github.com/OpenZeppelin/transaction-form-builder/commit/cf83ddc2168c577d54e078c21d56d9552cd01356)) +* **config:** add package configuration files for monorepo packages ([844eb0f](https://github.com/OpenZeppelin/transaction-form-builder/commit/844eb0f8114f23a688cac78e3cf7a0939fc2c1ff)) +* **config:** add publish workflow for form-renderer package ([71a6ea6](https://github.com/OpenZeppelin/transaction-form-builder/commit/71a6ea6a8411bd1b0feee4edc15f0de37542e232)) +* **config:** add Tailwind CSS v4 configuration for form-renderer demo ([c730e9e](https://github.com/OpenZeppelin/transaction-form-builder/commit/c730e9e84ad28887f9ace0cac340b1b80ea8dcca)) +* **config:** enforce Node.js 20+ in all Git hooks ([d2091f3](https://github.com/OpenZeppelin/transaction-form-builder/commit/d2091f3378a73b78347b602a4a028c8990466d99)) +* **config:** enforce Node.js 20+ requirement in pre-push hook ([b9c2235](https://github.com/OpenZeppelin/transaction-form-builder/commit/b9c22355ba1330aea3947c3c2a9d73ce323b554b)) +* **config:** initial monorepo package structure setup ([5526e04](https://github.com/OpenZeppelin/transaction-form-builder/commit/5526e042be7a3e3d4fbad70d35806a29fc3e23df)) +* **config:** require Node.js 18.17.0+ and update ESLint to v9 ([d563146](https://github.com/OpenZeppelin/transaction-form-builder/commit/d563146737671272a83f9176c71ffca17f50ad3b)) +* **config:** update Node.js requirement to v20.11.1 ([3eadd96](https://github.com/OpenZeppelin/transaction-form-builder/commit/3eadd968cd1e1a6ea94e82d22bdf9e089d520693)) +* **core:** [wip] add core utils for the automated export testing framework ([27bd8bb](https://github.com/OpenZeppelin/transaction-form-builder/commit/27bd8bbe99fb2667b79f82c36382958d6c4a47a1)) +* **core:** add adapter pattern enforcement with custom ESLint rule ([dc3e5f5](https://github.com/OpenZeppelin/transaction-form-builder/commit/dc3e5f504ab7d644074fa9687a6146de1516a8c8)) +* **core:** add comprehensive export testing framework ([cd6428b](https://github.com/OpenZeppelin/transaction-form-builder/commit/cd6428b7c1ce99fd921db13c97f3f9c317eaff6f)) +* **core:** add configurable logger utility ([a6e46a7](https://github.com/OpenZeppelin/transaction-form-builder/commit/a6e46a772a86ab4f00aa95b984e7250c6865d861)) +* **core:** add editable form title and description fields ([f7bd9c5](https://github.com/OpenZeppelin/transaction-form-builder/commit/f7bd9c5a52aea9577a692d1842ce4a6c68c9880d)) +* **core:** add field exclusion and hardcoding UI controls ([f627689](https://github.com/OpenZeppelin/transaction-form-builder/commit/f62768930c70002c8a63529f1f67a67f74469316)) +* **core:** add form-renderer placeholder components and utilities ([91d2ebc](https://github.com/OpenZeppelin/transaction-form-builder/commit/91d2ebc2e1cfbe247eae5ae6e4a0dc32185043da)) +* **core:** add JSON formatting utility with dedicated tests ([f8d9431](https://github.com/OpenZeppelin/transaction-form-builder/commit/f8d94311e643d71b8254af448be7c9392043fc94)) +* **core:** add modifiesState flag and getWritableFunctions method to contract adapters ([1f537b7](https://github.com/OpenZeppelin/transaction-form-builder/commit/1f537b7b3da01614ee28ebc3edef39618ee4c64c)) +* **core:** add template manager for export ([34f3d2c](https://github.com/OpenZeppelin/transaction-form-builder/commit/34f3d2c682a7d8e82df6db7acff0d3265ff624bb)) +* **core:** add types and adapter interface for execution method step ([a89e738](https://github.com/OpenZeppelin/transaction-form-builder/commit/a89e738f2f1cd4d53b5fd115dc4d5110819b12b7)) +* **core:** complete export integration for execution method step ([8d8754e](https://github.com/OpenZeppelin/transaction-form-builder/commit/8d8754eca3f6154eead60bf3063a0374784999e1)) +* **core:** configure form-renderer package for publishing ([3fcadfd](https://github.com/OpenZeppelin/transaction-form-builder/commit/3fcadfd9741ed9230aac216d0ad5c1a56e7f43eb)) +* **core:** create adapter configuration files ([f2ad9e5](https://github.com/OpenZeppelin/transaction-form-builder/commit/f2ad9e57c6ffb54980980a48138f72b4ab360acf)) +* **core:** define core configuration types ([d45b0e6](https://github.com/OpenZeppelin/transaction-form-builder/commit/d45b0e623f448b345aa26d72c7053e277fdb8a95)) +* **core:** enhance form generation with adapter pattern and complex types support ([57e5d53](https://github.com/OpenZeppelin/transaction-form-builder/commit/57e5d53cd8cd7d27a520cb9c9f5c2c9e2ac071da)) +* **core:** ensure labels are start case ([a80e57a](https://github.com/OpenZeppelin/transaction-form-builder/commit/a80e57a1a576f23d8c48f39b478a90cf20c273aa)) +* **core:** export framework testing Integration and cli for manual testing ([c455e29](https://github.com/OpenZeppelin/transaction-form-builder/commit/c455e29088db4c91ad39a002eb51d328907c9e02)) +* **core:** focus on EVM adapter and fix form field editor typing ([58404fa](https://github.com/OpenZeppelin/transaction-form-builder/commit/58404fadeb61e41246d85f0a3ce054bfe248ccf6)) +* **core:** generate kebab case name ([f428c08](https://github.com/OpenZeppelin/transaction-form-builder/commit/f428c08632a605b67c58a4f52c56075b042caf4e)) +* **core:** implement adapter export system ([6de5cd2](https://github.com/OpenZeppelin/transaction-form-builder/commit/6de5cd22f3210f5442959045533042afbce5bb66)) +* **core:** implement form code generator and export functionality ([0517c27](https://github.com/OpenZeppelin/transaction-form-builder/commit/0517c276d8ae7094477348572325a66153633480)) +* **core:** implement package manager class ([ce77bdb](https://github.com/OpenZeppelin/transaction-form-builder/commit/ce77bdb4f4890b351e72afa5f67087c0ac1631fb)) +* **core:** implement StepExecutionMethod UI component ([523ae1a](https://github.com/OpenZeppelin/transaction-form-builder/commit/523ae1aed2eba23f5c863144709fe741dfaf0faf)) +* **core:** implement ZIP generation utility for form exports ([9fb4357](https://github.com/OpenZeppelin/transaction-form-builder/commit/9fb435762d5d1b3167820332b1e81ba9fbd218c4)) +* **core:** improve export system with environment modes and path constraints ([07a54a6](https://github.com/OpenZeppelin/transaction-form-builder/commit/07a54a6f9de25a2cb7360d07ff48e5fe3e2d7448)) +* **core:** improve form-renderer package build system ([8c42a01](https://github.com/OpenZeppelin/transaction-form-builder/commit/8c42a01cfa4f6e179a4bd64cb2d8546afce19a06)) +* **core:** integrate execution method step and validation logic ([23caacb](https://github.com/OpenZeppelin/transaction-form-builder/commit/23caacb06e7f66f327cef5ea15af77f91f45e574)) +* **core:** migrate core application files to monorepo structure ([776bee5](https://github.com/OpenZeppelin/transaction-form-builder/commit/776bee5a384dd1ad82271066a48eb3d8ba081e98)) +* **core:** place contract mock files into template directory temporarily ([64f2b01](https://github.com/OpenZeppelin/transaction-form-builder/commit/64f2b01297e11e5566f1e19e1f10af0b906845e1)) +* **core:** prepare formatTransactionData for hardcoded value logic ([42bed1d](https://github.com/OpenZeppelin/transaction-form-builder/commit/42bed1df53aa11eebc75d84d846ac338256c7b4e)) +* **core:** separate generateId into general utils and include in the export ([a88faa5](https://github.com/OpenZeppelin/transaction-form-builder/commit/a88faa584c47e19b61ee72b293453056ddb7d732)) +* **core:** template manager to use import.meta.glob ([4cc3073](https://github.com/OpenZeppelin/transaction-form-builder/commit/4cc3073d8568b9cde24c530c8e3df7835f9bb42a)) +* **core:** template processing plugin for vite ([422064a](https://github.com/OpenZeppelin/transaction-form-builder/commit/422064a7155a8accf5de0873d96bdffb0d4783e3)) +* **core:** template-based generation implementation ([4b96570](https://github.com/OpenZeppelin/transaction-form-builder/commit/4b965702357b17dc2ec6addb6e56a4b50f33b6b1)) +* **core:** update contract adapters to use MockContractService ([0fdd8b6](https://github.com/OpenZeppelin/transaction-form-builder/commit/0fdd8b6c22bee58fbb23cd341decd2cdc0f78343)) +* **core:** use builderConfigToRenderSchema in code gen ([d2af408](https://github.com/OpenZeppelin/transaction-form-builder/commit/d2af40896ef168f283f5cae948d11b9e26c19c32)) +* **deps:** add JSZip for form export functionality ([ff4274b](https://github.com/OpenZeppelin/transaction-form-builder/commit/ff4274b499186db93d25666963c673716ee70bed)) +* **eslint:** add unused-imports plugin to auto-remove unused imports ([3546074](https://github.com/OpenZeppelin/transaction-form-builder/commit/3546074e7dbaee2022376c3a056c52b54a0e3465)) +* **export:** add executionConfig to test config ([1484af9](https://github.com/OpenZeppelin/transaction-form-builder/commit/1484af9ac13d2a302c2c559233b7fbec26b82784)) +* **export:** implement style export manager and update docs ([f15d9c5](https://github.com/OpenZeppelin/transaction-form-builder/commit/f15d9c53de2d8611b1667a897c180a515f4a4dd1)) +* **export:** integrate automatic JSON formatting in export system ([9ecdbfb](https://github.com/OpenZeppelin/transaction-form-builder/commit/9ecdbfbeafb445749679bccd7325f3647f23a1fb)) +* **export:** integrate logger utility into export system ([dd65d81](https://github.com/OpenZeppelin/transaction-form-builder/commit/dd65d815b76a8e51b696067363084c22cdb02f4b)) +* **export:** support hidden, hardcoded, and readonly fields in export ([c922998](https://github.com/OpenZeppelin/transaction-form-builder/commit/c922998ac555a92d236cd40923dcc4764a32884e)) +* **form:** add enhanced validation handling to all field components ([4cf5d5a](https://github.com/OpenZeppelin/transaction-form-builder/commit/4cf5d5a6768bf4f7e8734b5892e586d4369492d6)) +* **form:** add MockContractSelector component for contract selection ([639e97b](https://github.com/OpenZeppelin/transaction-form-builder/commit/639e97b7a23b952409a6b6dd4e18a3be51cb6399)) +* **form:** add SelectField component for form rendering ([4602485](https://github.com/OpenZeppelin/transaction-form-builder/commit/4602485f58e0862f71342dbfe476ec201e97f8ea)) +* **form:** create common error handling and validation display ([55248c9](https://github.com/OpenZeppelin/transaction-form-builder/commit/55248c90807380ca6494770a2dacfff8c16e6413)) +* **form:** create form-renderer configuration file ([b12b4e6](https://github.com/OpenZeppelin/transaction-form-builder/commit/b12b4e652df5509c1ad02107982a450947d69ec5)) +* **form:** enhance field components with improved accessibility ([e28bb2d](https://github.com/OpenZeppelin/transaction-form-builder/commit/e28bb2de9b66336f8536789d8c16135358c05337)) +* **form:** enhance field type selector and fix real-time update bug ([6459d84](https://github.com/OpenZeppelin/transaction-form-builder/commit/6459d84a3264d911080ffc6c026c44bd1d053bb2)) +* **form:** enhance field type selector with blockchain type compatibility ([3f9e2d6](https://github.com/OpenZeppelin/transaction-form-builder/commit/3f9e2d6ffa9f98e779f1b01ad0ca74915d2323fa)) +* **form:** enhance field type selector with grouped options and compatibility indicators ([e3b0e31](https://github.com/OpenZeppelin/transaction-form-builder/commit/e3b0e3163c7df651cc72374eade53c6a3990343e)) +* **form:** enhance FormField with generic type parameters ([101593b](https://github.com/OpenZeppelin/transaction-form-builder/commit/101593b58e4d52baeb0d6b45e2e210e0d32518ae)) +* **form:** implement boolean field component ([377f6a0](https://github.com/OpenZeppelin/transaction-form-builder/commit/377f6a00fc7ce0d93db2bf3b0c6fe1a203932448)) +* **form:** implement data transform system ([b8f99da](https://github.com/OpenZeppelin/transaction-form-builder/commit/b8f99dac300cd1187f746558a7531a4faa739b66)) +* **form:** implement FormSchemaFactory with transforms ([383c9eb](https://github.com/OpenZeppelin/transaction-form-builder/commit/383c9eb1c45089b8004802f064b4337f46725548)) +* **form:** implement generic type parameters in all adapters ([7bc7fc1](https://github.com/OpenZeppelin/transaction-form-builder/commit/7bc7fc12664c464c42db6c080e540cf9bf2f756d)) +* **form:** implement NumberField and AddressField components with React Hook Form integration ([dc5df2f](https://github.com/OpenZeppelin/transaction-form-builder/commit/dc5df2f694801f7fe640e34af803095fd13a78ad)) +* **form:** implement preview logic for hidden, hardcoded, and read-only fields ([2554b34](https://github.com/OpenZeppelin/transaction-form-builder/commit/2554b34637cf32d31844c8309aa09af59829f760)) +* **form:** integrate form-renderer in FormPreview component ([307d628](https://github.com/OpenZeppelin/transaction-form-builder/commit/307d628272df70ce99adb97e443a80a7dc1b0e9b)) +* **form:** integrate LoadingButton and MockContractSelector into StepContractDefinition ([5504516](https://github.com/OpenZeppelin/transaction-form-builder/commit/5504516bd1671ffa6608f2b83c62ee433a974f38)) +* **form:** move button-variants.ts from core to form-renderer ([15c8a2b](https://github.com/OpenZeppelin/transaction-form-builder/commit/15c8a2b665d81e58b3113c535790fdb394feb324)) +* **form:** update ContractAdapter interface with generics ([78c9660](https://github.com/OpenZeppelin/transaction-form-builder/commit/78c9660a75f9cd8c00fad7db842d80631cabde38)) +* **form:** update form builder components to use enhanced types ([ad6f6c4](https://github.com/OpenZeppelin/transaction-form-builder/commit/ad6f6c4a624dd97decbe7454d94f2fc493410603)) +* **form:** update SelectField ([33a118d](https://github.com/OpenZeppelin/transaction-form-builder/commit/33a118df7bba2a3563e83f8dcddbffd789730563)) +* **form:** use CommonFormProperties in components and services ([c9ce271](https://github.com/OpenZeppelin/transaction-form-builder/commit/c9ce2715cac068f13bba7bb147cc57dd399def00)) +* **ui:** add AmountField story ([81b5bca](https://github.com/OpenZeppelin/transaction-form-builder/commit/81b5bca0c4e9ac57bc4ddda073d115701a69f6cf)) +* **ui:** add blockchain address input field component with validation ([ca67c5f](https://github.com/OpenZeppelin/transaction-form-builder/commit/ca67c5fee2944be32fda6fb136fe350612999ca9)) +* **ui:** add Button and LoadingButton component stories ([ecec1c3](https://github.com/OpenZeppelin/transaction-form-builder/commit/ecec1c3f1a6212e6faca88a23625e6343965d038)) +* **ui:** add data-slot attributes for consistent styling ([646bb97](https://github.com/OpenZeppelin/transaction-form-builder/commit/646bb978a0b06b2f5ead07afbb39c27238ea069a)) +* **ui:** add Dialog component with storybook documentation ([6afb472](https://github.com/OpenZeppelin/transaction-form-builder/commit/6afb4721ffcaabf8c148bf30f0c2739b90595356)) +* **ui:** add LoadingButton component with storybook documentation ([6d09188](https://github.com/OpenZeppelin/transaction-form-builder/commit/6d091881187e4a6d410c9e535de246765579e5b6)) +* **ui:** add RadioGroup component using Radix UI ([52e3268](https://github.com/OpenZeppelin/transaction-form-builder/commit/52e32688856cc2b942f7823eb957921d7fc1e368)) +* **ui:** add Storybook component stories and fix styling issues ([dc1046d](https://github.com/OpenZeppelin/transaction-form-builder/commit/dc1046df73d25c076a808c5d5eac3c4eb29a558f)) +* **ui:** add styles package to monorepo checks ([70a7af2](https://github.com/OpenZeppelin/transaction-form-builder/commit/70a7af207d0f3efedb55412595ee7e17d76efe51)) +* **ui:** add template structure for exported applications ([c984423](https://github.com/OpenZeppelin/transaction-form-builder/commit/c984423b92b0eb719c649469d895eddb2ff501ee)) +* **ui:** enhance blockchain selector with tile-based UI ([7f832f2](https://github.com/OpenZeppelin/transaction-form-builder/commit/7f832f21a9e607d4a4e5cadb9294a3a2856440ed)) +* **ui:** enhance form-renderer demo App with Tailwind CSS styling ([40e8545](https://github.com/OpenZeppelin/transaction-form-builder/commit/40e8545a763b3e1c14c85eb6c07576e215fff8e2)) +* **ui:** enhance Textarea component with accessibility features ([145b109](https://github.com/OpenZeppelin/transaction-form-builder/commit/145b109395efba2cd3a842d530c761ab8da30a2c)) +* **ui:** improve field type transformation display with tooltips ([079fe52](https://github.com/OpenZeppelin/transaction-form-builder/commit/079fe526f3a100008dd9335a6220b5ad71eb3411)) +* **ui:** reorganize mock contracts into chain-specific directories ([9753355](https://github.com/OpenZeppelin/transaction-form-builder/commit/975335565a18c5024e40b08aee27d42c672da7ae)) +* **ui:** update to latest Tailwind v4 and shadcn/ui patterns ([7fdd328](https://github.com/OpenZeppelin/transaction-form-builder/commit/7fdd328be59bf827bfeda4fda17f3acbcac0ed6c)) +* **ui:** update ui components and TypeScript config ([d71cc25](https://github.com/OpenZeppelin/transaction-form-builder/commit/d71cc25bea9633a4fcd0e88a121ec485e7ca54bb)) + + +### Performance Improvements + +* **core:** lazy load adapters ([5cba3d3](https://github.com/OpenZeppelin/transaction-form-builder/commit/5cba3d3d73876369e4940e811f6dce2c2d9a0881)) +* **core:** lazy load templates ([38d6234](https://github.com/OpenZeppelin/transaction-form-builder/commit/38d623480f278355ba134265491997af345834ea)) + + + + + +[Changes][v1.1.0] + + + +# [v1.0.4](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.0.4) - 2025-03-12 + +## [1.0.4](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.0.3...v1.0.4) (2025-03-12) + + +### Bug Fixes + +* **ui:** tailwind integration ([13ef57d](https://github.com/OpenZeppelin/transaction-form-builder/commit/13ef57d1e1535ef4774bf7a3bf73412485f2f594)) + + + + + +[Changes][v1.0.4] + + + +# [v1.0.3](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.0.3) - 2025-03-07 + +## [1.0.3](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.0.2...v1.0.3) (2025-03-07) + + +### Bug Fixes + +* update theme utility classes for Tailwind CSS v4 compatibility ([c5ef108](https://github.com/OpenZeppelin/transaction-form-builder/commit/c5ef108fbd81c146e14289fb2955fa8fc1cbf544)) + + + + + +[Changes][v1.0.3] + + + +# [v1.0.2](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.0.2) - 2025-03-07 + +## [1.0.2](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.0.1...v1.0.2) (2025-03-07) + + +### Bug Fixes + +* update Tailwind CSS v4 configuration for ESM compatibility ([b5fd682](https://github.com/OpenZeppelin/transaction-form-builder/commit/b5fd6820098124cebb870d04d1270b8f1ecc2e91)) + + + + + +[Changes][v1.0.2] + + + +# [v1.0.1](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.0.1) - 2025-03-07 + +## [1.0.1](https://github.com/OpenZeppelin/transaction-form-builder/compare/v1.0.0...v1.0.1) (2025-03-07) + + +### Bug Fixes + +* rename postcss.config.js to .cjs to fix ESM compatibility ([78dada4](https://github.com/OpenZeppelin/transaction-form-builder/commit/78dada4f1eb5c7d3afe6b4d24d5a08228142a313)) + + + + + +[Changes][v1.0.1] + + + +# [v1.0.0](https://github.com/OpenZeppelin/ui-builder/releases/tag/v1.0.0) - 2025-03-07 + +# 1.0.0 (2025-03-07) + + +### Bug Fixes + +* **config:** migrate to ESLint 9.x CommonJS configuration ([b6ef58d](https://github.com/OpenZeppelin/transaction-form-builder/commit/b6ef58da42aa41f160361b052c12b8096d81db3a)) +* **config:** remove ESM version of ESLint config ([05f9aad](https://github.com/OpenZeppelin/transaction-form-builder/commit/05f9aad2e811ce823710bd2e2e681f2e5afef16b)) +* **config:** resolve ESLint TypeScript import issues ([118e91e](https://github.com/OpenZeppelin/transaction-form-builder/commit/118e91eaed5b4ea133e5ff00cca7809fbd7e2d55)) + + +### Features + +* **core:** initial commit ([30f1f8b](https://github.com/OpenZeppelin/transaction-form-builder/commit/30f1f8b983f4d5696742c7789f9cb7333a82b180)) + + + + + +[Changes][v1.0.0] + + +[@openzeppelin/ui-builder-utils@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-utils@0.10.0...@openzeppelin/ui-builder-utils@0.10.1 +[@openzeppelin/ui-builder-utils@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-ui@0.10.1...@openzeppelin/ui-builder-utils@0.10.0 +[@openzeppelin/ui-builder-ui@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-ui@0.10.0...@openzeppelin/ui-builder-ui@0.10.1 +[@openzeppelin/ui-builder-ui@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-types@0.10.1...@openzeppelin/ui-builder-ui@0.10.0 +[@openzeppelin/ui-builder-types@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-types@0.10.0...@openzeppelin/ui-builder-types@0.10.1 +[@openzeppelin/ui-builder-types@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-styles@0.10.0...@openzeppelin/ui-builder-types@0.10.0 +[@openzeppelin/ui-builder-styles@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-storage@0.10.0...@openzeppelin/ui-builder-styles@0.10.0 +[@openzeppelin/ui-builder-storage@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-renderer@0.10.1...@openzeppelin/ui-builder-storage@0.10.0 +[@openzeppelin/ui-builder-renderer@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-renderer@0.10.0...@openzeppelin/ui-builder-renderer@0.10.1 +[@openzeppelin/ui-builder-renderer@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-react-core@0.10.0...@openzeppelin/ui-builder-renderer@0.10.0 +[@openzeppelin/ui-builder-react-core@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-stellar@0.10.1...@openzeppelin/ui-builder-react-core@0.10.0 +[@openzeppelin/ui-builder-adapter-stellar@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-stellar@0.10.0...@openzeppelin/ui-builder-adapter-stellar@0.10.1 +[@openzeppelin/ui-builder-adapter-stellar@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-solana@0.10.1...@openzeppelin/ui-builder-adapter-stellar@0.10.0 +[@openzeppelin/ui-builder-adapter-solana@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-solana@0.10.0...@openzeppelin/ui-builder-adapter-solana@0.10.1 +[@openzeppelin/ui-builder-adapter-solana@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-midnight@0.10.1...@openzeppelin/ui-builder-adapter-solana@0.10.0 +[@openzeppelin/ui-builder-adapter-midnight@0.10.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-midnight@0.10.0...@openzeppelin/ui-builder-adapter-midnight@0.10.1 +[@openzeppelin/ui-builder-adapter-midnight@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/ui-builder-adapter-evm@0.10.0...@openzeppelin/ui-builder-adapter-midnight@0.10.0 +[@openzeppelin/ui-builder-adapter-evm@0.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.9.0...@openzeppelin/ui-builder-adapter-evm@0.10.0 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.9.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-utils@0.8.0...@openzeppelin/contracts-ui-builder-adapter-evm@0.9.0 +[@openzeppelin/contracts-ui-builder-utils@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.8.0...@openzeppelin/contracts-ui-builder-utils@0.8.0 +[@openzeppelin/contracts-ui-builder-ui@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-types@0.8.0...@openzeppelin/contracts-ui-builder-ui@0.8.0 +[@openzeppelin/contracts-ui-builder-types@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-storage@0.8.0...@openzeppelin/contracts-ui-builder-types@0.8.0 +[@openzeppelin/contracts-ui-builder-storage@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.8.0...@openzeppelin/contracts-ui-builder-storage@0.8.0 +[@openzeppelin/contracts-ui-builder-renderer@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-react-core@0.8.0...@openzeppelin/contracts-ui-builder-renderer@0.8.0 +[@openzeppelin/contracts-ui-builder-react-core@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-stellar@0.8.0...@openzeppelin/contracts-ui-builder-react-core@0.8.0 +[@openzeppelin/contracts-ui-builder-adapter-stellar@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-solana@0.8.0...@openzeppelin/contracts-ui-builder-adapter-stellar@0.8.0 +[@openzeppelin/contracts-ui-builder-adapter-solana@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.8.0...@openzeppelin/contracts-ui-builder-adapter-solana@0.8.0 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.8.0...@openzeppelin/contracts-ui-builder-adapter-midnight@0.8.0 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.7.2...@openzeppelin/contracts-ui-builder-adapter-evm@0.8.0 +[@openzeppelin/contracts-ui-builder-ui@0.7.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.7.2...@openzeppelin/contracts-ui-builder-ui@0.7.2 +[@openzeppelin/contracts-ui-builder-renderer@0.7.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.2...@openzeppelin/contracts-ui-builder-renderer@0.7.2 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.7.1...@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.2 +[@openzeppelin/contracts-ui-builder-ui@0.7.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.7.1...@openzeppelin/contracts-ui-builder-ui@0.7.1 +[@openzeppelin/contracts-ui-builder-renderer@0.7.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.1...@openzeppelin/contracts-ui-builder-renderer@0.7.1 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.7.1...@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.1 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.7.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-utils@0.7.0...@openzeppelin/contracts-ui-builder-adapter-evm@0.7.1 +[@openzeppelin/contracts-ui-builder-utils@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.7.0...@openzeppelin/contracts-ui-builder-utils@0.7.0 +[@openzeppelin/contracts-ui-builder-ui@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-storage@0.7.0...@openzeppelin/contracts-ui-builder-ui@0.7.0 +[@openzeppelin/contracts-ui-builder-storage@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.7.0...@openzeppelin/contracts-ui-builder-storage@0.7.0 +[@openzeppelin/contracts-ui-builder-renderer@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-react-core@0.7.0...@openzeppelin/contracts-ui-builder-renderer@0.7.0 +[@openzeppelin/contracts-ui-builder-react-core@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-stellar@0.7.0...@openzeppelin/contracts-ui-builder-react-core@0.7.0 +[@openzeppelin/contracts-ui-builder-adapter-stellar@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-solana@0.7.0...@openzeppelin/contracts-ui-builder-adapter-stellar@0.7.0 +[@openzeppelin/contracts-ui-builder-adapter-solana@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.0...@openzeppelin/contracts-ui-builder-adapter-solana@0.7.0 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.7.0...@openzeppelin/contracts-ui-builder-adapter-midnight@0.7.0 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.6.0...@openzeppelin/contracts-ui-builder-adapter-evm@0.7.0 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.6.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-utils@0.4.1...@openzeppelin/contracts-ui-builder-adapter-evm@0.6.0 +[@openzeppelin/contracts-ui-builder-utils@0.4.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.5.1...@openzeppelin/contracts-ui-builder-utils@0.4.1 +[@openzeppelin/contracts-ui-builder-ui@0.5.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-types@0.4.0...@openzeppelin/contracts-ui-builder-ui@0.5.1 +[@openzeppelin/contracts-ui-builder-types@0.4.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-storage@0.3.4...@openzeppelin/contracts-ui-builder-types@0.4.0 +[@openzeppelin/contracts-ui-builder-storage@0.3.4]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.4.0...@openzeppelin/contracts-ui-builder-storage@0.3.4 +[@openzeppelin/contracts-ui-builder-renderer@0.4.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-react-core@0.2.5...@openzeppelin/contracts-ui-builder-renderer@0.4.0 +[@openzeppelin/contracts-ui-builder-react-core@0.2.5]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.9...@openzeppelin/contracts-ui-builder-react-core@0.2.5 +[@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.9]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-solana@0.0.9...@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.9 +[@openzeppelin/contracts-ui-builder-adapter-solana@0.0.9]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.1.4...@openzeppelin/contracts-ui-builder-adapter-solana@0.0.9 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.1.4]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.5.0...@openzeppelin/contracts-ui-builder-adapter-midnight@0.1.4 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.5.0]: https://github.com/OpenZeppelin/ui-builder/compare/deploy-2025-08-12-1835...@openzeppelin/contracts-ui-builder-adapter-evm@0.5.0 +[deploy-2025-08-12-1835]: https://github.com/OpenZeppelin/ui-builder/compare/deploy-2025-08-11-1522...deploy-2025-08-12-1835 +[deploy-2025-08-11-1522]: https://github.com/OpenZeppelin/ui-builder/compare/deploy-2025-08-11-1352...deploy-2025-08-11-1522 +[deploy-2025-08-11-1352]: https://github.com/OpenZeppelin/ui-builder/compare/deploy-2025-08-08-1508...deploy-2025-08-11-1352 +[deploy-2025-08-08-1508]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-storage@0.3.3...deploy-2025-08-08-1508 +[@openzeppelin/contracts-ui-builder-storage@0.3.3]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.2.0...@openzeppelin/contracts-ui-builder-storage@0.3.3 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.2.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.1.3...@openzeppelin/contracts-ui-builder-adapter-evm@0.2.0 +[@openzeppelin/contracts-ui-builder-ui@0.1.3]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-styles@0.1.2...@openzeppelin/contracts-ui-builder-ui@0.1.3 +[@openzeppelin/contracts-ui-builder-styles@0.1.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.1.3...@openzeppelin/contracts-ui-builder-styles@0.1.2 +[@openzeppelin/contracts-ui-builder-renderer@0.1.3]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.3...@openzeppelin/contracts-ui-builder-renderer@0.1.3 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.3]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.1.3...@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.3 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.1.3]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-utils@0.1.1...@openzeppelin/contracts-ui-builder-adapter-evm@0.1.3 +[@openzeppelin/contracts-ui-builder-utils@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.1.2...@openzeppelin/contracts-ui-builder-utils@0.1.1 +[@openzeppelin/contracts-ui-builder-ui@0.1.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-ui@0.1.1...@openzeppelin/contracts-ui-builder-ui@0.1.2 +[@openzeppelin/contracts-ui-builder-ui@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-types@0.1.2...@openzeppelin/contracts-ui-builder-ui@0.1.1 +[@openzeppelin/contracts-ui-builder-types@0.1.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-types@0.1.1...@openzeppelin/contracts-ui-builder-types@0.1.2 +[@openzeppelin/contracts-ui-builder-types@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-styles@0.1.1...@openzeppelin/contracts-ui-builder-types@0.1.1 +[@openzeppelin/contracts-ui-builder-styles@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.1.2...@openzeppelin/contracts-ui-builder-styles@0.1.1 +[@openzeppelin/contracts-ui-builder-renderer@0.1.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-renderer@0.1.1...@openzeppelin/contracts-ui-builder-renderer@0.1.2 +[@openzeppelin/contracts-ui-builder-renderer@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-react-core@0.1.2...@openzeppelin/contracts-ui-builder-renderer@0.1.1 +[@openzeppelin/contracts-ui-builder-react-core@0.1.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-react-core@0.1.1...@openzeppelin/contracts-ui-builder-react-core@0.1.2 +[@openzeppelin/contracts-ui-builder-react-core@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.2...@openzeppelin/contracts-ui-builder-react-core@0.1.1 +[@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.1...@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.2 +[@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-solana@0.0.2...@openzeppelin/contracts-ui-builder-adapter-stellar@0.0.1 +[@openzeppelin/contracts-ui-builder-adapter-solana@0.0.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-solana@0.0.1...@openzeppelin/contracts-ui-builder-adapter-solana@0.0.2 +[@openzeppelin/contracts-ui-builder-adapter-solana@0.0.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.2...@openzeppelin/contracts-ui-builder-adapter-solana@0.0.1 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.1...@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.2 +[@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.1.2...@openzeppelin/contracts-ui-builder-adapter-midnight@0.0.1 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.1.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/contracts-ui-builder-adapter-evm@0.1.1...@openzeppelin/contracts-ui-builder-adapter-evm@0.1.2 +[@openzeppelin/contracts-ui-builder-adapter-evm@0.1.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-evm@1.18.0...@openzeppelin/contracts-ui-builder-adapter-evm@0.1.1 +[@openzeppelin/transaction-form-adapter-evm@1.18.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-ui@1.18.0...@openzeppelin/transaction-form-adapter-evm@1.18.0 +[@openzeppelin/transaction-form-ui@1.18.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-renderer@1.17.1...@openzeppelin/transaction-form-ui@1.18.0 +[@openzeppelin/transaction-form-renderer@1.17.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-react-core@1.17.0...@openzeppelin/transaction-form-renderer@1.17.1 +[@openzeppelin/transaction-form-react-core@1.17.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-midnight@0.3.1...@openzeppelin/transaction-form-react-core@1.17.0 +[@openzeppelin/transaction-form-adapter-midnight@0.3.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-evm@1.17.1...@openzeppelin/transaction-form-adapter-midnight@0.3.1 +[@openzeppelin/transaction-form-adapter-evm@1.17.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-utils@1.17.0...@openzeppelin/transaction-form-adapter-evm@1.17.1 +[@openzeppelin/transaction-form-utils@1.17.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-ui@1.17.0...@openzeppelin/transaction-form-utils@1.17.0 +[@openzeppelin/transaction-form-ui@1.17.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-types@1.17.0...@openzeppelin/transaction-form-ui@1.17.0 +[@openzeppelin/transaction-form-types@1.17.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-renderer@1.17.0...@openzeppelin/transaction-form-types@1.17.0 +[@openzeppelin/transaction-form-renderer@1.17.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-stellar@0.3.0...@openzeppelin/transaction-form-renderer@1.17.0 +[@openzeppelin/transaction-form-adapter-stellar@0.3.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-solana@0.3.0...@openzeppelin/transaction-form-adapter-stellar@0.3.0 +[@openzeppelin/transaction-form-adapter-solana@0.3.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-midnight@0.3.0...@openzeppelin/transaction-form-adapter-solana@0.3.0 +[@openzeppelin/transaction-form-adapter-midnight@0.3.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-evm@1.17.0...@openzeppelin/transaction-form-adapter-midnight@0.3.0 +[@openzeppelin/transaction-form-adapter-evm@1.17.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-utils@1.16.0...@openzeppelin/transaction-form-adapter-evm@1.17.0 +[@openzeppelin/transaction-form-utils@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-ui@1.16.0...@openzeppelin/transaction-form-utils@1.16.0 +[@openzeppelin/transaction-form-ui@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-types@1.16.0...@openzeppelin/transaction-form-ui@1.16.0 +[@openzeppelin/transaction-form-types@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-styles@1.16.0...@openzeppelin/transaction-form-types@1.16.0 +[@openzeppelin/transaction-form-styles@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-renderer@1.16.0...@openzeppelin/transaction-form-styles@1.16.0 +[@openzeppelin/transaction-form-renderer@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-react-core@1.16.0...@openzeppelin/transaction-form-renderer@1.16.0 +[@openzeppelin/transaction-form-react-core@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-stellar@0.2.1...@openzeppelin/transaction-form-react-core@1.16.0 +[@openzeppelin/transaction-form-adapter-stellar@0.2.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-solana@0.2.1...@openzeppelin/transaction-form-adapter-stellar@0.2.1 +[@openzeppelin/transaction-form-adapter-solana@0.2.1]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-midnight@0.2.2...@openzeppelin/transaction-form-adapter-solana@0.2.1 +[@openzeppelin/transaction-form-adapter-midnight@0.2.2]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-adapter-evm@1.16.0...@openzeppelin/transaction-form-adapter-midnight@0.2.2 +[@openzeppelin/transaction-form-adapter-evm@1.16.0]: https://github.com/OpenZeppelin/ui-builder/compare/@openzeppelin/transaction-form-renderer@1.15.1...@openzeppelin/transaction-form-adapter-evm@1.16.0 +[@openzeppelin/transaction-form-renderer@1.15.1]: https://github.com/OpenZeppelin/ui-builder/compare/v1.15.0...@openzeppelin/transaction-form-renderer@1.15.1 +[v1.15.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.14.3...v1.15.0 +[v1.14.3]: https://github.com/OpenZeppelin/ui-builder/compare/v1.14.2...v1.14.3 +[v1.14.2]: https://github.com/OpenZeppelin/ui-builder/compare/v1.14.1...v1.14.2 +[v1.14.1]: https://github.com/OpenZeppelin/ui-builder/compare/v1.14.0...v1.14.1 +[v1.14.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.13.2...v1.14.0 +[v1.13.2]: https://github.com/OpenZeppelin/ui-builder/compare/v1.13.1...v1.13.2 +[v1.13.1]: https://github.com/OpenZeppelin/ui-builder/compare/v1.13.0...v1.13.1 +[v1.13.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.12.0...v1.13.0 +[v1.12.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.11.0...v1.12.0 +[v1.11.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.10.0...v1.11.0 +[v1.10.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.9.0...v1.10.0 +[v1.9.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.8.1...v1.9.0 +[v1.8.1]: https://github.com/OpenZeppelin/ui-builder/compare/v1.8.0...v1.8.1 +[v1.8.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.7.0...v1.8.0 +[v1.7.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.6.2...v1.7.0 +[v1.6.2]: https://github.com/OpenZeppelin/ui-builder/compare/v1.6.1...v1.6.2 +[v1.6.1]: https://github.com/OpenZeppelin/ui-builder/compare/v1.6.0...v1.6.1 +[v1.6.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.5.0...v1.6.0 +[v1.5.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.4.0...v1.5.0 +[v1.4.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.3.0...v1.4.0 +[v1.3.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.2.0...v1.3.0 +[v1.2.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.1.0...v1.2.0 +[v1.1.0]: https://github.com/OpenZeppelin/ui-builder/compare/v1.0.4...v1.1.0 +[v1.0.4]: https://github.com/OpenZeppelin/ui-builder/compare/v1.0.3...v1.0.4 +[v1.0.3]: https://github.com/OpenZeppelin/ui-builder/compare/v1.0.2...v1.0.3 +[v1.0.2]: https://github.com/OpenZeppelin/ui-builder/compare/v1.0.1...v1.0.2 +[v1.0.1]: https://github.com/OpenZeppelin/ui-builder/compare/v1.0.0...v1.0.1 +[v1.0.0]: https://github.com/OpenZeppelin/ui-builder/tree/v1.0.0 diff --git a/docs/content/ui-builder/customization.mdx b/docs/content/ui-builder/customization.mdx new file mode 100644 index 00000000..3dae685f --- /dev/null +++ b/docs/content/ui-builder/customization.mdx @@ -0,0 +1,66 @@ +--- +title: Customization +--- + +Once you have selected the function you want to build a form for, you can finally customize the experience the form will take. + +## General Settings + +In the general settings you can update the name of the form and a custom description to help clarify it’s purpose + +![Customization general settings](/ui-builder/customization-general.png) + +## Fields + +The fields section will let you customize how someone might interact with your form. This can be incredibly useful if you need to provide a better UX for values to fill in. You can also make certain fields or values hard coded into the form and only give users the option to execute the function. All of these are available so you can craft an experience for yourself or for someone who is non-technical and might need guidance on what to add. + +![Customization fields](/ui-builder/customization-fields.png) + +## Execution Method + +When it comes to how the form is executed as a transaction there are several options. + + + +These execution method restrictions only apply on the client form, NOT the contract itself. Make sure your contract has secure access controls in place! + + +### EOA + +The EOA method allows a user to connect their wallet and execute on the form. You can also specify if anyone can connect or just one specific address. + +![Customization EOA settings](/ui-builder/customization-eoa.png) + +### OpenZeppelin Relayer + +Forms can also be configured to be executed by an OpenZeppelin Relayer by providing the service URL and an API key for the relayer. From there you can select which of the available relayers to use. + +![Customization relayer settings](/ui-builder/customization-relayer.png) + +## Multisig (coming soon) + +Multisig services like Safe, Squads, etc. will be available in the near future! + +![Customization multisig settings](/ui-builder/customization-multisig.png) + +## Wallet UI Kit + +If you are using an EOA execution method you can customize the wallet connection experience. You can do this by using the OpenZeppelin setup which requires no additional configuration: + +![Customization wallet UI settings](/ui-builder/customization-wallet-ui.png) + +Of you can use RainbowKit which will require the additional RainbowKit config to be added to your final app. + +![Customization Rainbow Kit settings](/ui-builder/customization-rainbow.png) + +## Form Previews + +At any time during the customization phase you can open up the preview for your form to see what the final product will look like. + +![Customization preview button](/ui-builder/customization-preview-button.png) + +In the preview you can also test the interaction by connecting your wallet and using it! + +![Customization preview usage](/ui-builder/customization-preview-usage.png) + +![Customization preview transaction](/ui-builder/customization-preview-tx.png) diff --git a/docs/content/ui-builder/exporting-and-history.mdx b/docs/content/ui-builder/exporting-and-history.mdx new file mode 100644 index 00000000..471c57e4 --- /dev/null +++ b/docs/content/ui-builder/exporting-and-history.mdx @@ -0,0 +1,33 @@ +--- +title: Exporting and History +--- + +Once you have completed customizing your form it’s ready to ship! There are a few ways you can export and save previous UIs. + +## Exporting + +On the last step of the UI builder you can click the export button to download a zip file of your form as a VIte + React project. + +![Exporting export button](/ui-builder/exporting-export-button.png) + +After the download is complete, unzip the project and install dependencies and build the project. + +```bash +pnpm install && pnpm build +``` + +Once the build is complete run the dev server to see the form locally. + +```bash +pnpm dev +``` + +## History + +As you use the Contracts UI Builder the app will store a local history of all the forms you’ve built which can be see in the sidebar. + +![Exporting history contracts](/ui-builder/exporting-history-contracts.png) + +By clicking on the three small dots you can rename, duplicate, download, or delete templates from your history. There is also the ability to backup and restore histories as JSON files by clicking on the `Download` button on the sidebar. + +![Exporting history save configs](/ui-builder/exporting-history-save-configs.png) diff --git a/docs/content/ui-builder/functions.mdx b/docs/content/ui-builder/functions.mdx new file mode 100644 index 00000000..85e80ee3 --- /dev/null +++ b/docs/content/ui-builder/functions.mdx @@ -0,0 +1,13 @@ +--- +title: Functions +--- + +Once you have provided your contract information you can choose which function you would like to build a form for. At the moment forms are limited to a single function. Since contract state is included by default only write functions are available for forms. + +![Functions select interface](/ui-builder/functions-select.png) + +By using the information from the ABI the UI Builder will preview what functions are available and what the parameters are for each of them. + + + +If you don’t see your function listed be sure to check the ABI and the original contract diff --git a/docs/content/ui-builder/index.mdx b/docs/content/ui-builder/index.mdx new file mode 100644 index 00000000..58e136b2 --- /dev/null +++ b/docs/content/ui-builder/index.mdx @@ -0,0 +1,59 @@ +--- +title: Quickstart +--- + +The Contracts UI Builder is an open source tool you can quickly create online forms to interact with your smart contracts for testing or for administration purposes. It includes a vast amount of features including: + +* Configurable EVM Networks +* Automatic contract state and ABI scraping +* Custom forms to handle different types of contract inputs and functions +* Execution restriction options +* OpenZeppelin Wallet UI or Rainbow Kit +* Export as React App project + +## Getting Started + +Visit [builder.openzeppelin.com](https://builder.openzeppelin.com) to get started + +### 1. Select Network + +First select the network your contract is deployed to + +![Quickstart select network](/ui-builder/quickstart-select-network.png) + +### 2. Provide Contract Address + +Paste in the contract address and the UI Builder will fetch the ABI if the contract is verified. If it is not verified then provide the ABI in the form. + +![Quickstart contract address](/ui-builder/quickstart-contract-address.png) + +### 3. Select Function + +Choose which write function you would like to build a form for. + +![Quickstart select function](/ui-builder/quickstart-select-function.png) + +### 4. Customize + +Setup the form for your function and customize any applicable fields, execution method restrictions, or wallet UI kit. + + + +Check out the [Customization](/ui-builder/customization) section for more details + + +![Quickstart customize form](/ui-builder/quickstart-customize-form.png) + +### 5. Export + +Once complete you can click the "Export" button which will download the form as a React app you can deploy or customize further. + +![Quickstart export form](/ui-builder/quickstart-export-form.png) + +## Next Steps + +Learn how you can customize [networks](/ui-builder/networks) or customize the [forms](/ui-builder/functions) for your project. + +Visit the GitHub repo with the link below and open an issue if you have any problems! + +[GitHub Repo](https://github.com/OpenZeppelin/ui-builder) diff --git a/docs/content/ui-builder/loading-contracts.mdx b/docs/content/ui-builder/loading-contracts.mdx new file mode 100644 index 00000000..8d1e88b3 --- /dev/null +++ b/docs/content/ui-builder/loading-contracts.mdx @@ -0,0 +1,20 @@ +--- +title: Loading Contracts +--- + +After you have selected your chain you can paste in the deployed contract address. After providing the contract address the UI Builder will try to use public block explorer APIs to fetch the contract ABIs. This will only work if the contract is verified. + + + +If you experience rate or usage limits with the public block explorers [configure the network to use an API key](/ui-builder/networks) + + +![Contracts ABI auto loading](/ui-builder/contracts-abi-auto.png) + +If your contract is not verified you can still provide the ABI manually. + +![Contracts ABI manual input](/ui-builder/contracts-abi-manual.png) + +Once a contract and it’s ABI is loaded the UI Builder will automatically fetch the state of the contract, which can be toggled if you wish to hide it + +![Contracts state display](/ui-builder/contracts-state.png) diff --git a/docs/content/ui-builder/networks.mdx b/docs/content/ui-builder/networks.mdx new file mode 100644 index 00000000..ada27a12 --- /dev/null +++ b/docs/content/ui-builder/networks.mdx @@ -0,0 +1,55 @@ +--- +title: Networks +--- + +## Supported Networks + +Currently the Contracts UI Builder supports the following EVM networks + +**Mainnet** + +* Ethereum +* Arbitrum One +* Base +* Polygon +* Polygon zkEVM +* BNB Smart Chain +* OP Mainnet +* Avalanche C-Chain +* Linea +* Scroll +* ZkSync Era + +**Testnet** + +* Sepolia +* Arbitrum Sepolia +* Base Sepolia +* Polygon Amoy +* Polygon zkEVM Cardona +* BSC Testnet +* OP Sepolia +* Avalanche Fuji C-Chain +* Linea Sepolia +* Scroll Sepolia +* ZkSync Era Sepolia + +Midnight, Stellar, and Solana are planned to be supported in the future + +## Network Configs + +Each network can be configured to use custom RPCs, Explorers, and Explorer APIs. To open the network configuration modal, click on the settings icon on the right side of a network. + +![Networks config button](/ui-builder/networks-config-button.png) + +In this modal you can configure a custom RPC URL which can include path API key authorization + +![Networks RPC settings](/ui-builder/networks-rpc-settings.png) + +Under the explorer tab you can configure block explorers like Etherscan. By default the Contracts UI Builder uses public APIs that can be rate limited, so be sure to provide your own API key if that becomes an issue. + +![Networks explorer settings](/ui-builder/networks-explorer-settings.png) + +You can also toggle advance settings to use a custom block explorer setup. + +![Networks advance settings](/ui-builder/networks-advance-settings.png) diff --git a/docs/content/uniswap-hooks/api/base.mdx b/docs/content/uniswap-hooks/api/base.mdx new file mode 100644 index 00000000..59157b8f --- /dev/null +++ b/docs/content/uniswap-hooks/api/base.mdx @@ -0,0 +1,1605 @@ +--- +title: "Base" +description: "Smart contract base utilities and implementations" +--- + + + +
+ +## `BaseAsyncSwap` + + + + + +
+ +```solidity +import "uniswap-hooks/src/base/BaseAsyncSwap.sol"; +``` + +Base implementation for async swaps, which skip the v3-like swap implementation of the `PoolManager` +by taking the full swap input amount and returning a delta that nets out the specified amount to 0. + +This base hook allows developers to implement arbitrary logic to handle swaps, including use-cases like +asynchronous swaps and custom swap-ordering. However, given this flexibility, developers should ensure +that any logic implemented interacts safely with the `PoolManager` and works correctly. + +In order to handle async swaps, the hook mints ERC-6909 claim tokens for the specified currency and amount. +Inheriting contracts are free to handle these claim tokens as necessary, which can be redeemed for the +underlying currency by using the `settle` function from the `CurrencySettler` library. + + +If the hook is used for multiple pools, the ERC-6909 tokens must be separated and managed +independently for each pool in order to prevent draining of ERC-6909 tokens from one pool to another. + + + +The hook only supports async exact-input swaps. Exact-output swaps will be processed normally +by the `PoolManager`. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Functions

+
+- [constructor(_poolManager)](#BaseAsyncSwap-constructor-contract-IPoolManager-) +- [_beforeSwap(sender, key, params, )](#BaseAsyncSwap-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_calculateSwapFee(key, specifiedAmount)](#BaseAsyncSwap-_calculateSwapFee-struct-PoolKey-uint256-) +- [getHookPermissions()](#BaseAsyncSwap-getHookPermissions--) +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Events

+
+#### IHookEvents [!toc] +- [HookSwap(poolId, sender, amount0, amount1, hookLPfeeAmount0, hookLPfeeAmount1)](#IHookEvents-HookSwap-bytes32-address-int128-int128-uint128-uint128-) +- [HookFee(poolId, sender, feeAmount0, feeAmount1)](#IHookEvents-HookFee-bytes32-address-uint128-uint128-) +- [HookModifyLiquidity(poolId, sender, amount0, amount1)](#IHookEvents-HookModifyLiquidity-bytes32-address-int128-int128-) +- [HookBonus(poolId, amount0, amount1)](#IHookEvents-HookBonus-bytes32-uint128-uint128-) +#### BaseHook [!toc] +#### IHooks [!toc] +
+
+ +
+

Errors

+
+#### IHookEvents [!toc] +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the `PoolManager` address. + +
+
+ + + +
+
+

_beforeSwap(address sender, struct PoolKey key, struct SwapParams params, bytes) → bytes4, BeforeSwapDelta, uint24

+
+

internal

+# +
+
+
+ +Skip the v3-like swap implementation of the `PoolManager` by returning a delta that nets out the +specified amount to 0 to enable asynchronous swaps. + +
+
+ + + +
+
+

_calculateSwapFee(struct PoolKey key, uint256 specifiedAmount) → uint256 feeAmount

+
+

internal

+# +
+
+
+ +Calculate the fee amount for the swap. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically `beforeSwap` and `beforeSwapReturnDelta`. + +
+
+ + + +
+ +## `BaseCustomAccounting` + + + + + +
+ +```solidity +import "uniswap-hooks/src/base/BaseCustomAccounting.sol"; +``` + +Base implementation for custom accounting and hook-owned liquidity. + +To enable hook-owned liquidity, tokens must be deposited via the hook to allow control and flexibility +over the liquidity. The implementation inheriting this hook must implement the respective functions +to calculate the liquidity modification parameters and the amount of liquidity shares to mint or burn. + +Additionally, the implementer must consider that the hook is the sole owner of the liquidity and +manage fees over liquidity shares accordingly. + + +This base hook is designed to work with a single pool key. If you want to use the same custom +accounting hook for multiple pools, you must have multiple storage instances of this contract and +initialize them via the `PoolManager` with their respective pool keys. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Modifiers

+
+- [ensure(deadline)](#BaseCustomAccounting-ensure-uint256-) +
+
+ +
+

Functions

+
+- [constructor(_poolManager)](#BaseCustomAccounting-constructor-contract-IPoolManager-) +- [poolKey()](#BaseCustomAccounting-poolKey--) +- [addLiquidity(params)](#BaseCustomAccounting-addLiquidity-struct-BaseCustomAccounting-AddLiquidityParams-) +- [removeLiquidity(params)](#BaseCustomAccounting-removeLiquidity-struct-BaseCustomAccounting-RemoveLiquidityParams-) +- [_modifyLiquidity(params)](#BaseCustomAccounting-_modifyLiquidity-bytes-) +- [unlockCallback(rawData)](#BaseCustomAccounting-unlockCallback-bytes-) +- [_handleAccruedFees(data, callerDelta, feesAccrued)](#BaseCustomAccounting-_handleAccruedFees-struct-BaseCustomAccounting-CallbackData-BalanceDelta-BalanceDelta-) +- [_beforeInitialize(, key, )](#BaseCustomAccounting-_beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeAddLiquidity(, , , )](#BaseCustomAccounting-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseCustomAccounting-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_getAddLiquidity(sqrtPriceX96, params)](#BaseCustomAccounting-_getAddLiquidity-uint160-struct-BaseCustomAccounting-AddLiquidityParams-) +- [_getRemoveLiquidity(params)](#BaseCustomAccounting-_getRemoveLiquidity-struct-BaseCustomAccounting-RemoveLiquidityParams-) +- [_mint(params, callerDelta, feesAccrued, shares)](#BaseCustomAccounting-_mint-struct-BaseCustomAccounting-AddLiquidityParams-BalanceDelta-BalanceDelta-uint256-) +- [_burn(params, callerDelta, feesAccrued, shares)](#BaseCustomAccounting-_burn-struct-BaseCustomAccounting-RemoveLiquidityParams-BalanceDelta-BalanceDelta-uint256-) +- [getHookPermissions()](#BaseCustomAccounting-getHookPermissions--) +#### IUnlockCallback [!toc] +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_beforeSwap(, , , )](#BaseHook-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Events

+
+#### IUnlockCallback [!toc] +#### IHookEvents [!toc] +- [HookSwap(poolId, sender, amount0, amount1, hookLPfeeAmount0, hookLPfeeAmount1)](#IHookEvents-HookSwap-bytes32-address-int128-int128-uint128-uint128-) +- [HookFee(poolId, sender, feeAmount0, feeAmount1)](#IHookEvents-HookFee-bytes32-address-uint128-uint128-) +- [HookModifyLiquidity(poolId, sender, amount0, amount1)](#IHookEvents-HookModifyLiquidity-bytes32-address-int128-int128-) +- [HookBonus(poolId, amount0, amount1)](#IHookEvents-HookBonus-bytes32-uint128-uint128-) +#### BaseHook [!toc] +#### IHooks [!toc] +
+
+ +
+

Errors

+
+- [ExpiredPastDeadline()](#BaseCustomAccounting-ExpiredPastDeadline--) +- [PoolNotInitialized()](#BaseCustomAccounting-PoolNotInitialized--) +- [TooMuchSlippage()](#BaseCustomAccounting-TooMuchSlippage--) +- [LiquidityOnlyViaHook()](#BaseCustomAccounting-LiquidityOnlyViaHook--) +- [InvalidNativeValue()](#BaseCustomAccounting-InvalidNativeValue--) +- [AlreadyInitialized()](#BaseCustomAccounting-AlreadyInitialized--) +#### IUnlockCallback [!toc] +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

ensure(uint256 deadline)

+
+

internal

+# +
+
+ +
+ +Ensure the deadline of a liquidity modification request is not expired. + +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the pool `PoolManager` address. + +
+
+ + + +
+
+

poolKey() → struct PoolKey

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

addLiquidity(struct BaseCustomAccounting.AddLiquidityParams params) → BalanceDelta delta

+
+

public

+# +
+
+
+ +To cover all possible scenarios, `msg.sender` should have already given the hook an allowance +of at least amount0Desired/amount1Desired on token0/token1. Always adds assets at the ideal ratio, +according to the price when the transaction is executed. + + +The `amount0Min` and `amount1Min` parameters are relative to the principal delta, which excludes +fees accrued from the liquidity modification delta. + + +
+
+ + + +
+
+

removeLiquidity(struct BaseCustomAccounting.RemoveLiquidityParams params) → BalanceDelta delta

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

_modifyLiquidity(bytes params) → BalanceDelta callerDelta, BalanceDelta feesAccrued

+
+

internal

+# +
+
+
+ +Calls the `PoolManager` to unlock and call back the hook's `unlockCallback` function. + +
+
+ + + +
+
+

unlockCallback(bytes rawData) → bytes returnData

+
+

public

+# +
+
+
+ +Callback from the `PoolManager` when liquidity is modified, either adding or removing. + +
+
+ + + +
+
+

_handleAccruedFees(struct BaseCustomAccounting.CallbackData data, BalanceDelta callerDelta, BalanceDelta feesAccrued)

+
+

internal

+# +
+
+
+ +Handle any fees accrued in a liquidity position. By default, this function transfers the tokens to the +owner of the liquidity position. However, this function can be overridden to take fees accrued in the position, +or any other desired logic. + +
+
+ + + +
+
+

_beforeInitialize(address, struct PoolKey key, uint160) → bytes4

+
+

internal

+# +
+
+
+ +Initialize the hook's pool key. The stored key should act immutably so that +it can safely be used across the hook's functions. + +
+
+ + + +
+
+

_beforeAddLiquidity(address, struct PoolKey, struct ModifyLiquidityParams, bytes) → bytes4

+
+

internal

+# +
+
+
+ +Revert when liquidity is attempted to be added via the `PoolManager`. + +
+
+ + + +
+
+

_beforeRemoveLiquidity(address, struct PoolKey, struct ModifyLiquidityParams, bytes) → bytes4

+
+

internal

+# +
+
+
+ +Revert when liquidity is attempted to be removed via the `PoolManager`. + +
+
+ + + +
+
+

_getAddLiquidity(uint160 sqrtPriceX96, struct BaseCustomAccounting.AddLiquidityParams params) → bytes modify, uint256 shares

+
+

internal

+# +
+
+
+ +Get the liquidity modification to apply for a given liquidity addition, +and the amount of liquidity shares would be minted to the sender. + +
+
+ + + +
+
+

_getRemoveLiquidity(struct BaseCustomAccounting.RemoveLiquidityParams params) → bytes modify, uint256 shares

+
+

internal

+# +
+
+
+ +Get the liquidity modification to apply for a given liquidity removal, +and the amount of liquidity shares would be burned from the sender. + +
+
+ + + +
+
+

_mint(struct BaseCustomAccounting.AddLiquidityParams params, BalanceDelta callerDelta, BalanceDelta feesAccrued, uint256 shares)

+
+

internal

+# +
+
+
+ +Mint liquidity shares to the sender. + +
+
+ + + +
+
+

_burn(struct BaseCustomAccounting.RemoveLiquidityParams params, BalanceDelta callerDelta, BalanceDelta feesAccrued, uint256 shares)

+
+

internal

+# +
+
+
+ +Burn liquidity shares from the sender. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically `beforeInitialize`, `beforeAddLiquidity` and `beforeRemoveLiquidity`. + +
+
+ + + +
+
+

ExpiredPastDeadline()

+
+

error

+# +
+
+
+ +A liquidity modification order was attempted to be executed after the deadline. + +
+
+ + + +
+
+

PoolNotInitialized()

+
+

error

+# +
+
+
+ +Pool was not initialized. + +
+
+ + + +
+
+

TooMuchSlippage()

+
+

error

+# +
+
+
+ +Principal delta of liquidity modification resulted in too much slippage. + +
+
+ + + +
+
+

LiquidityOnlyViaHook()

+
+

error

+# +
+
+
+ +Liquidity was attempted to be added or removed via the `PoolManager` instead of the hook. + +
+
+ + + +
+
+

InvalidNativeValue()

+
+

error

+# +
+
+
+ +Native currency was not sent with the correct amount. + +
+
+ + + +
+
+

AlreadyInitialized()

+
+

error

+# +
+
+
+ +Hook was already initialized. + +
+
+ + + +
+ +## `BaseCustomCurve` + + + + + +
+ +```solidity +import "uniswap-hooks/src/base/BaseCustomCurve.sol"; +``` + +Base implementation for custom curves, inheriting from [`BaseCustomAccounting`](#BaseCustomAccounting). + +This hook allows to implement a custom curve (or any logic) for swaps, which overrides the default v3-like +concentrated liquidity implementation of the `PoolManager`. During a swap, the hook calls the +[`BaseCustomCurve._getUnspecifiedAmount`](#BaseCustomCurve-_getUnspecifiedAmount-struct-SwapParams-) function to get the amount of tokens to be sent to the receiver. The return delta +created from this calculation is then consumed and applied by the `PoolManager`. + + +This hook by default does not include fee or salt mechanisms, which can be implemented by inheriting +contracts if needed. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Functions

+
+- [constructor(_poolManager)](#BaseCustomCurve-constructor-contract-IPoolManager-) +- [_getAddLiquidity(, params)](#BaseCustomCurve-_getAddLiquidity-uint160-struct-BaseCustomAccounting-AddLiquidityParams-) +- [_getRemoveLiquidity(params)](#BaseCustomCurve-_getRemoveLiquidity-struct-BaseCustomAccounting-RemoveLiquidityParams-) +- [_beforeSwap(sender, key, params, )](#BaseCustomCurve-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_modifyLiquidity(params)](#BaseCustomCurve-_modifyLiquidity-bytes-) +- [unlockCallback(rawData)](#BaseCustomCurve-unlockCallback-bytes-) +- [_getUnspecifiedAmount(params)](#BaseCustomCurve-_getUnspecifiedAmount-struct-SwapParams-) +- [_getSwapFeeAmount(params, unspecifiedAmount)](#BaseCustomCurve-_getSwapFeeAmount-struct-SwapParams-uint256-) +- [_getAmountOut(params)](#BaseCustomCurve-_getAmountOut-struct-BaseCustomAccounting-RemoveLiquidityParams-) +- [_getAmountIn(params)](#BaseCustomCurve-_getAmountIn-struct-BaseCustomAccounting-AddLiquidityParams-) +- [getHookPermissions()](#BaseCustomCurve-getHookPermissions--) +#### BaseCustomAccounting [!toc] +- [poolKey()](#BaseCustomAccounting-poolKey--) +- [addLiquidity(params)](#BaseCustomAccounting-addLiquidity-struct-BaseCustomAccounting-AddLiquidityParams-) +- [removeLiquidity(params)](#BaseCustomAccounting-removeLiquidity-struct-BaseCustomAccounting-RemoveLiquidityParams-) +- [_handleAccruedFees(data, callerDelta, feesAccrued)](#BaseCustomAccounting-_handleAccruedFees-struct-BaseCustomAccounting-CallbackData-BalanceDelta-BalanceDelta-) +- [_beforeInitialize(, key, )](#BaseCustomAccounting-_beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeAddLiquidity(, , , )](#BaseCustomAccounting-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseCustomAccounting-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_mint(params, callerDelta, feesAccrued, shares)](#BaseCustomAccounting-_mint-struct-BaseCustomAccounting-AddLiquidityParams-BalanceDelta-BalanceDelta-uint256-) +- [_burn(params, callerDelta, feesAccrued, shares)](#BaseCustomAccounting-_burn-struct-BaseCustomAccounting-RemoveLiquidityParams-BalanceDelta-BalanceDelta-uint256-) +#### IUnlockCallback [!toc] +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Events

+
+#### BaseCustomAccounting [!toc] +#### IUnlockCallback [!toc] +#### IHookEvents [!toc] +- [HookSwap(poolId, sender, amount0, amount1, hookLPfeeAmount0, hookLPfeeAmount1)](#IHookEvents-HookSwap-bytes32-address-int128-int128-uint128-uint128-) +- [HookFee(poolId, sender, feeAmount0, feeAmount1)](#IHookEvents-HookFee-bytes32-address-uint128-uint128-) +- [HookModifyLiquidity(poolId, sender, amount0, amount1)](#IHookEvents-HookModifyLiquidity-bytes32-address-int128-int128-) +- [HookBonus(poolId, amount0, amount1)](#IHookEvents-HookBonus-bytes32-uint128-uint128-) +#### BaseHook [!toc] +#### IHooks [!toc] +
+
+ +
+

Errors

+
+#### BaseCustomAccounting [!toc] +- [ExpiredPastDeadline()](#BaseCustomAccounting-ExpiredPastDeadline--) +- [PoolNotInitialized()](#BaseCustomAccounting-PoolNotInitialized--) +- [TooMuchSlippage()](#BaseCustomAccounting-TooMuchSlippage--) +- [LiquidityOnlyViaHook()](#BaseCustomAccounting-LiquidityOnlyViaHook--) +- [InvalidNativeValue()](#BaseCustomAccounting-InvalidNativeValue--) +- [AlreadyInitialized()](#BaseCustomAccounting-AlreadyInitialized--) +#### IUnlockCallback [!toc] +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the pool `PoolManager` address. + +
+
+ + + +
+
+

_getAddLiquidity(uint160, struct BaseCustomAccounting.AddLiquidityParams params) → bytes, uint256

+
+

internal

+# +
+
+
+ +Defines how the liquidity modification data is encoded and returned +for an add liquidity request. + +
+
+ + + +
+
+

_getRemoveLiquidity(struct BaseCustomAccounting.RemoveLiquidityParams params) → bytes, uint256

+
+

internal

+# +
+
+
+ +Defines how the liquidity modification data is encoded and returned +for a remove liquidity request. + +
+
+ + + +
+
+

_beforeSwap(address sender, struct PoolKey key, struct SwapParams params, bytes) → bytes4, BeforeSwapDelta, uint24

+
+

internal

+# +
+
+
+ +Overrides the default swap logic of the `PoolManager` and calls the [`BaseCustomCurve._getUnspecifiedAmount`](#BaseCustomCurve-_getUnspecifiedAmount-struct-SwapParams-) +to get the amount of tokens to be sent to the receiver. + + +In order to take and settle tokens from the pool, the hook must hold the liquidity added +via the [`BaseCustomAccounting.addLiquidity`](#BaseCustomAccounting-addLiquidity-struct-BaseCustomAccounting-AddLiquidityParams-) function. + + +
+
+ + + +
+
+

_modifyLiquidity(bytes params) → BalanceDelta callerDelta, BalanceDelta feesAccrued

+
+

internal

+# +
+
+
+ +Overrides the custom accounting logic to support the custom curve integer amounts. + +
+
+ + + +
+
+

unlockCallback(bytes rawData) → bytes returnData

+
+

public

+# +
+
+
+ +Decodes the callback data and applies the liquidity modifications, overriding the custom +accounting logic to mint and burn ERC-6909 claim tokens which are used in swaps. + +
+
+ + + +
+
+

_getUnspecifiedAmount(struct SwapParams params) → uint256 unspecifiedAmount

+
+

internal

+# +
+
+
+ +Calculate the amount of the unspecified currency to be taken or settled from the swapper, depending on the swap +direction and the fee amount to be paid to LPs. + +
+
+ + + +
+
+

_getSwapFeeAmount(struct SwapParams params, uint256 unspecifiedAmount) → uint256 swapFeeAmount

+
+

internal

+# +
+
+
+ +Calculate the amount of fees to be paid to LPs in a swap. + +
+
+ + + +
+
+

_getAmountOut(struct BaseCustomAccounting.RemoveLiquidityParams params) → uint256 amount0, uint256 amount1, uint256 shares

+
+

internal

+# +
+
+
+ +Calculate the amount of tokens to use and liquidity shares to burn for a remove liquidity request. + +
+
+ + + +
+
+

_getAmountIn(struct BaseCustomAccounting.AddLiquidityParams params) → uint256 amount0, uint256 amount1, uint256 shares

+
+

internal

+# +
+
+
+ +Calculate the amount of tokens to use and liquidity shares to mint for an add liquidity request. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically `beforeInitialize`, `beforeAddLiquidity`, `beforeRemoveLiquidity`, +`beforeSwap`, and `beforeSwapReturnDelta` + +
+
+ + + +
+ +## `BaseHook` + + + + + +
+ +```solidity +import "uniswap-hooks/src/base/BaseHook.sol"; +``` + +Base hook implementation. + +This contract defines all hook entry points, as well as security and permission helpers. +Based on the [Uniswap v4 periphery implementation](https://github.com/Uniswap/v4-periphery/blob/main/src/base/hooks/BaseHook.sol). + + +Hook entry points must be overridden and implemented by the inheriting hook to be used. Their respective +flags must be set to true in the `getHookPermissions` function as well. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Modifiers

+
+- [onlyPoolManager()](#BaseHook-onlyPoolManager--) +- [onlySelf()](#BaseHook-onlySelf--) +- [onlyValidPools(hooks)](#BaseHook-onlyValidPools-contract-IHooks-) +
+
+ +
+

Functions

+
+- [constructor(_poolManager)](#BaseHook-constructor-contract-IPoolManager-) +- [getHookPermissions()](#BaseHook-getHookPermissions--) +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_beforeSwap(, , , )](#BaseHook-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Errors

+
+- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

onlyPoolManager()

+
+

internal

+# +
+
+ +
+ +
+
+ + + +
+
+

onlySelf()

+
+

internal

+# +
+
+ +
+ +Restrict the function to only be callable by the hook itself. + +
+
+ + + +
+
+

onlyValidPools(contract IHooks hooks)

+
+

internal

+# +
+
+ +
+ +Restrict the function to only be called for a valid pool. + +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the pool manager and check that the hook address matches the expected permissions and flags. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Get the hook permissions to signal which hook functions are to be implemented. + +Used at deployment to validate the address correctly represents the expected permissions. + +
+
+ + + +
+
+

_validateHookAddress(contract BaseHook hook)

+
+

internal

+# +
+
+
+ +Validate the hook address against the expected permissions. + +
+
+ + + +
+
+

beforeInitialize(address sender, struct PoolKey key, uint160 sqrtPriceX96) → bytes4

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_beforeInitialize(address, struct PoolKey, uint160) → bytes4

+
+

internal

+# +
+
+
+ +Hook implementation for `beforeInitialize`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

afterInitialize(address sender, struct PoolKey key, uint160 sqrtPriceX96, int24 tick) → bytes4

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_afterInitialize(address, struct PoolKey, uint160, int24) → bytes4

+
+

internal

+# +
+
+
+ +Hook implementation for `afterInitialize`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

beforeAddLiquidity(address sender, struct PoolKey key, struct ModifyLiquidityParams params, bytes hookData) → bytes4

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_beforeAddLiquidity(address, struct PoolKey, struct ModifyLiquidityParams, bytes) → bytes4

+
+

internal

+# +
+
+
+ +Hook implementation for `beforeAddLiquidity`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

beforeRemoveLiquidity(address sender, struct PoolKey key, struct ModifyLiquidityParams params, bytes hookData) → bytes4

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_beforeRemoveLiquidity(address, struct PoolKey, struct ModifyLiquidityParams, bytes) → bytes4

+
+

internal

+# +
+
+
+ +Hook implementation for `beforeRemoveLiquidity`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

afterAddLiquidity(address sender, struct PoolKey key, struct ModifyLiquidityParams params, BalanceDelta delta0, BalanceDelta delta1, bytes hookData) → bytes4, BalanceDelta

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_afterAddLiquidity(address, struct PoolKey, struct ModifyLiquidityParams, BalanceDelta, BalanceDelta, bytes) → bytes4, BalanceDelta

+
+

internal

+# +
+
+
+ +Hook implementation for `afterAddLiquidity`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

afterRemoveLiquidity(address sender, struct PoolKey key, struct ModifyLiquidityParams params, BalanceDelta delta0, BalanceDelta delta1, bytes hookData) → bytes4, BalanceDelta

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_afterRemoveLiquidity(address, struct PoolKey, struct ModifyLiquidityParams, BalanceDelta, BalanceDelta, bytes) → bytes4, BalanceDelta

+
+

internal

+# +
+
+
+ +Hook implementation for `afterRemoveLiquidity`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

beforeSwap(address sender, struct PoolKey key, struct SwapParams params, bytes hookData) → bytes4, BeforeSwapDelta, uint24

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_beforeSwap(address, struct PoolKey, struct SwapParams, bytes) → bytes4, BeforeSwapDelta, uint24

+
+

internal

+# +
+
+
+ +Hook implementation for `beforeSwap`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

afterSwap(address sender, struct PoolKey key, struct SwapParams params, BalanceDelta delta, bytes hookData) → bytes4, int128

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_afterSwap(address, struct PoolKey, struct SwapParams, BalanceDelta, bytes) → bytes4, int128

+
+

internal

+# +
+
+
+ +Hook implementation for `afterSwap`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

beforeDonate(address sender, struct PoolKey key, uint256 amount0, uint256 amount1, bytes hookData) → bytes4

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_beforeDonate(address, struct PoolKey, uint256, uint256, bytes) → bytes4

+
+

internal

+# +
+
+
+ +Hook implementation for `beforeDonate`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

afterDonate(address sender, struct PoolKey key, uint256 amount0, uint256 amount1, bytes hookData) → bytes4

+
+

external

+# +
+
+
+ +
+
+ + + +
+
+

_afterDonate(address, struct PoolKey, uint256, uint256, bytes) → bytes4

+
+

internal

+# +
+
+
+ +Hook implementation for `afterDonate`, to be overridden by the inheriting hook. The +flag must be set to true in the `getHookPermissions` function. + +
+
+ + + +
+
+

poolManager() → contract IPoolManager

+
+

public

+# +
+
+
+ +
+
+ + + +
+
+

NotSelf()

+
+

error

+# +
+
+
+ +The hook is not the caller. + +
+
+ + + +
+
+

InvalidPool()

+
+

error

+# +
+
+
+ +The pool is not authorized to use this hook. + +
+
+ + + +
+
+

HookNotImplemented()

+
+

error

+# +
+
+
+ +The hook function is not implemented. + +
+
+ + + +
+
+

NotPoolManager()

+
+

error

+# +
+
+
+ +
+
diff --git a/docs/content/uniswap-hooks/api/fee.mdx b/docs/content/uniswap-hooks/api/fee.mdx new file mode 100644 index 00000000..df0131c7 --- /dev/null +++ b/docs/content/uniswap-hooks/api/fee.mdx @@ -0,0 +1,652 @@ +--- +title: "Fee" +description: "Smart contract fee utilities and implementations" +--- + + + +
+ +## `BaseDynamicAfterFee` + + + + + +
+ +```solidity +import "uniswap-hooks/src/fee/BaseDynamicAfterFee.sol"; +``` + +Base implementation for dynamic target hook fees applied after swaps. + +Enables to enforce a dynamic target determined by [`BaseDynamicAfterFee._getTargetUnspecified`](#BaseDynamicAfterFee-_getTargetUnspecified-address-struct-PoolKey-struct-SwapParams-bytes-) for the unspecified currency of the swap +during [`BaseAsyncSwap._beforeSwap`](/uniswap-hooks/api/base#BaseAsyncSwap-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-), where if the swap outcome results better than the target, any positive difference is taken +as a hook fee, being posteriorily handled or distributed by the hook via [`BaseDynamicAfterFee._afterSwapHandler`](#BaseDynamicAfterFee-_afterSwapHandler-struct-PoolKey-struct-SwapParams-BalanceDelta-uint256-uint256-). + + +In order to use this hook, the inheriting contract must implement [`BaseDynamicAfterFee._getTargetUnspecified`](#BaseDynamicAfterFee-_getTargetUnspecified-address-struct-PoolKey-struct-SwapParams-bytes-) to determine the target, +and [`BaseDynamicAfterFee._afterSwapHandler`](#BaseDynamicAfterFee-_afterSwapHandler-struct-PoolKey-struct-SwapParams-BalanceDelta-uint256-uint256-) to handle accumulated fees. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Functions

+
+- [_transientTargetUnspecifiedAmount()](#BaseDynamicAfterFee-_transientTargetUnspecifiedAmount--) +- [_transientApplyTarget()](#BaseDynamicAfterFee-_transientApplyTarget--) +- [_setTransientTargetUnspecifiedAmount(value)](#BaseDynamicAfterFee-_setTransientTargetUnspecifiedAmount-uint256-) +- [_setTransientApplyTarget(value)](#BaseDynamicAfterFee-_setTransientApplyTarget-bool-) +- [constructor(_poolManager)](#BaseDynamicAfterFee-constructor-contract-IPoolManager-) +- [_beforeSwap(sender, key, params, hookData)](#BaseDynamicAfterFee-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_afterSwap(sender, key, params, delta, )](#BaseDynamicAfterFee-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_getTargetUnspecified(sender, key, params, hookData)](#BaseDynamicAfterFee-_getTargetUnspecified-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_afterSwapHandler(key, params, delta, targetUnspecifiedAmount, feeAmount)](#BaseDynamicAfterFee-_afterSwapHandler-struct-PoolKey-struct-SwapParams-BalanceDelta-uint256-uint256-) +- [getHookPermissions()](#BaseDynamicAfterFee-getHookPermissions--) +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Events

+
+#### IHookEvents [!toc] +- [HookSwap(poolId, sender, amount0, amount1, hookLPfeeAmount0, hookLPfeeAmount1)](#IHookEvents-HookSwap-bytes32-address-int128-int128-uint128-uint128-) +- [HookFee(poolId, sender, feeAmount0, feeAmount1)](#IHookEvents-HookFee-bytes32-address-uint128-uint128-) +- [HookModifyLiquidity(poolId, sender, amount0, amount1)](#IHookEvents-HookModifyLiquidity-bytes32-address-int128-int128-) +- [HookBonus(poolId, amount0, amount1)](#IHookEvents-HookBonus-bytes32-uint128-uint128-) +#### BaseHook [!toc] +#### IHooks [!toc] +
+
+ +
+

Errors

+
+#### IHookEvents [!toc] +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

_transientTargetUnspecifiedAmount() → uint256

+
+

internal

+# +
+
+
+ +The target unspecified amount to be enforced by the `afterSwap` hook. + +
+
+ + + +
+
+

_transientApplyTarget() → bool

+
+

internal

+# +
+
+
+ +Whether the target unspecified amount should be enforced by the `afterSwap` hook. + +
+
+ + + +
+
+

_setTransientTargetUnspecifiedAmount(uint256 value)

+
+

internal

+# +
+
+
+ +Set the target unspecified amount to be enforced by the `afterSwap` hook. + +
+
+ + + +
+
+

_setTransientApplyTarget(bool value)

+
+

internal

+# +
+
+
+ +Set the apply flag to be used in the `afterSwap` hook. + +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the `PoolManager` address. + +
+
+ + + +
+
+

_beforeSwap(address sender, struct PoolKey key, struct SwapParams params, bytes hookData) → bytes4, BeforeSwapDelta, uint24

+
+

internal

+# +
+
+
+ +Sets the target unspecified amount and apply flag to be used in the `afterSwap` hook. + + +The target unspecified amount and the apply flag are reset in the `afterSwap` hook. + + +
+
+ + + +
+
+

_afterSwap(address sender, struct PoolKey key, struct SwapParams params, BalanceDelta delta, bytes) → bytes4, int128

+
+

internal

+# +
+
+
+ +Enforce the target unspecified amount to the unspecified currency of the swap. + +When the swap is `exactInput` and the unspecified target is surpassed, the difference is decreased from the +output as a hook fee. Accordingly, when the swap is `exactOutput` and the unspecified target is not reached, the +difference is increased to the input as a hook fee. Note that the fee is always applied to the unspecified +currency of the swap, regardless of the swap direction. + +The fees are minted to this hook as ERC-6909 tokens, which can then be distributed in [`BaseDynamicAfterFee._afterSwapHandler`](#BaseDynamicAfterFee-_afterSwapHandler-struct-PoolKey-struct-SwapParams-BalanceDelta-uint256-uint256-) + + +The target unspecified amount and the apply flag are reset on purpose to avoid state overlapping across swaps. + + +
+
+ + + +
+
+

_getTargetUnspecified(address sender, struct PoolKey key, struct SwapParams params, bytes hookData) → uint256 targetUnspecifiedAmount, bool applyTarget

+
+

internal

+# +
+
+
+ +Return the target unspecified amount to be enforced by the `afterSwap` hook. + +
+
+ + + +
+
+

_afterSwapHandler(struct PoolKey key, struct SwapParams params, BalanceDelta delta, uint256 targetUnspecifiedAmount, uint256 feeAmount)

+
+

internal

+# +
+
+
+ +Customizable handler called after `_afterSwap` to handle or distribute the fees. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically [`BaseHook.beforeSwap`](/uniswap-hooks/api/base#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-), [`BaseHook.afterSwap`](/uniswap-hooks/api/base#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) and `afterSwapReturnDelta`. + +
+
+ + + +
+ +## `BaseDynamicFee` + + + + + +
+ +```solidity +import "uniswap-hooks/src/fee/BaseDynamicFee.sol"; +``` + +Base implementation to apply a dynamic fee via the `PoolManager`'s `updateDynamicLPFee` function. + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Functions

+
+- [constructor(_poolManager)](#BaseDynamicFee-constructor-contract-IPoolManager-) +- [_getFee(key)](#BaseDynamicFee-_getFee-struct-PoolKey-) +- [_afterInitialize(, key, , )](#BaseDynamicFee-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [poke(key)](#BaseDynamicFee-poke-struct-PoolKey-) +- [getHookPermissions()](#BaseDynamicFee-getHookPermissions--) +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_beforeSwap(, , , )](#BaseHook-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Errors

+
+- [NotDynamicFee()](#BaseDynamicFee-NotDynamicFee--) +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the `PoolManager` address. + +
+
+ + + +
+
+

_getFee(struct PoolKey key) → uint24

+
+

internal

+# +
+
+
+ +Returns a fee, denominated in hundredths of a bip, to be applied to the pool after it is initialized. + +
+
+ + + +
+
+

_afterInitialize(address, struct PoolKey key, uint160, int24) → bytes4

+
+

internal

+# +
+
+
+ +Set the fee after the pool is initialized. + +
+
+ + + +
+
+

poke(struct PoolKey key)

+
+

public

+# +
+
+
+ +Updates the dynamic LP fee for the given pool, which must have a key +that contains this hook's address. + + +This function can be called by anyone at any time. If `_getFee` implementation +depends on external conditions (e.g., oracle prices, other pool states, token balances), +it may be vulnerable to manipulation. An attacker could potentially: +1. Manipulate the external conditions that `_getFee` depends on +2. Call `poke()` to update the fee to a more favorable rate +3. Execute trades at the manipulated fee rate + + +Inheriting contracts should consider implementing access controls on this function, +make the logic in `_getFee` resistant to short-term manipulation, or accept the risk +of fee manipulation. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically `afterInitialize`. + +
+
+ + + +
+
+

NotDynamicFee()

+
+

error

+# +
+
+
+ +The hook was attempted to be initialized with a non-dynamic fee. + +
+
+ + + +
+ +## `BaseOverrideFee` + + + + + +
+ +```solidity +import "uniswap-hooks/src/fee/BaseOverrideFee.sol"; +``` + +Base implementation for automatic dynamic fees applied before swaps. + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.0_ + +
+

Functions

+
+- [constructor(_poolManager)](#BaseOverrideFee-constructor-contract-IPoolManager-) +- [_afterInitialize(, key, , )](#BaseOverrideFee-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_getFee(sender, key, params, hookData)](#BaseOverrideFee-_getFee-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_beforeSwap(sender, key, params, hookData)](#BaseOverrideFee-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [getHookPermissions()](#BaseOverrideFee-getHookPermissions--) +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Errors

+
+- [NotDynamicFee()](#BaseOverrideFee-NotDynamicFee--) +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +Set the `PoolManager` address. + +
+
+ + + +
+
+

_afterInitialize(address, struct PoolKey key, uint160, int24) → bytes4

+
+

internal

+# +
+
+
+ +Check that the pool key has a dynamic fee. + +
+
+ + + +
+
+

_getFee(address sender, struct PoolKey key, struct SwapParams params, bytes hookData) → uint24

+
+

internal

+# +
+
+
+ +Returns a fee, denominated in hundredths of a bip, to be applied to a swap. + +
+
+ + + +
+
+

_beforeSwap(address sender, struct PoolKey key, struct SwapParams params, bytes hookData) → bytes4, BeforeSwapDelta, uint24

+
+

internal

+# +
+
+
+ +Set the fee before the swap is processed using the override fee flag. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically `afterInitialize` and `beforeSwap`. + +
+
+ + + +
+
+

NotDynamicFee()

+
+

error

+# +
+
+
+ +The hook was attempted to be initialized with a non-dynamic fee. + +
+
diff --git a/docs/content/uniswap-hooks/api/general.mdx b/docs/content/uniswap-hooks/api/general.mdx new file mode 100644 index 00000000..703fadb1 --- /dev/null +++ b/docs/content/uniswap-hooks/api/general.mdx @@ -0,0 +1,1314 @@ +--- +title: "General" +description: "Smart contract general utilities and implementations" +--- + + + +
+ +## `AntiSandwichHook` + + + + + +
+ +```solidity +import "uniswap-hooks/src/general/AntiSandwichHook.sol"; +``` + +This hook implements the sandwich-resistant AMM design introduced +[here](https://www.umbraresearch.xyz/writings/sandwich-resistant-amm). Specifically, +this hook guarantees that no swaps get filled at a price better than the price at +the beginning of the slot window (i.e. one block). + +Within a slot window, swaps impact the pool asymmetrically for buys and sells. +When a buy order is executed, the offer on the pool increases in accordance with +the xy=k curve. However, the bid price remains constant, instead increasing the +amount of liquidity on the bid. Subsequent sells eat into this liquidity, while +decreasing the offer price according to xy=k. + +In order to use this hook, the inheriting contract must implement the `_handleCollectedFees` function +to determine how to handle the collected fees from the anti-sandwich mechanism. + + +The Anti-sandwich mechanism only protects swaps in the zeroForOne swap direction. +Swaps in the !zeroForOne direction are not protected by this hook design. + + + +Since this hook makes MEV not profitable, there's not as much arbitrage in +the pool, making prices at beginning of the block not necessarily close to market price. + + + +In `_beforeSwap`, the hook iterates over all ticks between last tick and current tick. +Developers must be aware that for large price changes in pools with small tick spacing, the `for` +loop will iterate over a large number of ticks, which could lead to `MemoryOOG` error. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v1.1.0_ + +
+

Functions

+
+- [constructor(_poolManager)](#AntiSandwichHook-constructor-contract-IPoolManager-) +- [_beforeSwap(sender, key, params, hookData)](#AntiSandwichHook-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_getBlockNumber()](#AntiSandwichHook-_getBlockNumber--) +- [_getTargetUnspecified(, key, params, )](#AntiSandwichHook-_getTargetUnspecified-address-struct-PoolKey-struct-SwapParams-bytes-) +- [getHookPermissions()](#AntiSandwichHook-getHookPermissions--) +#### BaseDynamicAfterFee [!toc] +- [_transientTargetUnspecifiedAmount()](#BaseDynamicAfterFee-_transientTargetUnspecifiedAmount--) +- [_transientApplyTarget()](#BaseDynamicAfterFee-_transientApplyTarget--) +- [_setTransientTargetUnspecifiedAmount(value)](#BaseDynamicAfterFee-_setTransientTargetUnspecifiedAmount-uint256-) +- [_setTransientApplyTarget(value)](#BaseDynamicAfterFee-_setTransientApplyTarget-bool-) +- [_afterSwap(sender, key, params, delta, )](#BaseDynamicAfterFee-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwapHandler(key, params, delta, targetUnspecifiedAmount, feeAmount)](#BaseDynamicAfterFee-_afterSwapHandler-struct-PoolKey-struct-SwapParams-BalanceDelta-uint256-uint256-) +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Events

+
+#### BaseDynamicAfterFee [!toc] +#### IHookEvents [!toc] +- [HookSwap(poolId, sender, amount0, amount1, hookLPfeeAmount0, hookLPfeeAmount1)](#IHookEvents-HookSwap-bytes32-address-int128-int128-uint128-uint128-) +- [HookFee(poolId, sender, feeAmount0, feeAmount1)](#IHookEvents-HookFee-bytes32-address-uint128-uint128-) +- [HookModifyLiquidity(poolId, sender, amount0, amount1)](#IHookEvents-HookModifyLiquidity-bytes32-address-int128-int128-) +- [HookBonus(poolId, amount0, amount1)](#IHookEvents-HookBonus-bytes32-uint128-uint128-) +#### BaseHook [!toc] +#### IHooks [!toc] +
+
+ +
+

Errors

+
+#### BaseDynamicAfterFee [!toc] +#### IHookEvents [!toc] +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

_beforeSwap(address sender, struct PoolKey key, struct SwapParams params, bytes hookData) → bytes4, BeforeSwapDelta, uint24

+
+

internal

+# +
+
+
+ +Handles the before swap hook. + +For the first swap in a block, it saves the current pool state as a checkpoint. + +For subsequent swaps in the same block, it calculates a target output based on the beginning-of-block state, +and sets the inherited `_targetOutput` and `_applyTargetOutput` variables to enforce price limits in [`BaseHook._afterSwap`](/uniswap-hooks/api/base#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-). + +
+
+ + + +
+
+

_getBlockNumber() → uint48

+
+

internal

+# +
+
+
+ +Returns the current block number. + +
+
+ + + +
+
+

_getTargetUnspecified(address, struct PoolKey key, struct SwapParams params, bytes) → uint256 targetUnspecifiedAmount, bool applyTarget

+
+

internal

+# +
+
+
+ +Calculates the unspecified amount based on the pool state at the beginning of the block. +This prevents sandwich attacks by ensuring trades can't get better prices than what was available +at the start of the block. Note that the calculated unspecified amount could either be input or output, depending +if it's an exactInput or outputOutput swap. In cases of zeroForOne == true, the target unspecified amount is not +applicable, and the max uint256 value is returned as a flag only. + +The anti-sandwich mechanism works such as: + +- For currency0 to currency1 swaps (zeroForOne = true): The pool behaves normally with xy=k curve. +- For currency1 to currency0 swaps (zeroForOne = false): The price is fixed at the beginning-of-block + price, which prevents attackers from manipulating the price within a block. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hook permissions, specifically `beforeSwap`, `afterSwap`, and `afterSwapReturnDelta`. + +
+
+ + + +
+ +## `OrderIdLibrary` + + + + + +
+ +```solidity +import "uniswap-hooks/src/general/LimitOrderHook.sol"; +``` + +The order id library. + +
+

Functions

+
+- [equals(a, b)](#OrderIdLibrary-equals-OrderIdLibrary-OrderId-OrderIdLibrary-OrderId-) +- [unsafeIncrement(a)](#OrderIdLibrary-unsafeIncrement-OrderIdLibrary-OrderId-) +
+
+ + + +
+
+

equals(OrderIdLibrary.OrderId a, OrderIdLibrary.OrderId b) → bool

+
+

internal

+# +
+
+
+ +Compare two order ids for equality. Takes two `OrderId` values `a` and `b` and +returns whether their underlying values are equal. + +
+
+ + + +
+
+

unsafeIncrement(OrderIdLibrary.OrderId a) → OrderIdLibrary.OrderId

+
+

internal

+# +
+
+
+ +Increment the order id `a`. Might overflow. + +
+
+ + + +
+ +## `LimitOrderHook` + + + + + +
+ +```solidity +import "uniswap-hooks/src/general/LimitOrderHook.sol"; +``` + +Limit Order Mechanism hook. + +Allows users to place limit orders at specific ticks outside of the current price range, +which will be filled if the pool's price crosses the order's tick. + +Note that given the way UniswapV4 pools works, when liquidity is added out of the current range, +a single currency will be provided, instead of both currencies as in in-range liquidity additions. + +Orders can be cancelled at any time until they are filled and their liquidity is removed from the pool. +Once completely filled, the resulting liquidity can be withdrawn from the pool. + + +When cancelling or adding more liquidity into an existing order, it's possible that fees +have been accrued. In those cases, the accrued fees are added to the order info, benefitting the remaining +limit order placers. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v1.1.0_ + +
+

Functions

+
+- [constructor(_poolManager)](#LimitOrderHook-constructor-contract-IPoolManager-) +- [_afterInitialize(, key, , tick)](#LimitOrderHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterSwap(, key, params, , )](#LimitOrderHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [placeOrder(key, tick, zeroForOne, liquidity)](#LimitOrderHook-placeOrder-struct-PoolKey-int24-bool-uint128-) +- [cancelOrder(key, tickLower, zeroForOne, to)](#LimitOrderHook-cancelOrder-struct-PoolKey-int24-bool-address-) +- [withdraw(orderId, to)](#LimitOrderHook-withdraw-OrderIdLibrary-OrderId-address-) +- [unlockCallback(rawData)](#LimitOrderHook-unlockCallback-bytes-) +- [_handlePlaceCallback(placeData)](#LimitOrderHook-_handlePlaceCallback-struct-LimitOrderHook-PlaceCallbackData-) +- [_handleCancelCallback(cancelData)](#LimitOrderHook-_handleCancelCallback-struct-LimitOrderHook-CancelCallbackData-) +- [_handleWithdrawCallback(withdrawData)](#LimitOrderHook-_handleWithdrawCallback-struct-LimitOrderHook-WithdrawCallbackData-) +- [_fillOrder(key, tickLower, zeroForOne)](#LimitOrderHook-_fillOrder-struct-PoolKey-int24-bool-) +- [_getCrossedTicks(poolId, tickSpacing)](#LimitOrderHook-_getCrossedTicks-PoolId-int24-) +- [getTickLowerLast(poolId)](#LimitOrderHook-getTickLowerLast-PoolId-) +- [getOrderId(key, tickLower, zeroForOne)](#LimitOrderHook-getOrderId-struct-PoolKey-int24-bool-) +- [_getTickLower(tick, tickSpacing)](#LimitOrderHook-_getTickLower-int24-int24-) +- [getOrderLiquidity(orderId, owner)](#LimitOrderHook-getOrderLiquidity-OrderIdLibrary-OrderId-address-) +- [_getTick(poolId)](#LimitOrderHook-_getTick-PoolId-) +- [getOrderInfo(orderId)](#LimitOrderHook-getOrderInfo-OrderIdLibrary-OrderId-) +- [getHookPermissions()](#LimitOrderHook-getHookPermissions--) +#### IUnlockCallback [!toc] +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterAddLiquidity(, , , , , )](#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(, , , , , )](#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_beforeSwap(, , , )](#BaseHook-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Events

+
+- [Place(owner, orderId, key, tickLower, zeroForOne, liquidity)](#LimitOrderHook-Place-address-OrderIdLibrary-OrderId-struct-PoolKey-int24-bool-uint128-) +- [Fill(orderId, key, tickLower, zeroForOne)](#LimitOrderHook-Fill-OrderIdLibrary-OrderId-struct-PoolKey-int24-bool-) +- [Cancel(owner, orderId, key, tickLower, zeroForOne, liquidity)](#LimitOrderHook-Cancel-address-OrderIdLibrary-OrderId-struct-PoolKey-int24-bool-uint128-) +- [Withdraw(owner, orderId, liquidity)](#LimitOrderHook-Withdraw-address-OrderIdLibrary-OrderId-uint128-) +#### IUnlockCallback [!toc] +#### BaseHook [!toc] +#### IHooks [!toc] +
+
+ +
+

Errors

+
+- [ZeroLiquidity()](#LimitOrderHook-ZeroLiquidity--) +- [InRange()](#LimitOrderHook-InRange--) +- [CrossedRange()](#LimitOrderHook-CrossedRange--) +- [Filled()](#LimitOrderHook-Filled--) +- [NotFilled()](#LimitOrderHook-NotFilled--) +#### IUnlockCallback [!toc] +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager)

+
+

public

+# +
+
+
+ +Set the `PoolManager` address. + +
+
+ + + +
+
+

_afterInitialize(address, struct PoolKey key, uint160, int24 tick) → bytes4

+
+

internal

+# +
+
+
+ +Hooks into the `afterInitialize` hook to set the last tick lower for the pool. + +
+
+ + + +
+
+

_afterSwap(address, struct PoolKey key, struct SwapParams params, BalanceDelta, bytes) → bytes4, int128

+
+

internal

+# +
+
+
+ +Hooks into the `afterSwap` hook to get the ticks crossed by the swap and fill the orders that are crossed, filling them. + +
+
+ + + +
+
+

placeOrder(struct PoolKey key, int24 tick, bool zeroForOne, uint128 liquidity)

+
+

public

+# +
+
+
+ +Places a limit order by adding liquidity out of range at a specific tick. The order will be filled when the +pool price crosses the specified `tick`. Takes a `PoolKey` `key`, target `tick`, direction `zeroForOne` indicating +whether to buy currency0 or currency1, and amount of `liquidity` to place. The interaction with the `poolManager` is done +via the `unlock` function, which will trigger the [`BaseCustomAccounting.unlockCallback`](/uniswap-hooks/api/base#BaseCustomAccounting-unlockCallback-bytes-) function. + +
+
+ + + +
+
+

cancelOrder(struct PoolKey key, int24 tickLower, bool zeroForOne, address to)

+
+

public

+# +
+
+
+ +Cancels a limit order by removing liquidity from the pool. Takes a `PoolKey` `key`, `tickLower` of the order, +direction `zeroForOne` indicating whether it was buying currency0 or currency1, and recipient address `to` for the +removed liquidity. Note that partial cancellation is not supported - the entire liquidity added by the msg.sender will be removed. +Note also that cancelling an order will cancel the order placed by the msg.sender, not orders placed by other users in the same tick range. +The interaction with the `poolManager` is done via the `unlock` function, which will trigger the [`BaseCustomAccounting.unlockCallback`](/uniswap-hooks/api/base#BaseCustomAccounting-unlockCallback-bytes-) function. + +
+
+ + + +
+
+

withdraw(OrderIdLibrary.OrderId orderId, address to) → uint256 amount0, uint256 amount1

+
+

public

+# +
+
+
+ +Withdraws liquidity from a filled order, sending it to address `to`. Takes an `OrderId` `orderId` of the filled +order to withdraw from. Returns the withdrawn amounts as `(amount0, amount1)`. Can only be called after the order is +filled - use `cancelOrder` to remove liquidity from unfilled orders. The interaction with the `poolManager` is done via the +`unlock` function, which will trigger the [`BaseCustomAccounting.unlockCallback`](/uniswap-hooks/api/base#BaseCustomAccounting-unlockCallback-bytes-) function. + +
+
+ + + +
+
+

unlockCallback(bytes rawData) → bytes returnData

+
+

public

+# +
+
+
+ +Handles callbacks from the `PoolManager` for order operations. Takes encoded `rawData` containing the callback type +and operation-specific data. Returns encoded data containing fees accrued for cancel operations, or empty bytes +otherwise. Only callable by the PoolManager. + +
+
+ + + +
+
+

_handlePlaceCallback(struct LimitOrderHook.PlaceCallbackData placeData) → uint256 amount0Fee, uint256 amount1Fee

+
+

internal

+# +
+
+
+ +Internal handler for place order callbacks. Takes `placeData` containing the order details and adds the +specified liquidity to the pool out of range. Reverts if the order would be placed in range or on the wrong +side of the range. + +
+
+ + + +
+
+

_handleCancelCallback(struct LimitOrderHook.CancelCallbackData cancelData) → uint256 amount0Fee, uint256 amount1Fee

+
+

internal

+# +
+
+
+ +Internal handler for cancel order callbacks. Takes `cancelData` containing the cancellation details and +removes liquidity from the pool. Returns accrued fees `(amount0Fee, amount1Fee)` which are allocated to remaining +limit order placers, or to the cancelling user if they're removing all liquidity. + +
+
+ + + +
+
+

_handleWithdrawCallback(struct LimitOrderHook.WithdrawCallbackData withdrawData)

+
+

internal

+# +
+
+
+ +Internal handler for withdraw callbacks. Takes `withdrawData` containing withdrawal amounts and recipient, +burns the specified currency amounts from the hook, and transfers them to the recipient address. + +
+
+ + + +
+
+

_fillOrder(struct PoolKey key, int24 tickLower, bool zeroForOne)

+
+

internal

+# +
+
+
+ +Internal handler for filling limit orders when price crosses a tick. Takes a `PoolKey` `key`, target `tickLower`, +and direction `zeroForOne`. Removes liquidity from filled orders, mints the received currencies to the hook, and +updates order state to track filled amounts. + +
+
+ + + +
+
+

_getCrossedTicks(PoolId poolId, int24 tickSpacing) → int24 tickLower, int24 lower, int24 upper

+
+

internal

+# +
+
+
+ +Internal helper that calculates the range of ticks crossed during a price change. Takes a `PoolId` `poolId` +and `tickSpacing`, returns the current `tickLower` and the range of ticks crossed (`lower`, `upper`) that need +to be checked for limit orders. + +
+
+ + + +
+
+

getTickLowerLast(PoolId poolId) → int24

+
+

public

+# +
+
+
+ +Returns the last recorded lower tick for a given pool. Takes a `PoolId` `poolId` and returns the +stored `tickLowerLast` value. + +
+
+ + + +
+
+

getOrderId(struct PoolKey key, int24 tickLower, bool zeroForOne) → OrderIdLibrary.OrderId

+
+

public

+# +
+
+
+ +Retrieves the order id for a given pool position. Takes a `PoolKey` `key`, target `tickLower`, and direction +`zeroForOne` indicating whether it's buying currency0 or currency1. Returns the [`OrderIdLibrary.OrderId`](#OrderIdLibrary-OrderId) associated with this +position, or the default order id if no order exists. + +
+
+ + + +
+
+

_getTickLower(int24 tick, int24 tickSpacing) → int24

+
+

internal

+# +
+
+
+ +Get the tick lower. Takes a `tick` and `tickSpacing` and returns the nearest valid tick boundary +at or below the input tick, accounting for negative tick handling. + +
+
+ + + +
+
+

getOrderLiquidity(OrderIdLibrary.OrderId orderId, address owner) → uint256

+
+

external

+# +
+
+
+ +Get the liquidity of an order for a given order id and owner. Takes an [`OrderIdLibrary.OrderId`](#OrderIdLibrary-OrderId) `orderId` and `owner` address +and returns the amount of liquidity the owner has contributed to the order. + +
+
+ + + +
+
+

_getTick(PoolId poolId) → int24 tick

+
+

internal

+# +
+
+
+ +Get the current tick for a given pool. Takes a `PoolId` `poolId` and returns the tick calculated +from the pool's current sqrt price. + +
+
+ + + +
+
+

getOrderInfo(OrderIdLibrary.OrderId orderId) → bool filled, Currency currency0, Currency currency1, uint256 currency0Total, uint256 currency1Total, uint128 liquidityTotal

+
+

external

+# +
+
+
+ +Get the order info for a given order id. Takes an [`OrderIdLibrary.OrderId`](#OrderIdLibrary-OrderId) `orderId` and returns the order info. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Get the hook permissions for this contract. Returns a `Hooks.Permissions` struct configured to enable +`afterInitialize` and `afterSwap` hooks while disabling all other hooks. + +
+
+ + + +
+
+

Place(address indexed owner, OrderIdLibrary.OrderId indexed orderId, struct PoolKey key, int24 tickLower, bool zeroForOne, uint128 liquidity)

+
+

event

+# +
+
+ +
+ +Emitted when an `owner` places a limit order with the given `orderId`, in the pool identified by `key`, +at the given `tickLower`, `zeroForOne` indicating the direction of the order, and `liquidity` the amount of liquidity +added. + +
+
+ + +
+
+

Fill(OrderIdLibrary.OrderId indexed orderId, struct PoolKey key, int24 tickLower, bool zeroForOne)

+
+

event

+# +
+
+ +
+ +Emitted when a limit order with the given `orderId` is filled in the pool identified by `key`, +at the given `tickLower`, `zeroForOne` indicating the direction of the order. + +
+
+ + +
+
+

Cancel(address indexed owner, OrderIdLibrary.OrderId indexed orderId, struct PoolKey key, int24 tickLower, bool zeroForOne, uint128 liquidity)

+
+

event

+# +
+
+ +
+ +Emitted when an `owner` cancels a limit order with the given `orderId`, in the pool identified by `key`, +at the given `tickLower`, `zeroForOne` indicating the direction of the order, and `liquidity` the amount of liquidity +removed. + +
+
+ + +
+
+

Withdraw(address indexed owner, OrderIdLibrary.OrderId indexed orderId, uint128 liquidity)

+
+

event

+# +
+
+ +
+ +Emitted when an `owner` withdraws their `liquidity` from a limit order with the given `orderId`, in the pool identified by `key`, +at the given `tickLower`, `zeroForOne` indicating the direction of the order. + +
+
+ + + +
+
+

ZeroLiquidity()

+
+

error

+# +
+
+
+ +Zero liquidity was attempted to be added or removed. + +
+
+ + + +
+
+

InRange()

+
+

error

+# +
+
+
+ +Limit order was placed in range. + +
+
+ + + +
+
+

CrossedRange()

+
+

error

+# +
+
+
+ +Limit order placed on the wrong side of the range. + +
+
+ + + +
+
+

Filled()

+
+

error

+# +
+
+
+ +Limit order was already filled. + +
+
+ + + +
+
+

NotFilled()

+
+

error

+# +
+
+
+ +Limit order is not filled. + +
+
+ + + +
+ +## `LiquidityPenaltyHook` + + + + + +
+ +```solidity +import "uniswap-hooks/src/general/LiquidityPenaltyHook.sol"; +``` + +Just-in-Time (JIT) liquidity provisioning resistant hook. + +This hook disincentivizes JIT attacks by penalizing LP fee collection during [`BaseHook._afterRemoveLiquidity`](/uniswap-hooks/api/base#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-), +and disabling it during [`BaseHook._afterAddLiquidity`](/uniswap-hooks/api/base#BaseHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) if liquidity was recently added to the position. +The penalty is donated to the pool's liquidity providers in range at the time of removal. + +See [`LiquidityPenaltyHook._calculateLiquidityPenalty`](#LiquidityPenaltyHook-_calculateLiquidityPenalty-BalanceDelta-uint48-) for penalty calculation. + + +If a long term liquidity provider adds liquidity continuously, a pause of `blockNumberOffset` +before removing will be needed if `feesAccrued` collection is intended, in order to avoid getting +penalized by the JIT protection mechanism. + + + +Altrough this hook achieves it's objective of protecting long term LP's in most scenarios, +low liquidity pools and long-tail assets may still be vulnerable depending on the configured `blockNumberOffset`. +Larger values of such are recommended in those cases in order to decrease the profitability of the attack. + + + +In low liquidity pools, this hook may be vulnerable to multi-account strategies: attackers may bypass JIT protection +by using a secondary account to add minimal liquidity at a target tick with no other liquidity, then moving the price there after a JIT attack. +This allows penalty fees to be redirected to the attacker's secondary account. While technically feasible, this attack is rarely profitable in practice, +due to the cost associated with moving the price to the target tick. + + + +This is experimental software and is provided on an "as is" and "as available" basis. We do +not give any warranties and will not be liable for any losses incurred through any use of this code +base. + + +_Available since v0.1.1_ + +
+

Functions

+
+- [constructor(_poolManager, _blockNumberOffset)](#LiquidityPenaltyHook-constructor-contract-IPoolManager-uint48-) +- [_afterAddLiquidity(sender, key, params, , feeDelta, )](#LiquidityPenaltyHook-_afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_afterRemoveLiquidity(sender, key, params, , feeDelta, )](#LiquidityPenaltyHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [_getBlockNumber()](#LiquidityPenaltyHook-_getBlockNumber--) +- [_updateLastAddedLiquidityBlock(poolId, positionKey)](#LiquidityPenaltyHook-_updateLastAddedLiquidityBlock-PoolId-bytes32-) +- [_takeFeesToHook(key, positionKey, feeDelta)](#LiquidityPenaltyHook-_takeFeesToHook-struct-PoolKey-bytes32-BalanceDelta-) +- [_settleFeesFromHook(key, positionKey)](#LiquidityPenaltyHook-_settleFeesFromHook-struct-PoolKey-bytes32-) +- [_calculateLiquidityPenalty(feeDelta, lastAddedLiquidityBlock)](#LiquidityPenaltyHook-_calculateLiquidityPenalty-BalanceDelta-uint48-) +- [getLastAddedLiquidityBlock(poolId, positionKey)](#LiquidityPenaltyHook-getLastAddedLiquidityBlock-PoolId-bytes32-) +- [getWithheldFees(poolId, positionKey)](#LiquidityPenaltyHook-getWithheldFees-PoolId-bytes32-) +- [getHookPermissions()](#LiquidityPenaltyHook-getHookPermissions--) +- [MIN_BLOCK_NUMBER_OFFSET()](#LiquidityPenaltyHook-MIN_BLOCK_NUMBER_OFFSET-uint48) +- [blockNumberOffset()](#LiquidityPenaltyHook-blockNumberOffset-uint48) +#### BaseHook [!toc] +- [_validateHookAddress(hook)](#BaseHook-_validateHookAddress-contract-BaseHook-) +- [beforeInitialize(sender, key, sqrtPriceX96)](#BaseHook-beforeInitialize-address-struct-PoolKey-uint160-) +- [_beforeInitialize(, , )](#BaseHook-_beforeInitialize-address-struct-PoolKey-uint160-) +- [afterInitialize(sender, key, sqrtPriceX96, tick)](#BaseHook-afterInitialize-address-struct-PoolKey-uint160-int24-) +- [_afterInitialize(, , , )](#BaseHook-_afterInitialize-address-struct-PoolKey-uint160-int24-) +- [beforeAddLiquidity(sender, key, params, hookData)](#BaseHook-beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeAddLiquidity(, , , )](#BaseHook-_beforeAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [beforeRemoveLiquidity(sender, key, params, hookData)](#BaseHook-beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [_beforeRemoveLiquidity(, , , )](#BaseHook-_beforeRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-bytes-) +- [afterAddLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterAddLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [afterRemoveLiquidity(sender, key, params, delta0, delta1, hookData)](#BaseHook-afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) +- [beforeSwap(sender, key, params, hookData)](#BaseHook-beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [_beforeSwap(, , , )](#BaseHook-_beforeSwap-address-struct-PoolKey-struct-SwapParams-bytes-) +- [afterSwap(sender, key, params, delta, hookData)](#BaseHook-afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [_afterSwap(, , , , )](#BaseHook-_afterSwap-address-struct-PoolKey-struct-SwapParams-BalanceDelta-bytes-) +- [beforeDonate(sender, key, amount0, amount1, hookData)](#BaseHook-beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_beforeDonate(, , , , )](#BaseHook-_beforeDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [afterDonate(sender, key, amount0, amount1, hookData)](#BaseHook-afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [_afterDonate(, , , , )](#BaseHook-_afterDonate-address-struct-PoolKey-uint256-uint256-bytes-) +- [poolManager()](#BaseHook-poolManager-contract-IPoolManager) +#### IHooks [!toc] +
+
+ +
+

Errors

+
+- [BlockNumberOffsetTooLow()](#LiquidityPenaltyHook-BlockNumberOffsetTooLow--) +- [NoLiquidityToReceiveDonation()](#LiquidityPenaltyHook-NoLiquidityToReceiveDonation--) +#### BaseHook [!toc] +- [NotSelf()](#BaseHook-NotSelf--) +- [InvalidPool()](#BaseHook-InvalidPool--) +- [HookNotImplemented()](#BaseHook-HookNotImplemented--) +- [NotPoolManager()](#BaseHook-NotPoolManager--) +#### IHooks [!toc] +
+
+ + + +
+
+

constructor(contract IPoolManager _poolManager, uint48 _blockNumberOffset)

+
+

public

+# +
+
+
+ +Sets the `PoolManager` address and the `getBlockNumberOffset`. + +
+
+ + + +
+
+

_afterAddLiquidity(address sender, struct PoolKey key, struct ModifyLiquidityParams params, BalanceDelta, BalanceDelta feeDelta, bytes) → bytes4, BalanceDelta

+
+

internal

+# +
+
+
+ +Tracks `lastAddedLiquidityBlock` and withholds `feeDelta` if liquidity was recently added within +the `blockNumberOffset` period. + +See [`BaseHook._afterRemoveLiquidity`](/uniswap-hooks/api/base#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) for claiming the withheld fees back. + +
+
+ + + +
+
+

_afterRemoveLiquidity(address sender, struct PoolKey key, struct ModifyLiquidityParams params, BalanceDelta, BalanceDelta feeDelta, bytes) → bytes4, BalanceDelta

+
+

internal

+# +
+
+
+ +Penalizes the collection of any existing LP `feesDelta` and `withheldFees` after liquidity removal if +liquidity was recently added to the position. + + +The penalty is applied on both `withheldFees` and `feeDelta` equally. +Therefore, regardless of how many times liquidity was added to the position within the `blockNumberOffset` period, +all accrued fees are penalized as if the liquidity was added only once during that period. This ensures that +splitting liquidity additions within the `blockNumberOffset` period does not reduce or increase the penalty. + + + +The penalty is donated to the pool's liquidity providers in range at the time of liquidity removal, +which may be different from the liquidity providers in range at the time of liquidity addition. + + +
+
+ + + +
+
+

_getBlockNumber() → uint48

+
+

internal

+# +
+
+
+ +Returns the current block number. + +
+
+ + + +
+
+

_updateLastAddedLiquidityBlock(PoolId poolId, bytes32 positionKey)

+
+

internal

+# +
+
+
+ +Updates the `lastAddedLiquidityBlock` for a liquidity position. + +
+
+ + + +
+
+

_takeFeesToHook(struct PoolKey key, bytes32 positionKey, BalanceDelta feeDelta)

+
+

internal

+# +
+
+
+ +Takes `feeDelta` from a liquidity position as `withheldFees` into this hook. + +
+
+ + + +
+
+

_settleFeesFromHook(struct PoolKey key, bytes32 positionKey) → BalanceDelta withheldFees

+
+

internal

+# +
+
+
+ +Returns `withheldFees` from this hook to the liquidity provider. + +
+
+ + + +
+
+

_calculateLiquidityPenalty(BalanceDelta feeDelta, uint48 lastAddedLiquidityBlock) → BalanceDelta liquidityPenalty

+
+

internal

+# +
+
+
+ +Calculates the penalty to be applied to JIT liquidity provisioning. + +The penalty is calculated as a linear function of the block number difference between the `lastAddedLiquidityBlock` and the `currentBlockNumber`. + +The used formula is: + +liquidityPenalty = feeDelta * ( 1 - (currentBlockNumber - lastAddedLiquidityBlock) / blockNumberOffset) + +As a result, the penalty is 100% at the same block where liquidity was last added and zero after the `blockNumberOffset` block time window. + + +Won't overflow if `currentBlockNumber - lastAddedLiquidityBlock < blockNumberOffset` is verified prior to calling this function. + + +
+
+ + + +
+
+

getLastAddedLiquidityBlock(PoolId poolId, bytes32 positionKey) → uint48

+
+

public

+# +
+
+
+ +Tracks the `lastAddedLiquidityBlock` for a liquidity position. + +`lastAddedLiquidityBlock` is the block number when liquidity was last added to the position. + +
+
+ + + +
+
+

getWithheldFees(PoolId poolId, bytes32 positionKey) → BalanceDelta

+
+

public

+# +
+
+
+ +Returns the `withheldFees` for a liquidity position. + +`withheldFees` are UniswapV4's `feesAccrued` retained by this hook during liquidity addition if liquidity +has been recently added within the `blockNumberOffset` block time window, with the purpose of disabling fee +collection during JIT liquidity provisioning attacks. See [`BaseHook._afterRemoveLiquidity`](/uniswap-hooks/api/base#BaseHook-_afterRemoveLiquidity-address-struct-PoolKey-struct-ModifyLiquidityParams-BalanceDelta-BalanceDelta-bytes-) for claiming the fees back. + +
+
+ + + +
+
+

getHookPermissions() → struct Hooks.Permissions permissions

+
+

public

+# +
+
+
+ +Set the hooks permissions, specifically `afterAddLiquidity`, `afterAddLiquidityReturnDelta`, `afterRemoveLiquidity` and `afterRemoveLiquidityReturnDelta`. + +
+
+ + + +
+
+

MIN_BLOCK_NUMBER_OFFSET() → uint48

+
+

public

+# +
+
+
+ +The minimum value for the [`LiquidityPenaltyHook.blockNumberOffset`](#LiquidityPenaltyHook-blockNumberOffset-uint48). + +
+
+ + + +
+
+

blockNumberOffset() → uint48

+
+

public

+# +
+
+
+ +The minimum time window (in blocks) that must pass after adding liquidity before it can be +removed without any penalty. During this period, JIT attacks are deterred through fee withholding +and penalties. Higher values provide stronger JIT protection but may discourage legitimate LPs. + +
+
+ + + +
+
+

BlockNumberOffsetTooLow()

+
+

error

+# +
+
+
+ +The hook was attempted to be constructed with a `blockNumberOffset` lower than `MIN_BLOCK_NUMBER_OFFSET`. + +
+
+ + + +
+
+

NoLiquidityToReceiveDonation()

+
+

error

+# +
+
+
+ +A penalty was attempted to be applied and donated to LP's in range, but there aren't any. + +
+
diff --git a/docs/content/uniswap-hooks/api/index.mdx b/docs/content/uniswap-hooks/api/index.mdx new file mode 100644 index 00000000..aae2e5ac --- /dev/null +++ b/docs/content/uniswap-hooks/api/index.mdx @@ -0,0 +1,37 @@ +--- +title: API Reference +--- + +# API Reference + +## Base Implementations + +- **[Base](./api/base)** - Core hook base implementations and building blocks + - `BaseAsyncSwap` - Base implementation for async swaps + - `BaseCustomAccounting` - Base implementation for hook-owned liquidity + - `BaseCustomCurve` - Base implementation for custom curves + - `BaseHook` - Base hook implementation with security and permission helpers + +## Fee Management + +- **[Fee](./api/fee)** - Fee utilities and dynamic fee implementations + - `BaseDynamicAfterFee` - Dynamic target hook fees applied after swaps + - `BaseDynamicFee` - Dynamic fee application via PoolManager + - `BaseOverrideFee` - Automatic dynamic fees applied before swaps + +## General Purpose Hooks + +- **[General](./api/general)** - Ready-to-use hooks for common use cases + - `AntiSandwichHook` - Sandwich-resistant AMM implementation + - `LimitOrderHook` - Limit order mechanism for pools + - `LiquidityPenaltyHook` - Just-in-time liquidity protection + +## Interfaces + +- **[Interfaces](./api/interfaces)** - Standard interfaces and contract definitions + - `IHookEvents` - Standard hook events interface + +## Utilities + +- **[Utils](./api/utils)** - General utilities and helper libraries + - `CurrencySettler` - Library for settling PoolManager deltas diff --git a/docs/content/uniswap-hooks/api/interfaces.mdx b/docs/content/uniswap-hooks/api/interfaces.mdx new file mode 100644 index 00000000..b7f5a8c2 --- /dev/null +++ b/docs/content/uniswap-hooks/api/interfaces.mdx @@ -0,0 +1,110 @@ +--- +title: "Interfaces" +description: "Smart contract interfaces utilities and implementations" +--- + + + +
+ +## `IHookEvents` + + + + + +
+ +```solidity +import "uniswap-hooks/src/interfaces/IHookEvents.sol"; +``` + +Interface for standard hook events emission. + + +Hooks should inherit from this interface to standardized event emission. + + +
+

Events

+
+- [HookSwap(poolId, sender, amount0, amount1, hookLPfeeAmount0, hookLPfeeAmount1)](#IHookEvents-HookSwap-bytes32-address-int128-int128-uint128-uint128-) +- [HookFee(poolId, sender, feeAmount0, feeAmount1)](#IHookEvents-HookFee-bytes32-address-uint128-uint128-) +- [HookModifyLiquidity(poolId, sender, amount0, amount1)](#IHookEvents-HookModifyLiquidity-bytes32-address-int128-int128-) +- [HookBonus(poolId, amount0, amount1)](#IHookEvents-HookBonus-bytes32-uint128-uint128-) +
+
+ + + +
+
+

HookSwap(bytes32 indexed poolId, address indexed sender, int128 amount0, int128 amount1, uint128 hookLPfeeAmount0, uint128 hookLPfeeAmount1)

+
+

event

+# +
+
+ +
+ +Emitted when a hook executes a swap outside of Uniswap's default concentrated liquidity AMM in a pool +identified by `poolId`, being `sender` the initiator of the swap, `amount0` and `amount1` the swap amounts +(positive for input, negative for output), and `hookLPfeeAmount0`, `hookLPfeeAmount1` the LP fees. + +
+
+ + +
+
+

HookFee(bytes32 indexed poolId, address indexed sender, uint128 feeAmount0, uint128 feeAmount1)

+
+

event

+# +
+
+ +
+ +Emitted when a hook charges fees in a pool identified by `poolId`, being `sender` the initiator of the swap or +the liquidity modifier, `feeAmount0` and `feeAmount1` the fees charged in currency0 and currency1, defined by the `poolId`. + +
+
+ + +
+
+

HookModifyLiquidity(bytes32 indexed poolId, address indexed sender, int128 amount0, int128 amount1)

+
+

event

+# +
+
+ +
+ +Emitted when a liquidity modification is executed in a pool identified by `poolId`, being `sender` the liquidity modifier, +`amount0` and `amount1` the amounts added or removed in currency0 and currency1, defined by the `poolId`. + +
+
+ + +
+
+

HookBonus(bytes32 indexed poolId, uint128 amount0, uint128 amount1)

+
+

event

+# +
+
+ +
+ +Emitted when a bonus is added to an operation in a pool identified by `poolId`, being `amount0` and `amount1` the amounts +added in currency0 and currency1, defined by the `poolId`. + +
+
diff --git a/docs/content/uniswap-hooks/api/utils.mdx b/docs/content/uniswap-hooks/api/utils.mdx new file mode 100644 index 00000000..2e991cff --- /dev/null +++ b/docs/content/uniswap-hooks/api/utils.mdx @@ -0,0 +1,68 @@ +--- +title: "Utils" +description: "Smart contract utils utilities and implementations" +--- + + + +
+ +## `CurrencySettler` + + + + + +
+ +```solidity +import "uniswap-hooks/src/utils/CurrencySettler.sol"; +``` + +Library used to interact with the `PoolManager` to settle any open deltas. +To settle a positive delta (a credit to the user), a user may take or mint. +To settle a negative delta (a debt on the user), a user may transfer or burn to pay off a debt. + +Based on the [Uniswap v4 test utils implementation](https://github.com/Uniswap/v4-core/blob/main/test/utils/CurrencySettler.sol). + + +Deltas are synced before any ERC-20 transfers in [`CurrencySettler.settle`](#CurrencySettler-settle-Currency-contract-IPoolManager-address-uint256-bool-) function. + + +
+

Functions

+
+- [settle(currency, poolManager, payer, amount, burn)](#CurrencySettler-settle-Currency-contract-IPoolManager-address-uint256-bool-) +- [take(currency, poolManager, recipient, amount, claims)](#CurrencySettler-take-Currency-contract-IPoolManager-address-uint256-bool-) +
+
+ + + +
+
+

settle(Currency currency, contract IPoolManager poolManager, address payer, uint256 amount, bool burn)

+
+

internal

+# +
+
+
+ +
+
+ + + +
+
+

take(Currency currency, contract IPoolManager poolManager, address recipient, uint256 amount, bool claims)

+
+

internal

+# +
+
+
+ +
+
diff --git a/docs/content/uniswap-hooks/base.mdx b/docs/content/uniswap-hooks/base.mdx new file mode 100644 index 00000000..70b9bdeb --- /dev/null +++ b/docs/content/uniswap-hooks/base.mdx @@ -0,0 +1,167 @@ +--- +title: Base +--- + +Base contract implementations are provided in the library as building blocks to leverage Uniswap v4’s features natively, such as custom accounting, custom curves, and asynchronous swaps. + +## Hook + +[BaseHook](/uniswap-hooks/api/base#BaseHook) is provided as the underlying scaffolding contract. It declares every supported hook callback along with modifiers and revert statements that enforce security and prevent misuse. By design, all hook entrypoints/actions are turned off. This allows the inheriting contract to choose which methods to enable by overriding the permissions struct in [`getHookPermissions`](/uniswap-hooks/api/base#BaseHook-getHookPermissions--) and implementing the respective internal functions. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import BaseHook from "src/base/BaseHook.sol"; +import Hooks from "v4-core/src/libraries/Hooks.sol"; +import PoolKey from "v4-core/src/types/PoolKey.sol"; +import IPoolManager from "v4-core/src/interfaces/IPoolManager.sol"; +import BeforeSwapDelta, toBeforeSwapDelta from "v4-core/src/types/BeforeSwapDelta.sol"; + +contract CounterHook is BaseHook { + uint256 public counter; + + constructor(IPoolManager _poolManager) BaseHook(_poolManager) {} + + /** + * @inheritdoc BaseHook + */ + function _beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + internal + virtual + override + returns (bytes4, BeforeSwapDelta, uint24) + { + counter++; + return (this.beforeSwap.selector, toBeforeSwapDelta(0, 0), 0); + } + + + /** + * @inheritdoc BaseHook + */ + function getHookPermissions() public pure override returns (Hooks.Permissions memory permissions) { + return Hooks.Permissions({ + beforeInitialize: false, + afterInitialize: false, + beforeAddLiquidity: false, + beforeRemoveLiquidity: false, + afterAddLiquidity: false, + afterRemoveLiquidity: false, + beforeSwap: true, + afterSwap: false, + beforeDonate: false, + afterDonate: false, + beforeSwapReturnDelta: false, + afterSwapReturnDelta: false, + afterAddLiquidityReturnDelta: false, + afterRemoveLiquidityReturnDelta: false + }); + } +} +``` + +Among the security checks enforced by [BaseHook](/uniswap-hooks/api/base#BaseHook), [`validateHookAddress`](/uniswap-hooks/api/base#BaseHook-validateHookAddress-contract-BaseHook-) ensures that the contract address matches the declared permissions. + +## Custom Accounting + +[BaseCustomAccounting](/uniswap-hooks/api/base#BaseCustomAccounting) inherits from [BaseHook](/uniswap-hooks/api/base#BaseHook) to enforce hook-owned liquidity and allow for custom token accounting for a specific pool. Liquidity modifications (addition/removal) are handled directly by the hook contract and then apply them to the pool via the `PoolManager`. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; +import {BaseCustomAccounting} from "src/base/BaseCustomAccounting.sol"; +import {ERC20} from "openzeppelin/token/ERC20/ERC20.sol"; +import {FullMath} from "v4-core/src/libraries/FullMath.sol"; +import {TickMath} from "v4-core/src/libraries/TickMath.sol"; +import {LiquidityAmounts} from "v4-periphery/src/libraries/LiquidityAmounts.sol"; +import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; +import {SafeCast} from "v4-core/src/libraries/SafeCast.sol"; +import {StateLibrary} from "v4-core/src/libraries/StateLibrary.sol"; + +contract SimpleAccounting is BaseCustomAccounting, ERC20 { + using SafeCast for uint256; + using StateLibrary for IPoolManager; + + constructor(IPoolManager _poolManager) BaseCustomAccounting(_poolManager) ERC20("Mock", "MOCK") {} + + /// @inheritdoc BaseCustomAccounting + function _getAddLiquidity(uint160 sqrtPriceX96, AddLiquidityParams memory params) + internal + pure + override + returns (bytes memory modify, uint256 liquidity) + { + + liquidity = LiquidityAmounts.getLiquidityForAmounts( + sqrtPriceX96, + TickMath.getSqrtPriceAtTick(params.tickLower), + TickMath.getSqrtPriceAtTick(params.tickUpper), + params.amount0Desired, + params.amount1Desired + ); + + return ( + abi.encode( + IPoolManager.ModifyLiquidityParams({ + tickLower: params.tickLower, + tickUpper: params.tickUpper, + liquidityDelta: liquidity.toInt256(), + salt: 0 + }) + ), + liquidity + ); + } + + /// @inheritdoc BaseCustomAccounting + function _getRemoveLiquidity(RemoveLiquidityParams memory params) + internal + view + override + returns (bytes memory, uint256 liquidity) + { + + liquidity = FullMath.mulDiv(params.liquidity, poolManager.getLiquidity(poolKey.toId()), totalSupply()); + + return ( + abi.encode( + IPoolManager.ModifyLiquidityParams({ + tickLower: params.tickLower, + tickUpper: params.tickUpper, + liquidityDelta: -liquidity.toInt256(), + salt: 0 + }) + ), + liquidity + ); + } + + /// @inheritdoc BaseCustomAccounting + function _mint(AddLiquidityParams memory params, BalanceDelta, uint256 liquidity) internal override { + _mint(params.to, liquidity); + } + + /// @inheritdoc BaseCustomAccounting + function _burn(RemoveLiquidityParams memory, BalanceDelta, uint256 liquidity) internal override { + _burn(msg.sender, liquidity); + } + +} +``` + +The inheriting contracts must implement the respective functions to calculate the liquidity modification parameters and the amount of liquidity shares to mint or burn. Additionally, the implementer must keep in mind that the hook is the sole liquidity owner and is therefore responsible for managing fees on any liquidity shares. + +## Custom Curve + +Building on the custom accounting foundation, [BaseCustomCurve](/uniswap-hooks/api/base#BaseCustomCurve) takes customization a step further by allowing developers to completely replace Uniswap v4’s default concentrated liquidity math with their own swap logic. + +By overriding the [`_beforeSwap`](/uniswap-hooks/api/base#BaseHook-_beforeSwap-address-struct-PoolKey-struct-IPoolManager-SwapParams-bytes-) function, the inheriting contract can implement its own swap logic and curves. Because the hook still owns the liquidity, it can route tokens around in ways that diverge from the standard invariant, perhaps adopting stable-swap curves, bonding curves, or other designs that better suit specialized use cases. The contract also redefines how liquidity additions and removals occur internally, but it does so in a manner that remains compatible with the rest of the Uniswap v4 engine’s architecture and routers. + +## Async Swap + +[BaseAsyncSwap](/uniswap-hooks/api/base#BaseAsyncSwap) offers a way to skip the execution of exact-input swaps by the `PoolManager` in order to support asynchronous swaps and other cases that require non-atomic execution. + +When processing exact-input swaps, the hook returns a delta that nets out the input amount to zero, then mints ERC-6909 tokens to the contract’s address. This approach effectively bypasses the standard swap logic and allows the hook to manage user positions or tokens until a final settlement stage. The user’s input tokens are held by the hook contract, which can later be redeemed or settled according to logic defined by the implementer. diff --git a/docs/content/uniswap-hooks/changelog.mdx b/docs/content/uniswap-hooks/changelog.mdx new file mode 100644 index 00000000..3c2c3ff1 --- /dev/null +++ b/docs/content/uniswap-hooks/changelog.mdx @@ -0,0 +1,68 @@ +--- +title: Changelog +--- + + +# [v1.1.0](https://github.com/OpenZeppelin/uniswap-hooks/releases/tag/v1.1.0) - 2025-07-11 + +### Changes by Category + +## Base + +* `BaseAsyncSwap`: Add interface `IHookEvents` to the contract in order to standardize event emissions in hooks. Add `_calculateSwapFee` function to calculate the swap fee amount in an asynchronous swap. ([#47](https://github.com/OpenZeppelin/uniswap-hooks/issues/47)) +* `BaseCustomAccounting`: Add interface `IHookEvents` to the contract in order to standardize event emissions in hooks ([#47](https://github.com/OpenZeppelin/uniswap-hooks/issues/47)). +* `BaseCustomCurve`: Add function `_getSwapFeeAmount` to calculate the amount of fees paid to LPs. ([#47](https://github.com/OpenZeppelin/uniswap-hooks/issues/47)) +* `BaseHook`: Update imports from `v4-core` ([#66](https://github.com/OpenZeppelin/uniswap-hooks/issues/66)) + +## Fee + +* `BaseDynamicAfterFee`: Add interface `IHookEvents` to the contract in order to standardize event emissions in hooks ([#47](https://github.com/OpenZeppelin/uniswap-hooks/issues/47)). Add functions `_transientTargetUnspecifiedAmount`, `_transientApplyTarget`, `_setTransientTargetUnspecifiedAmount` and `_setTransientApplyTarget` to handle the target unspecified and the apply flag using transient storage. Rename `_getTargetOutput` to `_getTargetUnspecified` and handle both exact input and exact output cases for targets. ([#86](https://github.com/OpenZeppelin/uniswap-hooks/issues/86)) +* `BaseDynamicFee`: Update imports from `v4-core` ([#66](https://github.com/OpenZeppelin/uniswap-hooks/issues/66)) +* `BaseOverrideFee`: Update imports from `v4-core` ([#66](https://github.com/OpenZeppelin/uniswap-hooks/issues/66)) + +## General +* `AntiSandwichHook`: Implementation of a sandwich-resistant pool using hooks. Specifically, it guarantees that no swaps get filled at a price better than the price at the beginning of the block. ([#80](https://github.com/OpenZeppelin/uniswap-hooks/issues/80)) +* `LimitOrderHook`: Hook to enable limit order placing on liquidity pools. The orders are placed as out-of-range liquidity increases. The hook allows for cancelling orders and withdrawing tokens after the orders are fulfilled. ([#77](https://github.com/OpenZeppelin/uniswap-hooks/issues/77)) +* `LiquidityPenaltyHook`: Hook that provides Just-in-time protection to pools. It penalizes LP fee collection during `_afterRemoveLiquidity` and disables it during `_afterAddLiquidity` if liquidity was recently added to the position. ([#67](https://github.com/OpenZeppelin/uniswap-hooks/issues/67)) + +## Interfaces +* `IHookEvents`: Interface created in order to standardize event emissions in hooks ([#47](https://github.com/OpenZeppelin/uniswap-hooks/issues/47)) + +## Utilities + +* `CurrencySettler`: Update to use `SafeERC20` ([#49](https://github.com/OpenZeppelin/uniswap-hooks/issues/49)) + +[Changes][v1.1.0] + + + +# [v1.0.0](https://github.com/OpenZeppelin/uniswap-hooks/releases/tag/v1.0.0) - 2025-02-24 + +## Base + +Base contracts for building secure and modular Uniswap hooks, providing core functionality and common patterns for hook development. + +* `BaseCustomAccounting`: Base hook implementation for custom accounting, including support for swaps and liquidity management. +* `BaseCustomCurve`: Base hook implementation for custom curves. +* `BaseHook`: Base implementation for hooks. +* `BaseAsyncSwap`: Base hook implementation for asynchronous swaps. + +## Fee + +Hooks for managing and customizing pool fees, including dynamic fee adjustments, fee overrides, and post-swap fee calculations. + +* `BaseDynamicFee`: Hook to apply a manual dynamic fee via the Uniswap PoolManager contract. +* `BaseOverrideFee`: Hook that overrides and applies a fee before swapping automatically. +* `BaseDynamicAfterFee`: Hook that applies a fee based on a delta after swapping. + +## Utilities + +Libraries and general purpose utilities to help develop hooks. + +* `CurrencySettler`: Library used to interact with the PoolManager to settle any open deltas, with support for ERC-6909 and native currencies. + +[Changes][v1.0.0] + + +[v1.1.0]: https://github.com/OpenZeppelin/uniswap-hooks/compare/v1.0.0...v1.1.0 +[v1.0.0]: https://github.com/OpenZeppelin/uniswap-hooks/tree/v1.0.0 diff --git a/docs/content/uniswap-hooks/fee.mdx b/docs/content/uniswap-hooks/fee.mdx new file mode 100644 index 00000000..76fd64b5 --- /dev/null +++ b/docs/content/uniswap-hooks/fee.mdx @@ -0,0 +1,92 @@ +--- +title: Fee +--- + +Fee-related base hooks are provided in the library to allow for flexible ways to adjust or override fees for different actions, like swaps and liquidity modifications. + +## Dynamic + +[BaseDynamicFee](/uniswap-hooks/api/fee#BaseDynamicFee) allows for dynamic setting and application of LP fees. Implementers must override the [`_getFee`](/uniswap-hooks/api/fee#BaseDynamicFee-_getFee-struct-PoolKey-) function to return a fee value expressed in hundredths of a bip, based on their chosen logic. The fee is automatically applied after initialization, and can be refreshed at any time by calling the permissionless [`poke`](/uniswap-hooks/api/fee#BaseDynamicFee-poke-struct-PoolKey-) function. This allows the fee to be dynamically updated based on external data or conditions. However, since [`poke`](/uniswap-hooks/api/fee#BaseDynamicFee-poke-struct-PoolKey-) can be called by anyone, implementers must carefully consider whether their [`_getFee`](/uniswap-hooks/api/fee#BaseDynamicFee-_getFee-struct-PoolKey-) implementation relies on external states that could be manipulated by adversaries. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import BaseDynamicFee, IPoolManager, PoolKey from "src/fee/BaseDynamicFee.sol"; +import Ownable from "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @dev A hook that allows the owner to dynamically update the LP fee. + */ +contract DynamicLPFeeHook is BaseDynamicFee, Ownable { + uint24 public fee; + + constructor(IPoolManager _poolManager) BaseDynamicFee(_poolManager) Ownable(msg.sender) {} + + /** + * @inheritdoc BaseDynamicFee + */ + function _getFee(PoolKey calldata) internal view override returns (uint24) { + return fee; + } + + /** + * @notice Sets the LP fee, denominated in hundredths of a bip. + */ + function setFee(uint24 _fee) external onlyOwner { + fee = _fee; + } + +} +``` + +The constructor checks if the pool is configured with the dynamic fee flag and reverts if not. + +## Override + +[BaseOverrideFee](/uniswap-hooks/api/fee#BaseOverrideFee) allows for dynamic setting and application of swap fees. Similar to [BaseDynamicFee](/uniswap-hooks/api/fee#BaseDynamicFee), implementers must override the [`_getFee`](/uniswap-hooks/api/fee#BaseOverrideFee-_getFee-struct-PoolKey-) function to return a fee value, which is masked with the override fee flag and passed to the `PoolManager` before a swap. + +This approach can be useful for time-based, volume-based, or volatility-based fees where the fee may fluctuate frequently. Because the hook runs before the swap is executed, an implementer can examine the current context (like liquidity levels or external price oracles) to decide the appropriate fee for each trade. It also doesn’t require poking the hook to refresh the fee, as the fee is dynamically fetched before each swap. + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import BaseOverrideFee, IPoolManager, PoolKey from "src/fee/BaseOverrideFee.sol"; +import Ownable from "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @dev A hook that allows the owner to dynamically update the swap fee. + */ +contract DynamicSwapFeeHook is BaseOverrideFee, Ownable { + uint24 public fee; + + constructor(IPoolManager _poolManager) BaseOverrideFee(_poolManager) Ownable(msg.sender) {} + + /** + * @inheritdoc BaseOverrideFee + */ + function _getFee(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata) + internal + view + override + returns (uint24) + { + return fee; + } + + /** + * @notice Sets the swap fee, denominated in hundredths of a bip. + */ + function setFee(uint24 _fee) external onlyOwner { + fee = _fee; + } + +} +``` + +## After Swap + +[BaseDynamicAfterFee](/uniswap-hooks/api/fee#BaseDynamicAfterFee) applies adjustments to the tokens to be received by a user for exact-input swaps. This strategy relies on first capturing the swap context in the [`_beforeSwap`](/uniswap-hooks/api/base#BaseHook-_beforeSwap-address-struct-PoolKey-struct-IPoolManager-SwapParams-bytes-) phase and storing a target delta. Once the swap is processed by the `PoolManager`, the hook’s [`_afterSwap`](/uniswap-hooks/api/base#BaseHook-_afterSwap-address-struct-PoolKey-struct-IPoolManager-SwapParams-BalanceDelta-bytes-) method checks for exact-input swaps and compares the actual user output with the stored target delta. Any positive difference becomes a fee donation to the pool, effectively implementing a dynamic fee that is only finalized once all of the swap’s internal calculations are done. + +Implementers should carefully consider how to mitigate the risk of attackers exploiting "just-in-time" liquidity additions to gain an outsized share of these fees. As the contract notes, the target deltas are cleared after each swap, so it is recommended to define or reset them each time in [`_beforeSwap`](/uniswap-hooks/api/base#BaseHook-_beforeSwap-address-struct-PoolKey-struct-IPoolManager-SwapParams-bytes-) to ensure consistency. diff --git a/docs/content/uniswap-hooks/index.mdx b/docs/content/uniswap-hooks/index.mdx new file mode 100644 index 00000000..1aeab87f --- /dev/null +++ b/docs/content/uniswap-hooks/index.mdx @@ -0,0 +1,75 @@ +--- +title: Uniswap Hooks +--- + +A [Solidity library](https://github.com/OpenZeppelin/uniswap-hooks) for secure and modular hooks for [Uniswap v4](https://docs.uniswap.org/contracts/v4/overview). This library includes: + +* Base implementations for custom accounting, asynchronous swaps, and custom curves +* Fee-related implementations for management and enforcement +* Ready-to-use hooks for general use cases, like sandwich protection +* Utilities and libraries for hook development + +## Overview + +### Installation + +The library can only be installed with Foundry using gitmodules for now. Support for Hardhat is coming soon. + +#### Foundry (git) + +```console +$ forge install OpenZeppelin/uniswap-hooks +``` + + +Make sure to add `@openzeppelin/uniswap-hooks/=lib/uniswap-hooks/src/` in `remappings.txt`. + + +### Usage + +Once installed, you can use the contracts in the library by importing them: + +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import {BaseDynamicFee, IPoolManager, PoolKey} from "src/fee/BaseDynamicFee.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +/** + * @dev A hook that allows the owner to dynamically update the LP fee. + */ +contract DynamicLPFeeHook is BaseDynamicFee, Ownable { + uint24 public fee; + + constructor(IPoolManager _poolManager) BaseDynamicFee(_poolManager) Ownable(msg.sender) { + } + + /** + * @inheritdoc BaseDynamicFee + */ + function _getFee(PoolKey calldata) internal view override returns (uint24) { + return fee; + } + + /** + * @notice Sets the LP fee, denominated in hundredths of a bip. + */ + function setFee(uint24 _fee) external onlyOwner { + fee = _fee; + } +} +``` + +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. + +### Videos +In order to facilitate understanding of Uniswap Hooks and help start building with them, we’ve released this playlist of guidelines on our YouTube channel. + + + +## Security + +Contracts in the hooks library are provided as is, with no particular guarantees, including backward compatibility. + +We kindly ask to report any issue directly to our security [contact](mailto:security@openzeppelin.org). The team will do its best to assist and mitigate any potential misuses of the library. diff --git a/docs/content/uniswap-hooks/utilities.mdx b/docs/content/uniswap-hooks/utilities.mdx new file mode 100644 index 00000000..c042133c --- /dev/null +++ b/docs/content/uniswap-hooks/utilities.mdx @@ -0,0 +1,49 @@ +--- +title: Utilities +--- + +Libraries and general purpose utilities are included in the library to help develop hooks. For technical details, refer to the [API Reference](/uniswap-hooks/api/utils). + +## Currency Settler + +Uniswap v4 introduces a specialized `Currency` type to handle both native ETH and ERC-20 tokens through a unified interface. This abstraction streamlines logic for transfers and balance checks, especially when combined with ephemeral “deltas” for each liquidity event or swap. A delta is simply the net difference that a position or user must either pay in or receive from the Uniswap `PoolManager` once all operations have completed. + +When tokens remain in the `PoolManager`, Uniswap v4 can seamlessly represent them as ERC-6909 tokens, enabling internal accounting without external transfers. Positive deltas (credits) can be redeemed by “taking” or minting ERC-6909 tokens, and negative deltas (debts) can be settled by “paying” or burning those tokens. + +The [`CurrencySettler`](/uniswap-hooks/api/utils#CurrencySettler) library provides easy-to-use utilities for modifying and closing these deltas. Based on the inputs, the functions determine whether to sync, transfer, or settle native assets, ERC-20 tokens, or ERC-6909 tokens. This removes the need to manually reconcile token balances or worry about the correct sequence of operations: + +```solidity +... + + /** + * @dev Skip the v3-like swap implementation of the `PoolManager` by returning a delta that nets out the + * specified amount to 0 to enable asynchronous swaps. + */ + function _beforeSwap(address, PoolKey calldata key, IPoolManager.SwapParams calldata params, bytes calldata) + internal + virtual + override + returns (bytes4, BeforeSwapDelta, uint24) + { + // Async swaps are only possible on exact-input swaps, so exact-output swaps are executed by the `PoolManager` as normal + if (params.amountSpecified < 0) { + // Determine which currency is specified + Currency specified = params.zeroForOne ? key.currency0 : key.currency1; + + // Get the positive specified amount + uint256 specifiedAmount = uint256(-params.amountSpecified); + + // Mint ERC-6909 claim token for the specified currency and amount + specified.take(poolManager, address(this), specifiedAmount, true); + + // Return delta that nets out specified amount to 0. + return (this.beforeSwap.selector, toBeforeSwapDelta(specifiedAmount.toInt128(), 0), 0); + } else { + return (this.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); + } + } + +... +``` + +The [`settle`](/uniswap-hooks/api/utils#CurrencySettler-settle-Currency-contract-IPoolManager-address-uint256-bool-) function is used to pay into the `PoolManager`, handling both ERC-20 transfers and ERC-6909 burns. On the other hand, the [`take`](/uniswap-hooks/api/utils#CurrencySettler-take-Currency-contract-IPoolManager-address-uint256-bool-) function allows you to receive a positive delta by either minting ERC-6909 tokens or transferring an ERC-20 token from the `PoolManager`. diff --git a/docs/content/upgrades-plugins/api-core.mdx b/docs/content/upgrades-plugins/api-core.mdx new file mode 100644 index 00000000..21231b84 --- /dev/null +++ b/docs/content/upgrades-plugins/api-core.mdx @@ -0,0 +1,311 @@ +--- +title: OpenZeppelin Upgrades Core & CLI +--- + +The `@openzeppelin/upgrades-core` package provides a `validate` command to check for upgrade safety and storage layout compatibility in upgradeable contracts. It can be used throughout your development process to ensure that your contracts are upgrade safe and compatible with previous versions. + +It also provides APIs to perform these checks programmatically, and contains the core logic for these checks to be performed with the OpenZeppelin Upgrades plugins. + +## CLI: Validate Command + +Detects upgradeable contracts from a directory containing build info files and validates whether they are upgrade safe. Use this if you want to validate all of your project's upgradeable contracts from the command line, in a script, or as part of your CI/CD pipeline. + + +"Build info files" are generated by your compilation toolchain (Hardhat, Foundry) and contain the inputs and outputs of the compilation process. + + +### Prerequisites + +Before using the `validate` command, you must define upgradeable contracts so that they can be detected and validated, define reference contracts for storage layout comparisons, and compile your contracts. + +#### Define Upgradeable Contracts + +The `validate` command performs upgrade safety checks on contracts that look like upgradeable contracts. Specifically, it performs checks on implementation contracts that meet any of the following criteria: + +* Inherits [`Initializable`](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol). +* Has an `upgradeTo(address)` or `upgradeToAndCall(address,bytes)` function. This is the case for contracts that inherit [`UUPSUpgradeable`](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/UUPSUpgradeable.sol). +* Has the NatSpec annotation `@custom:oz-upgrades` +* Has the NatSpec annotation `@custom:oz-upgrades-from ` according to [Define Reference Contracts](#define-reference-contracts) below. + + +Simply add the NatSpec annotation `@custom:oz-upgrades` or `@custom:oz-upgrades-from ` to each implementation contract so that it can be detected as an upgradeable contract for validation. + + +#### Define Reference Contracts + + +If an implementation contract is meant to deployed as an upgrade to an existing proxy, you **MUST** define a reference contract for storage layout comparisons. Otherwise, you will not receive errors if there are any storage layout incompatibilities. + + +Define a reference contract by adding the NatSpec annotation `@custom:oz-upgrades-from ` to your implementation contract, where `` is the contract name or fully qualified contract name of the reference contract to use for storage layout comparisons. The contract does not need to be in scope, and the contract name will suffice if it is unambiguous across the project. + +Example: +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @custom:oz-upgrades-from MyContractV1 +contract MyContractV2 { + ... +} +``` + +Or: +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @custom:oz-upgrades-from contracts/MyContract.sol:MyContractV1 +contract MyContractV2 { + ... +} +``` + +#### Compile Contracts with Storage Layouts + +Compile your contracts and ensure that your build is configured to output JSON files with Solidity compiler inputs and outputs in a build info directory. The compiler output must include storage layouts. If any previous build artifacts exist, they must be cleaned first to avoid duplicate contract definitions. + +##### Hardhat +Configure `hardhat.config.js` or `hardhat.config.ts` to include storage layout in the output selection: +```js +module.exports = { + solidity: { + settings: { + outputSelection: { + '*': { + '*': ['storageLayout'], + }, + }, + }, + }, +}; +``` + +Then compile your contracts: +```bash +npx hardhat clean && npx hardhat compile +``` + +##### Foundry +Configure `foundry.toml` to include build info and storage layout: +```toml +[profile.default] +build_info = true +extra_output = ["storageLayout"] +``` + +Then compile your contracts: +```bash +forge clean && forge build +``` + +### Usage + +After performing the prerequisites, run the `npx @openzeppelin/upgrades-core validate` command to validate your contracts: + +```bash +npx @openzeppelin/upgrades-core validate [] [] +``` + +If any errors are found, the command will exit with a non-zero exit code and print a detailed report of the errors to the console. + +**Parameters:** + +* `` - Optional path to the build info directory which contains JSON files with Solidity compiler input and output. Defaults to `artifacts/build-info` for Hardhat projects or `out/build-info` for Foundry projects. If your project uses a custom output directory, you must specify its build info directory here. + +**Options:** + +* `--contract ` - The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated. +* `--reference ` - Can only be used when the `--contract` option is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the `@custom:oz-upgrades-from` annotation if it is defined in the contract that is being validated. +* `--requireReference` - Can only be used when the `--contract` option is also provided. Not compatible with `--unsafeSkipStorageCheck`. If specified, requires either the `--reference` option to be provided or the contract to have a `@custom:oz-upgrades-from` annotation. +* `--referenceBuildInfoDirs "[,...]"` - Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix `:` before the contract name or fully qualified name in the `--reference` option or `@custom:oz-upgrades-from` annotation, where `` is the directory short name. Each directory short name must be unique, including compared to the main build info directory. If passing in multiple directories, separate them with commas or call the option multiple times, once for each directory. +* `--exclude "" [--exclude ""...]` - Exclude validations for contracts in source file paths that match any of the given glob patterns. For example, `--exclude "contracts/mocks/\***/**.sol"`. Does not apply to reference contracts. If passing in multiple patterns, call the option multiple times, once for each pattern. +* `--unsafeAllow "[,...]"` - Selectively disable one or more validation errors or warnings. Comma-separated list with one or more of the following: + * Errors: `state-variable-assignment, state-variable-immutable, external-library-linking, struct-definition, enum-definition, constructor, delegatecall, selfdestruct, missing-public-upgradeto, internal-function-storage, missing-initializer, missing-initializer-call, duplicate-initializer-call` + * Warnings: `incorrect-initializer-order` +* `--unsafeAllowRenames` - Configure storage layout check to allow variable renaming. +* `--unsafeSkipStorageCheck` - Skips checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort. + +## High-Level API + +The high-level API is a programmatic equivalent to the [validate command](#cli:-validate-command). Use this API if you want to validate all of your project's upgradeable contracts from a JavaScript or TypeScript environment. + +### Prerequisites + +Same prerequisites as the [validate command](#cli:-validate-command). + +### Usage + +Import the `validateUpgradeSafety` function: + +```ts +import { validateUpgradeSafety } from '@openzeppelin/upgrades-core'; +``` + +Then call the function to validate your contracts and get a project report with the validation results. + +#### validateUpgradeSafety +```ts +validateUpgradeSafety( + buildInfoDir?: string, + contract?: string, + reference?: string, + opts: ValidateUpgradeSafetyOptions = {}, + referenceBuildInfoDirs?: string[], + exclude?: string[], +): Promise +``` + +Detects upgradeable contracts from a build info directory and validates whether they are upgrade safe. Returns a [project report](#projectreport) with the results. + +Note that this function does not throw validation errors directly. Instead, you must use the project report to determine whether any errors were found. + +**Parameters:** + +* `buildInfoDir` - the path to the build info directory which contains JSON files with Solidity compiler input and output. Defaults to `artifacts/build-info` for Hardhat projects or `out/build-info` for Foundry projects. If your project uses a custom output directory, you must specify its build info directory here. +* `contract` - The name or fully qualified name of the contract to validate. If not specified, all upgradeable contracts in the build info directory will be validated. +* `reference` - Can only be used when the `contract` argument is also provided. The name or fully qualified name of the reference contract to use for storage layout comparisons. If not specified, uses the `@custom:oz-upgrades-from` annotation if it is defined in the contract that is being validated. +* `opts` - an object with the following options as defined in [Common Options](/upgrades-plugins/api-hardhat-upgrades#common-options): + * `unsafeAllow` + * `unsafeAllowRenames` + * `unsafeSkipStorageCheck` + * `requireReference` - Can only be used when the `contract` argument is also provided. Not compatible with the `unsafeSkipStorageCheck` option. If specified, requires either the `reference` argument to be provided or the contract to have a `@custom:oz-upgrades-from` annotation. +* `referenceBuildInfoDirs` - Optional paths of additional build info directories from previous versions of the project to use for storage layout comparisons. When using this option, refer to one of these directories using prefix `:` before the contract name or fully qualified name in the `reference` param or `@custom:oz-upgrades-from` annotation, where `` is the directory short name. Each directory short name must be unique, including compared to the main build info directory. +* `exclude` - Exclude validations for contracts in source file paths that match any of the given glob patterns. + +**Returns:** + +* a [project report](#projectreport). + +#### ProjectReport +```ts +interface ProjectReport { + ok: boolean; + explain(color?: boolean): string; + numPassed: number; + numTotal: number; +} +``` + +An object that represents the result of upgrade safety checks and storage layout comparisons, and contains a report of all errors found. + +***Members:*** + +* `ok` - `false` if any errors were found, otherwise `true`. +* `explain()` - returns a message explaining the errors in detail, if any. +* `numPassed` - number of contracts that passed upgrade safety checks. +* `numTotal` - total number of upgradeable contracts detected. + +## Low-Level API + + +This low-level API is deprecated. Use the [High-Level API](#high-level-api) instead. + + +The low-level API works with [Solidity input and output JSON objects](https://docs.soliditylang.org/en/latest/using-the-compiler.html#compiler-input-and-output-json-description) and lets you perform upgrade safety checks and storage layout comparisons on individual contracts. Use this API if you want to validate specific contracts rather than a whole project. + +### Prerequisites + +Compile your contracts to generate Solidity input and output JSON objects. The compiler output must include storage layouts. + +Note that the other prerequisites from the [validate command](#cli:-validate-command) are not required, because the low-level API does not detect upgradeable contracts automatically. Instead, you must create an instance of `UpgradeableContract` for each implementation contract that you want to validate, and call functions on it to get the upgrade safety and storage layout reports. + +### Usage + +Import the `UpgradeableContract` class: + +```ts +import { UpgradeableContract } from '@openzeppelin/upgrades-core'; +``` + +Then create an instance of `UpgradeableContract` for each implementation contract that you want to validate, and call `.getErrorReport()` and/or `.getStorageLayoutReport()` on it to get the upgrade safety and storage layout reports, respectively. + +#### UpgradeableContract + +This class represents the implementation for an upgradeable contract and gives access to error reports. + +##### constructor UpgradeableContract +```ts +constructor UpgradeableContract( + name: string, + solcInput: SolcInput, + solcOutput: SolcOutput, + opts?: { + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + }, + solcVersion?: string, +): UpgradeableContract +``` + +Creates a new instance of `UpgradeableContract`. + +**Parameters:** + +* `name` - the name of the implementation contract as either a fully qualified name or contract name. If multiple contracts have the same name, you must use the fully qualified name e.g., `contracts/Bar.sol:Bar`. +* `solcInput` - the Solidity input JSON object for the implementation contract. +* `solcOutput` - the Solidity output JSON object for the implementation contract. +* `opts` - an object with the following options as defined in [Common Options](/upgrades-plugins/api-hardhat-upgrades#common-options): + * `kind` + * `unsafeAllow` + * `unsafeAllowRenames` + * `unsafeSkipStorageCheck` +* `solcVersion` - the Solidity version used to compile the implementation contract. + + +In Hardhat, `solcInput` and `solcOutput` can be obtained from the Build Info file, which itself can be retrieved with `hre.artifacts.getBuildInfo`. + + +##### .getErrorReport +```ts +getErrorReport(): Report +``` + +***Returns:*** + +* a report about errors pertaining to proxied contracts, e.g. the use of `selfdestruct`. + +##### .getStorageUpgradeReport +```ts +getStorageUpgradeReport( + upgradedContract: UpgradeableContract, + opts?: { + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + }, +): Report +``` + +Compares the storage layout of an upgradeable contract with that of a proposed upgrade. + +**Parameters:** + +* `upgradedContract` - another instance of `UpgradeableContract` representing the proposed upgrade. +* `opts` - an object with the following options as defined in [Common Options](/upgrades-plugins/api-hardhat-upgrades#common-options): + * `kind` + * `unsafeAllow` + * `unsafeAllowRenames` + * `unsafeSkipStorageCheck` + +***Returns:*** + +* a report about errors pertaining to proxied contracts, e.g. the use of `selfdestruct`, and storage layout conflicts. + +#### Report +```ts +interface Report { + ok: boolean; + explain(color?: boolean): string; +} +``` + +An object that represents the results of an analysis. + +***Members:*** + +* `ok` - `false` if any errors were found, otherwise `true`. +* `explain()` - returns a message explaining the errors in detail, if any. diff --git a/docs/content/upgrades-plugins/api-hardhat-upgrades.mdx b/docs/content/upgrades-plugins/api-hardhat-upgrades.mdx new file mode 100644 index 00000000..326439ae --- /dev/null +++ b/docs/content/upgrades-plugins/api-hardhat-upgrades.mdx @@ -0,0 +1,693 @@ +--- +title: OpenZeppelin Hardhat Upgrades API +--- + +Both `deployProxy` and `upgradeProxy` functions will return instances of [ethers.js contracts](https://docs.ethers.io/v5/api/contract/contract), and require [ethers.js contract factories](https://docs.ethers.io/v5/api/contract/contract-factory) as arguments. For [beacons](/contracts/5.x/api/proxy#beacon), `deployBeacon` and `upgradeBeacon` will both return an upgradable beacon instance that can be used with a beacon proxy. All deploy and upgrade functions validate that the implementation contract is upgrade-safe, and will fail otherwise. + +## Common Options + +The following options are common to some functions. + +* `kind`: (`"uups" | "transparent" | "beacon"`) The kind of proxy to deploy, upgrade or import, or the kind of proxy that the implementation will be used with. `deployProxy()` and `upgradeProxy()` only support the values `"uups" | "transparent"`. Defaults to `"transparent"`. See [Transparent vs UUPS](/contracts/5.x/api/proxy#transparent-vs-uups). +* `unsafeAllow`: (`ValidationError[]`) Selectively disable one or more validation errors or warnings: + * `"external-library-linking"`: Allows a deployment with external libraries linked to the implementation contract. (External libraries are otherwise [not yet supported](/upgrades-plugins/faq#why-cant-i-use-external-libraries).) + * `"struct-definition"`, `"enum-definition"`: Used to be necessary to deploy a contract with structs or enums. No longer necessary. + * `"state-variable-assignment"`: Allows assigning state variables in a contract even though they will be stored in the implementation. + * `"state-variable-immutable"`: Allows use of immutable variables, which are not unsafe + * `"constructor"`: Allows defining a constructor. See `constructorArgs`. + * `"delegatecall"`, `"selfdestruct"`: Allow the use of these operations. Incorrect use of this option can put funds at risk of permanent loss. See [Can I safely use `delegatecall` and `selfdestruct`?](/upgrades-plugins/faq#can-i-safely-use-delegatecall-and-selfdestruct) + * `"missing-public-upgradeto"`: Allow UUPS implementations that do not contain a public `upgradeTo` or `upgradeToAndCall` function. Enabling this option is likely to cause a revert due to the built-in UUPS safety mechanism. + * `"internal-function-storage"`: Allow internal functions in storage variables. Internal functions are code pointers which will no longer be valid after an upgrade, so they must be reassigned during upgrades. See [How can I use internal functions in storage variables?](/upgrades-plugins/faq#how-can-i-use-internal-functions-in-storage-variables) + * `"missing-initializer"`: Allows implementations where an initializer function is not detected. + * `"missing-initializer-call"`: Allows implementations where a parent initializer is not called from the child initializer. + * `"duplicate-initializer-call"`: Allows implementations where a parent initializer is called more than once from the child initializer. + * `"incorrect-initializer-order"`: Allows implementations where parent initializers are not called in linearized order. **Note**: This condition shows a warning by default, and setting this option will silence the warning. +* `unsafeAllowRenames`: (`boolean`) Configure storage layout check to allow variable renaming. +* `unsafeSkipStorageCheck`: (`boolean`) upgrades the proxy or beacon without first checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort. +* `constructorArgs`: (`unknown[]`) Provide arguments for the constructor of the implementation contract. Note that these are different from initializer arguments, and will be used in the deployment of the implementation contract itself. Can be used to initialize immutable variables. +* `initialOwner`: (`string`) the address to set as the initial owner of a transparent proxy’s admin or initial owner of a beacon. Defaults to the externally owned account that is deploying the transparent proxy or beacon. Not supported for UUPS proxies. + * **Since:** `@openzeppelin/hardhat-upgrades@3.0.0` +* `unsafeSkipProxyAdminCheck`: (`boolean`) Skips checking the `initialOwner` option when deploying a transparent proxy. When deploying a transparent proxy, the `initialOwner` must be the address of an EOA or a contract that can call functions on a ProxyAdmin. It must not be a ProxyAdmin contract itself. Use this if you encounter an error due to this check and are sure that the `initialOwner` is not a ProxyAdmin contract. + * **Since:** `@openzeppelin/hardhat-upgrades@3.4.0` +* `timeout`: (`number`) Timeout in milliseconds to wait for the transaction confirmation when deploying an implementation contract. Defaults to `60000`. Use `0` to wait indefinitely. +* `pollingInterval`: (`number`) Polling interval in milliseconds between checks for the transaction confirmation when deploying an implementation contract. Defaults to `5000`. +* `redeployImplementation`: (`"always" | "never" | "onchange"`) Determines whether the implementation contract will be redeployed. Defaults to `"onchange"`. + * If set to `"always"`, the implementation contract is always redeployed even if it was previously deployed with the same bytecode. This can be used with the `salt` option when deploying a proxy through OpenZeppelin Defender to ensure that the implementation contract is deployed with the same salt as the proxy. + * If set to `"never"`, the implementation contract is never redeployed. If the implementation contract was not previously deployed or is not found in the network file, an error will be thrown. + * If set to `"onchange"`, the implementation contract is redeployed only if the bytecode has changed from previous deployments. +* `txOverrides`: (`ethers.Overrides`) An ethers.js [Overrides](https://docs.ethers.org/v6/api/contract/#Overrides) object to override transaction parameters, such as `gasLimit` and `gasPrice`. Applies to all transactions sent by a function with this option, even if the function sends multiple transactions. For OpenZeppelin Defender deployments, only the `gasLimit`, `gasPrice`, `maxFeePerGas`, and `maxPriorityFeePerGas` parameters are supported. +* `useDefenderDeploy`: (`boolean`) Deploy contracts using OpenZeppelin Defender instead of ethers.js. See [Using with OpenZeppelin Defender](/upgrades-plugins/defender-deploy). +* `verifySourceCode`: (`boolean`) When using OpenZeppelin Defender deployments, whether to verify source code on block explorers. Defaults to `true`. +* `relayerId`: (`string`) When using OpenZeppelin Defender deployments, the ID of the relayer to use for the deployment. Defaults to the relayer configured for your deployment environment on Defender. +* `salt`: (`string`) When using OpenZeppelin Defender deployments, if this is not set, deployments will be performed using the CREATE opcode. If this is set, deployments will be performed using the CREATE2 opcode with the provided salt. Note that deployments using a Safe are done using CREATE2 and require a salt. ***Warning:*** CREATE2 affects `msg.sender` behavior. See [Caveats](/defender/tutorial/deploy#caveats) for more information. +* `metadata`: (` commitHash?: string; tag?: string; [k: string]: any; `) When using OpenZeppelin Defender deployments, you can use this to identify, tag, or classify deployments. See [Metadata](/defender/module/deploy#metadata). +* `proxyFactory`: (`ethers.ContractFactory`) Customizes the ethers contract factory to use for deploying the proxy, allowing a custom proxy contract to be deployed. See [factories.ts](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/packages/plugin-hardhat/src/utils/factories.ts) for the default contract factory for each kind of proxy. + * **Since:** `@openzeppelin/hardhat-upgrades@3.7.0` +* `deployFunction`: (`(hre, opts, factory, ...args) => Promise`) Customizes the function used to deploy the proxy. Can be used along with the `proxyFactory` option to override constructor parameters for custom proxy deployments. See [deploy.ts](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/packages/plugin-hardhat/src/utils/deploy.ts) for the default deploy function. + * **Since:** `@openzeppelin/hardhat-upgrades@3.7.0` + +Note that the options `unsafeAllow` can also be specified in a more granular way directly in the source code if using Solidity >=0.8.2. See [How can I disable some of the checks?](/upgrades-plugins/faq#how-can-i-disable-some-of-the-checks) + +The following options have been deprecated. + +* `unsafeAllowLinkedLibraries`: Equivalent to including `"external-library-linking"` in `unsafeAllow`. +* `unsafeAllowCustomTypes`: Equivalent to including `"struct-definition"` and `"enum-definition"` in `unsafeAllow`. No longer necessary. +* `useDeployedImplementation`: (`boolean`) Equivalent to setting `redeployImplementation` to `"never"`. + +## deployProxy + +```ts +async function deployProxy( + Contract: ethers.ContractFactory, + args: unknown[] = [], + opts?: + initializer?: string | false, + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + initialOwner?: string, + unsafeSkipProxyAdminCheck?: boolean, + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: ethers.Overrides, + kind?: 'uups' | 'transparent', + useDefenderDeploy?: boolean, + proxyFactory?: ethers.ContractFactory, + deployFunction?: () => Promise, + , +): Promise +``` + +Creates a UUPS or Transparent proxy given an ethers contract factory to use as implementation, and returns a contract instance with the proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. + +If you call `deployProxy` several times for the same implementation contract, several proxies will be deployed, but only one implementation contract will be used. + +**Parameters:** + +* `Contract` - an ethers contract factory to use as the implementation. +* `args` - arguments for the initializer function. +* `opts` - an object with options: + * `initializer`: set a different initializer function to call (see [Specifying Fragments](https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--specifying-fragments)), or specify `false` to disable initialization. + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* a contract instance with the proxy address and the implementation interface. + +## upgradeProxy + +```ts +async function upgradeProxy( + proxy: string | ethers.Contract, + Contract: ethers.ContractFactory, + opts?: + call?: string | { fn: string; args?: unknown[] , + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: ethers.Overrides, + kind?: 'uups' | 'transparent', + }, +): Promise +``` + +Upgrades a UUPS or Transparent proxy at a specified address to a new implementation contract, and returns a contract instance with the proxy address and the new implementation interface. + +**Parameters:** + +* `proxy` - the proxy address or proxy contract instance. +* `Contract` - an ethers contract factory to use as the new implementation. +* `opts` - an object with options: + * `call`: enables the execution of an arbitrary function call during the upgrade process. This call is described using a function name, signature, or selector (see [Specifying Fragments](https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--specifying-fragments)), and optional arguments. It is batched into the upgrade transaction, making it safe to call migration initializing functions. + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* a contract instance with the proxy address and the new implementation interface. + +## deployBeacon + +```ts +async function deployBeacon( + Contract: ethers.ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + initialOwner?: string, + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: ethers.Overrides, + , +): Promise +``` + +Creates an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) given an ethers contract factory to use as implementation, and returns the beacon contract instance. + +**Parameters:** + +* `Contract` - an ethers contract factory to use as the implementation. +* `opts` - an object with options: + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* the beacon contract instance. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@1.13.0` + +## upgradeBeacon + +```ts +async function upgradeBeacon( + beacon: string | ethers.Contract, + Contract: ethers.ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: ethers.Overrides, + , +): Promise +``` + +Upgrades an [upgradable beacon](/contracts/5.x/api/proxy#UpgradeableBeacon) at a specified address to a new implementation contract, and returns the beacon contract instance. + +**Parameters:** + +* `beacon` - the beacon address or beacon contract instance. +* `Contract` - an ethers contract factory to use as the new implementation. +* `opts` - an object with options: + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* the beacon contract instance. + +**Since**: + +* `@openzeppelin/hardhat-upgrades@1.13.0` + +## deployBeaconProxy + +```ts +async function deployBeaconProxy( + beacon: string | ethers.Contract, + attachTo: ethers.ContractFactory, + args: unknown[] = [], + opts?: + initializer?: string | false, + txOverrides?: ethers.Overrides, + useDefenderDeploy?: boolean, + proxyFactory?: ethers.ContractFactory, + deployFunction?: () => Promise, + , +): Promise +``` + +Creates a [Beacon proxy](/contracts/5.x/api/proxy#BeaconProxy) given an existing beacon contract address and an ethers contract factory corresponding to the beacon’s current implementation contract, and returns a contract instance with the beacon proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. + +**Parameters:** + +* `beacon` - the beacon address or beacon contract instance. +* `attachTo` - an ethers contract factory corresponding to the beacon’s current implementation contract. +* `args` - arguments for the initializer function. +* `opts` - an object with options: + * `initializer`: set a different initializer function to call (see [Specifying Fragments](https://docs.ethers.io/v5/api/utils/abi/interface/#Interface--specifying-fragments)), or specify `false` to disable initialization. + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* a contract instance with the beacon proxy address and the implementation interface. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@1.13.0` + +## forceImport + +```ts +async function forceImport( + address: string, + deployedImpl: ethers.ContractFactory, + opts?: + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Forces the import of an existing proxy, beacon, or implementation contract deployment to be used with this plugin. Provide the address of an existing proxy, beacon or implementation, along with the ethers contract factory of the implementation contract that was deployed. + + +When importing a proxy or beacon, the `deployedImpl` argument must be the contract factory of the **current** implementation contract version that is being used, not the version that you are planning to upgrade to. + + +Use this function to recreate a lost [network file](/upgrades-plugins/network-files) by importing previous deployments, or to register proxies or beacons for upgrading even if they were not originally deployed by this plugin. Supported for UUPS, Transparent, and Beacon proxies, as well as beacons and implementation contracts. + +**Parameters:** + +* `address` - the address of an existing proxy, beacon or implementation. +* `deployedImpl` - the ethers contract factory of the implementation contract that was deployed. +* `opts` - an object with options: + * `kind`: (`"uups" | "transparent" | "beacon"`) forces a proxy to be treated as a UUPS, Transparent, or Beacon proxy. If not provided, the proxy kind will be automatically detected. + +**Returns:** + +* a contract instance representing the imported proxy, beacon or implementation. + +**Since** + +* `@openzeppelin/hardhat-upgrades@1.15.0` + +## validateImplementation + +```ts +async function validateImplementation( + Contract: ethers.ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Validates an implementation contract without deploying it. + +**Parameters:** + +* `Contract` - the ethers contract factory of the implementation contract. +* `opts` - an object with options: + * additional options as described in [Common Options](#common-options). + +**Since:** + +* `@openzeppelin/hardhat-upgrades@1.20.0` + +## deployImplementation + +```ts +async function deployImplementation( + Contract: ethers.ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: ethers.Overrides, + getTxResponse?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + useDefenderDeploy?: boolean, + , +): Promise +``` + +Validates and deploys an implementation contract, and returns its address. + +**Parameters:** + +* `Contract` - an ethers contract factory to use as the implementation. +* `opts` - an object with options: + * `getTxResponse`: if set to `true`, causes this function to return an ethers transaction response corresponding to the deployment of the new implementation contract instead of its address. Note that if the new implementation contract was originally imported as a result of `forceImport`, only the address will be returned. + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* the address or an ethers transaction response corresponding to the deployment of the implementation contract. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@1.20.0` + +## validateUpgrade + +```ts +async function validateUpgrade( + referenceAddressOrContract: string | ethers.ContractFactory, + newContract: ethers.ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Validates a new implementation contract without deploying it and without actually upgrading to it. Compares the current implementation contract to the new implementation contract to check for storage layout compatibility errors. If `referenceAddressOrContract` is the current implementation address, the `kind` option is required. + +**Parameters:** + +* `referenceAddressOrContract` - a proxy or beacon address that uses the current implementation, or an address or ethers contract factory corresponding to the current implementation. +* `newContract` - the new implementation contract. +* `opts` - an object with options: + * additional options as described in [Common Options](#common-options). + +**Since:** + +* `@openzeppelin/hardhat-upgrades@1.20.0` + +**Examples:** + +Validate upgrading an existing proxy to a new contract (replace `PROXY_ADDRESS` with the address of your proxy): +```ts +const ethers, upgrades = require('hardhat'); + +const BoxV2 = await ethers.getContractFactory('BoxV2'); +await upgrades.validateUpgrade(PROXY_ADDRESS, BoxV2); +``` + +Validate upgrading between two contract implementations: +```ts +const ethers, upgrades = require('hardhat'); + +const Box = await ethers.getContractFactory('Box'); +const BoxV2 = await ethers.getContractFactory('BoxV2'); +await upgrades.validateUpgrade(Box, BoxV2); +``` + +## prepareUpgrade + +```ts +async function prepareUpgrade( + referenceAddressOrContract: string | ethers.Contract, + Contract: ethers.ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: ethers.Overrides, + getTxResponse?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + useDefenderDeploy?: boolean, + , +): Promise +``` + +Validates and deploys a new implementation contract, and returns its address. If `referenceAddressOrContract` is the current implementation address, the `kind` option is required. Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from Hardhat. + +**Parameters:** + +* `referenceAddressOrContract` - the proxy or beacon or implementation address or contract instance. +* `Contract` - the new implementation contract. +* `opts` - an object with options: + * `getTxResponse`: if set to `true`, causes this function to return an ethers transaction response corresponding to the deployment of the new implementation contract instead of its address. Note that if the new implementation contract was originally imported as a result of `forceImport`, only the address will be returned. + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* the address or an ethers transaction response corresponding to the deployment of the new implementation contract. + +## defender.deployContract + +```ts +async function deployContract( + Contract: ethers.ContractFactory, + args: unknown[] = [], + opts?: + unsafeAllowDeployContract?: boolean, + pollingInterval?: number, + , +): Promise +``` + +Deploys a non-upgradeable contract using OpenZeppelin Defender, and returns a contract instance. Throws an error if the contract looks like an implementation contract. + + +Do not use this function to deploy implementations of upgradeable contracts, because upgrade safety validations are not performed with this function. For implementation contracts, use [deployImplementation](#deployimplementation) instead. + + +**Parameters:** + +* `Contract` - an ethers contract factory to use as the contract to deploy. +* `opts` - an object with options: + * `unsafeAllowDeployContract`: if set to `true`, allows the contract to be deployed even if it looks like an implementation contract. Defaults to `false`. + * `pollingInterval`: polling interval in milliseconds between checks for the transaction confirmation when calling `.waitForDeployment()` on the resulting contract instance. Defaults to `5000`. + +**Returns:** + +* the contract instance. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@2.2.0` + +## defender.getDeployApprovalProcess + +```ts +async function getDeployApprovalProcess( +): Promise< + approvalProcessId: string, + address?: string, + viaType?: 'EOA' | 'Contract' | 'Multisig' | 'Safe' | 'Gnosis Multisig' | 'Relayer' | 'Unknown' | 'Timelock Controller' | 'ERC20' | 'Governor' | 'Fireblocks', + > +``` + +Gets the default deploy approval process configured for your deployment environment on OpenZeppelin Defender. + +**Returns:** + +* an object with the default deploy approval process ID and the associated address, such as a Relayer, EOA, or multisig wallet address. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@2.5.0` + +## defender.getUpgradeApprovalProcess + +```ts +async function getUpgradeApprovalProcess( +): Promise< + approvalProcessId: string, + address?: string, + viaType?: 'EOA' | 'Contract' | 'Multisig' | 'Safe' | 'Gnosis Multisig' | 'Relayer' | 'Unknown' | 'Timelock Controller' | 'ERC20' | 'Governor' | 'Fireblocks', + > +``` + +Gets the default upgrade approval process configured for your deployment environment on OpenZeppelin Defender. For example, this is useful for determining the default multisig wallet that you can use in your scripts to assign as the owner of your proxy. + +**Returns:** + +* an object with the default upgrade approval process ID and the associated address, such as a multisig or governor contract address. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@2.5.0` + +## defender.proposeUpgradeWithApproval + +```ts +async function proposeUpgradeWithApproval( + proxyAddress: string, + ImplFactory: ContractFactory, + opts?: + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + kind?: 'uups' | 'transparent' | 'beacon', + useDefenderDeploy?: boolean, + approvalProcessId?: string, + , +): Promise< + proposalId: string, + url: string, + txResponse?: ethers.providers.TransactionResponse, + > +``` + +Proposes an upgrade using an upgrade approval process on OpenZeppelin Defender. + +Similar to `prepareUpgrade`. This method validates and deploys the new implementation contract, but also proposes an upgrade using an upgrade approval process on OpenZeppelin Defender. Supported for UUPS or Transparent proxies. Not currently supported for beacon proxies or beacons. For beacons, use `prepareUpgrade` along with a transaction proposal on Defender to upgrade the beacon to the deployed implementation. + +**Parameters:** + +* `proxyAddress` - the proxy address. +* `ImplFactory` - the new implementation contract. +* `opts` - an object with options: + * `approvalProcessId`: The ID of the upgrade approval process. Defaults to the upgrade approval process configured for your deployment environment on Defender. + * additional options as described in [Common Options](#common-options). + +**Returns:** + +* an object with the Defender proposal ID, the URL of the proposal in Safe App if applicable, and the ethers transaction response corresponding to the deployment of the new implementation contract. Note that if the new implementation contract was originally imported as a result of `forceImport`, the ethers transaction response will be undefined. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@2.2.0` + +## admin.changeProxyAdmin + +```ts +async function changeProxyAdmin( + proxyAddress: string, + newAdmin: string, + signer?: ethers.Signer, + opts?: + txOverrides?: ethers.Overrides, + +): Promise +``` + +Changes the admin for a specific proxy. + + +This function is not supported with admins or proxies from OpenZeppelin Contracts 5.x. + + +**Parameters:** + +* `proxyAddress` - the address of the proxy to change. +* `newAdmin` - the new admin address. +* `signer` - the signer to use for the transaction. +* `opts` - an object with options: + * additional options as described in [Common Options](#common-options). + +## admin.transferProxyAdminOwnership + +```ts +async function transferProxyAdminOwnership( + proxyAddress: string, + newOwner: string, + signer?: ethers.Signer, + opts?: + silent?: boolean, + txOverrides?: ethers.Overrides, + +): Promise +``` + +Changes the owner of the proxy admin contract for a specific proxy. + + +The `proxyAddress` parameter is required since `@openzeppelin/hardhat-upgrades@3.0.0` + + +**Parameters:** + +* `proxyAddress` - the address of the proxy whose admin ownership is to be transferred. +* `newOwner` - the new owner address for the proxy admin contract. +* `signer` - the signer to use for the transaction. +* `opts` - an object with options: + * `silent`: if set to `true`, silences console logging about each proxy affected by the admin ownership transfer. + * additional options as described in [Common Options](#common-options). + +**Since:** + +* `@openzeppelin/hardhat-upgrades@3.0.0` + +## erc1967 + +```ts +async function erc1967.getImplementationAddress(proxyAddress: string): Promise; +async function erc1967.getBeaconAddress(proxyAddress: string): Promise; +async function erc1967.getAdminAddress(proxyAddress: string): Promise; +``` + +Functions in this module provide access to the [ERC1967](https://eips.ethereum.org/EIPS/eip-1967) variables of a proxy contract. + +**Parameters:** + +* `proxyAddress` - the proxy address. + +**Returns:** + +* the implementation, beacon, or admin address depending on the function called. + +## beacon + +```ts +async function beacon.getImplementationAddress(beaconAddress: string): Promise; +``` + +This module provides a convenience function to get the implementation address from a beacon contract. + +**Parameters:** + +* `beaconAddress` - the beacon address. + +**Returns:** + +* the implementation address. + +**Since:** + +* `@openzeppelin/hardhat-upgrades@1.13.0` + +## silenceWarnings + +```ts +function silenceWarnings() +``` + + +This function is useful for tests, but its use in production deployment scripts is discouraged. + + +Silences all subsequent warnings about the use of unsafe flags. Prints a last warning before doing so. + +## verify + +Extends [hardhat-verify](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify)'s `verify` task to completely verify a proxy on Etherscan. This supports verifying proxy contracts that were deployed by the Hardhat Upgrades plugin. + +The arguments are the same as for hardhat-verify’s `verify` task. If the provided address is a proxy, this task will verify the proxy’s implementation contract, the proxy itself and any proxy-related contracts, as well as link the proxy to the implementation contract’s ABI on Etherscan. If the provided address is not a proxy, the regular `verify` task from hardhat-verify will be run on the address instead. + +The following contracts will be verified when you run this task on your proxy address: + +* Your implementation contract +* [ERC1967Proxy](/contracts/5.x/api/proxy#ERC1967Proxy) or [TransparentUpgradeableProxy](/contracts/5.x/api/proxy#TransparentUpgradeableProxy) or [BeaconProxy](/contracts/5.x/api/proxy#BeaconProxy) (for UUPS, transparent, or beacon proxies, respectively) +* [ProxyAdmin](/contracts/5.x/api/proxy#ProxyAdmin) (with transparent proxies) +* [UpgradeableBeacon](/contracts/5.x/api/proxy#UpgradeableBeacon) (with beacon proxies) + +**Since:** + +* `@openzeppelin/hardhat-upgrades@2.0.0` + +**Usage:** + +To use this task, ensure you have hardhat-verify installed: +```sh +npm install --save-dev @nomicfoundation/hardhat-verify +``` + +Then import the `@nomicfoundation/hardhat-verify` plugin along with the `@openzeppelin/hardhat-upgrades` plugin in your Hardhat configuration. +For example, if you are using JavaScript, import the plugins in `hardhat.config.js`: +```js +require("@nomicfoundation/hardhat-verify"); +require("@openzeppelin/hardhat-upgrades"); +``` +Or if you are using TypeScript, import the plugins in `hardhat.config.ts`: +```ts +import "@nomicfoundation/hardhat-verify"; +import "@openzeppelin/hardhat-upgrades"; +``` + +Finally, follow [hardhat-verify’s usage documentation](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#usage) to configure your Etherscan API key and run the `verify` task from the command line with the proxy address: +``` +npx hardhat verify --network mainnet PROXY_ADDRESS +``` +or programmatically using the [`verify:verify` subtask](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#using-programmatically): +```javascript +await hre.run("verify:verify", + address: PROXY_ADDRESS, +); +``` + +Note that you do not need to include constructor arguments when verifying if your implementation contract only uses initializers. However, if your implementation contract has an actual constructor with arguments (such as to set immutable variables), then include constructor arguments according to the usage information for the [task](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#usage) or [subtask](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify#using-programmatically). diff --git a/docs/content/upgrades-plugins/api-truffle-upgrades.mdx b/docs/content/upgrades-plugins/api-truffle-upgrades.mdx new file mode 100644 index 00000000..121e90d8 --- /dev/null +++ b/docs/content/upgrades-plugins/api-truffle-upgrades.mdx @@ -0,0 +1,512 @@ +--- +title: OpenZeppelin Truffle Upgrades API +--- + + +This package is deprecated. The recommended alternative is to use [Hardhat](https://hardhat.org/) along with the [Hardhat Upgrades](/upgrades-plugins/hardhat-upgrades) plugin. + + +Both `deployProxy` and `upgradeProxy` functions will return instances of [Truffle contracts](https://www.trufflesuite.com/docs/truffle/reference/contract-abstractions), and require Truffle contract classes (retrieved via `artifacts.require`) as arguments. For [beacons](/contracts/4.x/api/proxy#beacon), `deployBeacon` and `upgradeBeacon` will both return an upgradable beacon instance that can be used with a beacon proxy. All deploy and upgrade functions validate that the implementation contract is upgrade-safe, and will fail otherwise. + +## Common Options + +The following options are common to some functions. + +* `deployer`: Should be set to the Truffle migration deployer during migrations. +* `kind`: (`"uups" | "transparent" | "beacon"`) The kind of proxy to deploy, upgrade or import, or the kind of proxy that the implementation will be used with. `deployProxy()` and `upgradeProxy()` only support the values `"uups" | "transparent"`. Defaults to `"transparent"`. See [Transparent vs UUPS](/contracts/5.x/api/proxy#transparent-vs-uups). +* `unsafeAllow`: (`ValidationError[]`) Selectively disable one or more validation errors: + * `"external-library-linking"`: Allows a deployment with external libraries linked to the implementation contract. (External libraries are otherwise [not yet supported](/upgrades-plugins/faq#why-cant-i-use-external-libraries).) + * `"struct-definition"`, `"enum-definition"`: Used to be necessary to deploy a contract with structs or enums. No longer necessary. + * `"state-variable-assignment"`: Allows assigning state variables in a contract even though they will be stored in the implementation. + * `"state-variable-immutable"`: Allows use of immutable variables, which are not unsafe + * `"constructor"`: Allows defining a constructor. See `constructorArgs`. + * `"delegatecall"`, `"selfdestruct"`: Allow the use of these operations. Incorrect use of this option can put funds at risk of permanent loss. See [Can I safely use `delegatecall` and `selfdestruct`?](/upgrades-plugins/faq#can-i-safely-use-delegatecall-and-selfdestruct) + * `"missing-public-upgradeto"`: Allow UUPS implementations that do not contain a public `upgradeTo` or `upgradeToAndCall` function. Enabling this option is likely to cause a revert due to the built-in UUPS safety mechanism. +* `unsafeAllowRenames`: (`boolean`) Configure storage layout check to allow variable renaming. +* `unsafeSkipStorageCheck`: (`boolean`) upgrades the proxy or beacon without first checking for storage layout compatibility errors. This is a dangerous option meant to be used as a last resort. +* `constructorArgs`: (`unknown[]`) Provide arguments for the constructor of the implementation contract. Note that these are different from initializer arguments, and will be used in the deployment of the implementation contract itself. Can be used to initialize immutable variables. +* `redeployImplementation`: (`"always" | "never" | "onchange"`) Determines whether the implementation contract will be redeployed. Defaults to `"onchange"`. + * If set to `"always"`, the implementation contract is always redeployed even if it was previously deployed with the same bytecode. + * If set to `"never"`, the implementation contract is never redeployed. If the implementation contract was not previously deployed or is not found in the network file, an error will be thrown. + * If set to `"onchange"`, the implementation contract is redeployed only if the bytecode has changed from previous deployments. +* `txOverrides`: (`TruffleTxOptions`) A Truffle [options](https://trufflesuite.com/docs/truffle/how-to/contracts/run-migrations/#deployerdeploycontract-args-options) object to override transaction parameters, such as `gas` and `gasPrice`. Applies to all transactions sent by a function with this option, even if the function sends multiple transactions. + +Note that the options `unsafeAllow` can also be specified in a more granular way directly in the source code if using Solidity >=0.8.2. See [How can I disable some of the checks?](/upgrades-plugins/faq#how-can-i-disable-some-of-the-checks) + +The following options have been deprecated. + +* `unsafeAllowLinkedLibraries`: Equivalent to including `"external-library-linking"` in `unsafeAllow`. +* `unsafeAllowCustomTypes`: Equivalent to including `"struct-definition"` and `"enum-definition"` in `unsafeAllow`. No longer necessary. +* `useDeployedImplementation`: (`boolean`) Equivalent to setting `redeployImplementation` to `"never"`. + +## deployProxy + +```ts +async function deployProxy( + Contract: ContractClass, + args: unknown[] = [], + opts?: + deployer?: Deployer, + initializer?: string | false, + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: TruffleTxOptions, + kind?: 'uups' | 'transparent', + , +): Promise +``` + +Creates a UUPS or Transparent proxy given a Truffle contract class to use as implementation, and returns a contract instance with the proxy address and the implementation interface. During a migration, the proxy address will be stored in the implementation contract’s artifact, so you can use Truffle’s [`deployed()`](https://www.trufflesuite.com/docs/truffle/reference/contract-abstractions#-code-mycontract-deployed-code-) function to load it. + +If `args` is set, will call an initializer function `initialize` with the supplied `args` during proxy deployment. + +If you call `deployProxy` several times for the same implementation contract, several proxies will be deployed, but only one implementation contract will be used. + +**Parameters:** + +* `Contract` - a Truffle contract class to use as the implementation. +* `args` - arguments for the initializer function. +* `opts` - an object with options: + * `initializer`: set a different initializer function to call, or specify `false` to disable initialization + * See [Common Options](#common-options). + +**Returns:** + +* a contract instance with the proxy address and the implementation interface. + +## upgradeProxy + +```ts +async function upgradeProxy( + proxy: string | ContractInstance, + Contract: ContractClass, + opts?: + deployer?: Deployer, + call?: string | { fn: string; args?: unknown[] , + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: TruffleTxOptions, + kind?: 'uups' | 'transparent', + }, +): Promise +``` + +Upgrades a UUPS or Transparent proxy at a specified address to a new implementation contract, and returns a contract instance with the proxy address and the new implementation interface. + +**Parameters:** + +* `proxy` - the proxy address or proxy contract instance. +* `Contract` - a Truffle contract class to use as the new implementation. +* `opts` - an object with options: + * `call`: enables the execution of an arbitrary function call during the upgrade process. This call is described using a function name or signature and optional arguments. It is batched into the upgrade transaction, making it safe to call migration initializing functions. + * See [Common Options](#common-options). + +**Returns:** + +* a contract instance with the proxy address and the new implementation interface. + +## deployBeacon + +```ts +async function deployBeacon( + Contract: ContractClass, + opts?: + deployer?: Deployer, + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: TruffleTxOptions, + , +): Promise +``` + +Creates an [upgradable beacon](/contracts/4.x/api/proxy#UpgradeableBeacon) given a Truffle contract class to use as implementation, and returns the beacon contract instance. + +**Parameters:** + +* `Contract` - a Truffle contract class to use as the implementation. +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Returns:** + +* the beacon contract instance. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.12.0` + +## upgradeBeacon + +```ts +async function upgradeBeacon( + beacon: string | ContractInstance, + Contract: ContractClass, + opts?: + deployer?: Deployer, + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: TruffleTxOptions, + , +): Promise +``` + +Upgrades an [upgradable beacon](/contracts/4.x/api/proxy#UpgradeableBeacon) at a specified address to a new implementation contract, and returns the beacon contract instance. + +**Parameters:** + +* `beacon` - the beacon address or beacon contract instance. +* `Contract` - a Truffle contract class to use as the new implementation. +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Returns:** + +* the beacon contract instance. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.12.0` + +## deployBeaconProxy + +```ts +async function deployBeaconProxy( + beacon: string | ContractInstance, + attachTo: ContractClass, + args: unknown[] = [], + opts?: + deployer?: Deployer, + initializer?: string | false, + txOverrides?: TruffleTxOptions, + , +): Promise +``` + +Creates a [Beacon proxy](/contracts/4.x/api/proxy#BeaconProxy) given an existing beacon contract address and a Truffle contract class corresponding to the beacon’s current implementation contract, and returns a contract instance with the beacon proxy address and the implementation interface. If `args` is set, will call an initializer function `initialize` with the supplied args during proxy deployment. + +**Parameters:** + +* `beacon` - the beacon address or beacon contract instance. +* `attachTo` - a Truffle contract class corresponding to the beacon’s current implementation contract. +* `args` - arguments for the initializer function. +* `opts` - an object with options: + * `initializer`: set a different initializer function to call, or specify `false` to disable initialization + +**Returns:** + +* a contract instance with the beacon proxy address and the implementation interface. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.12.0` + +## forceImport + +```ts +async function forceImport( + address: string, + deployedImpl: ContractClass, + opts?: + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Forces the import of an existing proxy, beacon, or implementation contract deployment to be used with this plugin. Provide the address of an existing proxy, beacon or implementation, along with the Truffle contract class of the implementation contract that was deployed. + + +When importing a proxy or beacon, the `deployedImpl` argument must be the contract class of the **current** implementation contract version that is being used, not the version that you are planning to upgrade to. + + +Use this function to recreate a lost [network file](/upgrades-plugins/network-files) by importing previous deployments, or to register proxies or beacons for upgrading even if they were not originally deployed by this plugin. Supported for UUPS, Transparent, and Beacon proxies, as well as beacons and implementation contracts. + +**Parameters:** + +* `address` - the address of an existing proxy, beacon or implementation. +* `deployedImpl` - the Truffle contract class of the implementation contract that was deployed. +* `opts` - an object with options: + * `kind`: (`"uups" | "transparent" | "beacon"`) forces a proxy to be treated as a UUPS, Transparent, or Beacon proxy. If not provided, the proxy kind will be automatically detected. + +**Returns:** + +* a contract instance representing the imported proxy, beacon or implementation. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.13.0` + +## validateImplementation + +```ts +async function validateImplementation( + Contract: ContractClass, + opts?: + unsafeAllow?: ValidationError[], + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Validates an implementation contract without deploying it. + +**Parameters:** + +* `Contract` - the Truffle contract class of the implementation contract. +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.16.0` + +## deployImplementation + +```ts +async function deployImplementation( + Contract: ContractClass, + opts?: + deployer?: Deployer, + unsafeAllow?: ValidationError[], + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: TruffleTxOptions, + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Validates and deploys an implementation contract, and returns its address. + +**Parameters:** + +* `Contract` - a Truffle contract class to use as the implementation. +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Returns:** + +* the address of the implementation contract. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.16.0` + +## validateUpgrade + +```ts +async function validateUpgrade( + referenceAddressOrContract: string | ContractClass, + newContract: ContractClass, + opts?: + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Validates a new implementation contract without deploying it and without actually upgrading to it. Compares the current implementation contract to the new implementation contract to check for storage layout compatibility errors. If `referenceAddressOrContract` is the current implementation address, the `kind` option is required. + +**Parameters:** + +* `referenceAddressOrContract` - a proxy or beacon address that uses the current implementation, or an address or Truffle contract class corresponding to the current implementation. +* `newContract` - the new implementation contract. +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.16.0` + +**Examples:** + +Validate upgrading an existing proxy to a new contract (replace `PROXY_ADDRESS` with the address of your proxy): +```ts +const validateUpgrade = require('@openzeppelin/truffle-upgrades'); + +const BoxV2 = artifacts.require('BoxV2'); +await validateUpgrade(PROXY_ADDRESS, BoxV2); +``` + +Validate upgrading between two contract implementations: +```ts +const validateUpgrade = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); +const BoxV2 = artifacts.require('BoxV2'); +await validateUpgrade(Box, BoxV2); +``` + +## prepareUpgrade + +```ts +async function prepareUpgrade( + referenceAddressOrContract: string | ContractInstance, + Contract: ContractClass, + opts?: + deployer?: Deployer, + unsafeAllow?: ValidationError[], + unsafeAllowRenames?: boolean, + unsafeSkipStorageCheck?: boolean, + constructorArgs?: unknown[], + timeout?: number, + pollingInterval?: number, + redeployImplementation?: 'always' | 'never' | 'onchange', + txOverrides?: TruffleTxOptions, + kind?: 'uups' | 'transparent' | 'beacon', + , +): Promise +``` + +Validates and deploys a new implementation contract, and returns its address. If `referenceAddressOrContract` is the current implementation address, the `kind` option is required. Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from Truffle. + +**Parameters:** + +* `referenceAddressOrContract` - the proxy or beacon or implementation address or contract instance. +* `Contract` - the new implementation contract. +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Returns:** + +* the address of the new implementation contract. + +## deployProxyAdmin + +```ts +async function deployProxyAdmin( + opts?: + deployer?: Deployer, + timeout?: number, + pollingInterval?: number, + txOverrides?: TruffleTxOptions, + , +): Promise +``` + +Deploys a [proxy admin](/contracts/4.x/api/proxy#ProxyAdmin) contract and returns its address if one was not already deployed on the current network, or just returns the address of the proxy admin if one was already deployed. Note that this plugin currently only supports using one proxy admin per network. + +**Parameters:** + +* `opts` - an object with options: + * See [Common Options](#common-options). + +**Returns:** + +* the address of the proxy admin. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.16.0` + +## admin.changeProxyAdmin + +```ts +async function changeProxyAdmin( + proxyAddress: string, + newAdmin: string, + opts?: + deployer?: Deployer, + txOverrides?: TruffleTxOptions, + , +): Promise +``` + +Changes the admin for a specific proxy. + +**Parameters:** + +* `proxyAddress` - the address of the proxy to change. +* `newAdmin` - the new admin address. +* `opts` - an object with options: + * See [Common Options](#common-options). + +## admin.transferProxyAdminOwnership + +```ts +async function transferProxyAdminOwnership( + newAdmin: string, + opts?: + deployer?: Deployer, + txOverrides?: TruffleTxOptions, + , + +): Promise +``` + +Changes the owner of the proxy admin contract, which is the default admin for upgrade rights over all proxies. + +**Parameters:** + +* `newAdmin` - the new admin address. +* `opts` - an object with options: + * See [Common Options](#common-options). + +## erc1967 + +```ts +async function erc1967.getImplementationAddress(proxyAddress: string): Promise; +async function erc1967.getBeaconAddress(proxyAddress: string): Promise; +async function erc1967.getAdminAddress(proxyAddress: string): Promise; +``` + +Functions in this module provide access to the [ERC1967](https://eips.ethereum.org/EIPS/eip-1967) variables of a proxy contract. + +**Parameters:** + +* `proxyAddress` - the proxy address. + +**Returns:** + +* the implementation, beacon, or admin address depending on the function called. + +## beacon + +```ts +async function beacon.getImplementationAddress(beaconAddress: string): Promise; +``` + +This module provides a convenience function to get the implementation address from a beacon contract. + +**Parameters:** + +* `beaconAddress` - the beacon address. + +**Returns:** + +* the implementation address. + +**Since:** + +* `@openzeppelin/truffle-upgrades@1.12.0` + +## silenceWarnings + +```ts +function silenceWarnings() +``` + + +This function is useful for tests, but its use in production deployment scripts is discouraged. + + +Silences all subsequent warnings about the use of unsafe flags. Prints a last warning before doing so. diff --git a/docs/content/upgrades-plugins/defender-deploy.mdx b/docs/content/upgrades-plugins/defender-deploy.mdx new file mode 100644 index 00000000..d80b6a4a --- /dev/null +++ b/docs/content/upgrades-plugins/defender-deploy.mdx @@ -0,0 +1,107 @@ +--- +title: OpenZeppelin Defender with Hardhat +--- + +The Hardhat Upgrades package can use [OpenZeppelin Defender](/defender) for deployments instead of ethers.js, which allows for features such as gas pricing estimation, resubmissions, and automated bytecode and source code verification. + +## Configuration + +Create a deployment environment on OpenZeppelin Defender and provide the Team API Key and secret in your `hardhat.config.js` or `hardhat.config.ts` file under `defender`: + +```js +module.exports = + defender: { + apiKey: process.env.API_KEY, + apiSecret: process.env.API_SECRET, + +} +``` + + +The API key for the above must at least have the capability to Manage Deployments (optionally Manage Relayers is needed to create an approval process with a Relayer). You can configure your API keys at https://defender.openzeppelin.com/#/settings/api-keys. + + +## Network Selection + +The network that is used with OpenZeppelin Defender is determined by the network that Hardhat is connected to. +If you want to ensure that a specific network is used with Defender, set the `network` field in the `defender` section of your `hardhat.config.js` or `hardhat.config.ts` file: +```js +module.exports = + defender: { + apiKey: process.env.API_KEY, + apiSecret: process.env.API_SECRET, + network: "my-mainnet-fork", + +} +``` +If set, this must be the name of a public, private or forked network in Defender. If Hardhat is connected to a different network while this is set, the deployment will not occur and will throw an error instead. + + +This is required if you have multiple forked networks in Defender with the same chainId, in which case the one with name matching the `network` field will be used. + + +## Usage + +When using the [Hardhat Upgrades API functions](/upgrades-plugins/api-hardhat-upgrades), enable OpenZeppelin Defender deployments using any of the ways below. + + +Only functions that have the `useDefenderDeploy` option in their API reference support deployments through OpenZeppelin Defender. If you enable the following but use functions that do not support `useDefenderDeploy`, the first way below will cause those functions to deploy using ethers.js, whereas the second and third ways will cause those functions to give an error. + + +* Recommended: In `hardhat.config.js` or `hardhat.config.ts`, set `useDefenderDeploy: true` under `defender`. For example: + +```js +module.exports = + defender: { + apiKey: process.env.API_KEY, + apiSecret: process.env.API_SECRET, + useDefenderDeploy: true, + +} +``` + +```js +// scripts/create-box.js +const ethers, upgrades = require("hardhat"); + +async function main() + const Box = await ethers.getContractFactory("Box"); + const box = await upgrades.deployProxy(Box, [42]); + await box.waitForDeployment(); + console.log("Box deployed to:", await box.getAddress()); + + +main(); +``` + +* Use the `defender` module instead of `upgrades` from the Hardhat Runtime Environment. Use this if you want to make sure Defender is used and want to see an error if the function does not support Defender. For example: + +```js +// scripts/create-box.js +const ethers, defender = require("hardhat"); + +async function main() + const Box = await ethers.getContractFactory("Box"); + const box = await defender.deployProxy(Box, [42]); + await box.waitForDeployment(); + console.log("Box deployed to:", await box.getAddress()); + + +main(); +``` + +* Use the `useDefenderDeploy` common option. Setting this option overrides the above for specific functions. For example: + +```js +// scripts/create-box.js +const ethers, upgrades = require("hardhat"); + +async function main() + const Box = await ethers.getContractFactory("Box"); + const box = await upgrades.deployProxy(Box, [42], { useDefenderDeploy: true ); + await box.waitForDeployment(); + console.log("Box deployed to:", await box.getAddress()); +} + +main(); +``` diff --git a/docs/content/upgrades-plugins/faq.mdx b/docs/content/upgrades-plugins/faq.mdx new file mode 100644 index 00000000..34a99fe3 --- /dev/null +++ b/docs/content/upgrades-plugins/faq.mdx @@ -0,0 +1,283 @@ +--- +title: Frequently Asked Questions +--- + +## Can I change Solidity compiler versions when upgrading? + +Yes. The Solidity team guarantees that the compiler will [preserve the storage layout across versions](https://twitter.com/ethchris/status/1073692785176444928). + +## Why am I getting the error "Cannot call fallback function from the proxy admin"? + +This is due to the [Transparent Proxy Pattern](/upgrades-plugins/proxies#transparent-proxies-and-function-clashes). You shouldn’t get this error when using the OpenZeppelin Upgrades Plugins, since it uses the `ProxyAdmin` contract for managing your proxies. + +However, if you are using OpenZeppelin Contracts proxies programmatically you could potentially run into such error. The solution is to always interact with your proxies from an account that is not the admin of the proxy, unless you want to specifically call the functions of the proxy itself. + +## What does it mean for a contract to be upgrade safe? + +When deploying a proxy for a contract, there are some limitations to the contract code. In particular, the contract cannot have a constructor, and should not use the `selfdestruct` or `delegatecall` operations for security reasons. + +As a replacement for the constructor, it is common to set up an `initialize` function to take care of the contract’s initialization. You can use the [`Initializable`](./writing-upgradeable#initializers) base contract to have access to an `initializer` modifier that ensures the function is only called once. + +```solidity +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract MyContract is Initializable { + uint256 value; + function initialize(uint256 initialValue) public initializer { + value = initialValue; + } +} +``` + +Both plugins will validate that the contract you are trying to deploy complies with these rules. You can read more about how to write upgrade safe contracts [here](./writing-upgradeable). + +## How can I disable some of the checks? + +Deployment and upgrade related functions come with an optional `opts` object, which includes an `unsafeAllow` option. This can be set to disable any check performed by the plugin. The list of checks that can individually be disabled is: + +* `state-variable-assignment` +* `state-variable-immutable` +* `external-library-linking` +* `struct-definition` +* `enum-definition` +* `constructor` +* `delegatecall` +* `selfdestruct` +* `missing-public-upgradeto` +* `internal-function-storage` + +This function is a generalized version of the original `unsafeAllowCustomTypes` and `unsafeAllowLinkedLibraries` allowing any check to be manually disabled. + +For example, in order to upgrade to an implementation that contains a delegate call, you would call: + +```ts +await upgradeProxy(proxyAddress, implementationFactory, unsafeAllow: ['delegatecall'] ); +``` + +Additionally, it is possible to precisely disable checks directly from the Solidity source code using NatSpec comments. This requires Solidity >=0.8.2. + +```solidity +contract SomeContract { + function some_dangerous_function() public { + ... + /// @custom:oz-upgrades-unsafe-allow delegatecall + (bool success, bytes memory returndata) = msg.sender.delegatecall(""); + ... + } +} +``` + +This syntax can be used with the following errors: + +* `/// @custom:oz-upgrades-unsafe-allow state-variable-immutable` +* `/// @custom:oz-upgrades-unsafe-allow state-variable-assignment` +* `/// @custom:oz-upgrades-unsafe-allow external-library-linking` +* `/// @custom:oz-upgrades-unsafe-allow constructor` +* `/// @custom:oz-upgrades-unsafe-allow delegatecall` +* `/// @custom:oz-upgrades-unsafe-allow selfdestruct` + +In some cases you may want to allow multiple errors in a single line. + +```solidity +/// @custom:oz-upgrades-unsafe-allow constructor state-variable-immutable +contract SomeOtherContract { + uint256 immutable x; + constructor() { + x = block.number; + } +} +``` + +You can also use the following to allow specific errors in reachable code, which includes any referenced contracts, functions, and libraries: + +* `/// @custom:oz-upgrades-unsafe-allow-reachable delegatecall` +* `/// @custom:oz-upgrades-unsafe-allow-reachable selfdestruct` + +## Can I safely use `delegatecall` and `selfdestruct`? + + +This is an advanced technique and can put funds at risk of permanent loss. + + +It may be possible to safely use `delegatecall` and `selfdestruct` if they are guarded so that they can only be triggered through proxies and not on the implementation contract itself. A way to achieve this in Solidity is as follows. + +```solidity +abstract contract OnlyDelegateCall { + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + address private immutable self = address(this); + + function checkDelegateCall() private view { + require(address(this) != self); + } + + modifier onlyDelegateCall() { + checkDelegateCall(); + _; + } +} +``` + +```solidity +contract UsesUnsafeOperations is OnlyDelegateCall { + /// @custom:oz-upgrades-unsafe-allow selfdestruct + function destroyProxy() onlyDelegateCall { + selfdestruct(msg.sender); + } +} +``` + +## What does it mean for an implementation to be compatible? + +When upgrading a proxy from one implementation to another, the _storage layout_ of both implementations must be compatible. This means that, even though you can completely change the code of the implementation, you cannot modify the existing contract state variables. The only operation allowed is to append new state variables after the ones already declared. + +Both plugins will validate that the new implementation contract is compatible with the previous one. + +You can read more about how to make storage-compatible changes to an implementation contract [here](./writing-upgradeable#modifying-your-contracts). + +## What is a proxy admin? + +A `ProxyAdmin` is an intermediary contract that acts as the upgrader of a transparent proxy. Each `ProxyAdmin` is owned by the deployer address, or by the `initialOwner` address when deploying a transparent proxy from OpenZeppelin Contracts 5.0 or above. You can transfer the ownership of a proxy admin by calling [`transferOwnership`](/contracts/5.x/api/access#Ownable-transferOwnership-address-). + +## What is an implementation contract? + +Upgradeable deployments require at least two contracts: a proxy and an implementation. The proxy contract is the instance you and your users will interact with, and the implementation is the contract that holds the code. If you call `deployProxy` several times for the same implementation contract, several proxies will be deployed, but only one implementation contract will be used. + +When you upgrade a proxy to a new version, a new implementation contract is deployed if needed, and the proxy is set to use the new implementation contract. You can read more about the proxy upgrade pattern [here](/upgrades-plugins/proxies). + +## What is a proxy? + +A proxy is a contract that delegates all of its calls to a second contract, named an implementation contract. All state and funds are held in the proxy, but the code actually executed is that of the implementation. A proxy can be _upgraded_ by its admin to use a different implementation contract. + +You can read more about the proxy upgrade pattern [here](/upgrades-plugins/proxies). + +## Why can't I use `immutable` variables? + +Solidity 0.6.5 [introduced the `immutable` keyword](https://github.com/ethereum/solidity/releases/tag/v0.6.5) to declare a variable that can be assigned only once during construction and can be read only after construction. It does so by calculating its value during contract creation and storing its value directly into the bytecode. + +Notice that this behavior is incompatible with the way upgradeable contracts work for two reasons: + +1. Upgradeable contracts have no constructors but initializers, therefore they can’t handle immutable variables. +2. Since the immutable variable value is stored in the bytecode its value would be shared among all proxies pointing to a given contract instead of each proxy’s storage. + + +In some cases immutable variables are upgrade safe. The plugins cannot currently detect these cases automatically so they will point it out as an error anyway. You can manually disable the check using the option `unsafeAllow: ['state-variable-immutable']`, or in Solidity >=0.8.2 placing the comment `/// @custom:oz-upgrades-unsafe-allow state-variable-immutable` before the variable declaration. + + +## Why can't I use external libraries? + +At the moment, the plugins only have partial support for upgradeable contracts linked to external libraries. This is because it’s not known at compile time what implementation is going to be linked, thus making it very difficult to guarantee the safety of the upgrade operation. + +There are plans to add this functionality in the near future with certain constraints that make the issue easier to address like assuming that the external library’s source code is either present in the codebase or that it’s been deployed and mined so it can be fetched from the blockchain for analysis. + +In the meantime, you can deploy upgradeable contracts linked to external libraries by setting the `unsafeAllowLinkedLibraries` flag to true in the `deployProxy` or `upgradeProxy` calls, or including ’external-library-linking'` in the `unsafeAllow` array. Keep in mind the plugins will not verify that the linked libraries are upgrade safe. This has to be done manually for now until the full support for external libraries is implemented. + +You can follow or contribute to [this issue in GitHub](https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/52). + +## Why do I need a public `upgradeTo` or `upgradeToAndCall` function? + +When using UUPS proxies (through the `kind: 'uups'` option), the implementation contract must include one or both of the public functions `upgradeTo(address newImplementation)` or `upgradeToAndCall(address newImplementation, bytes memory data)`. This is because in the UUPS pattern the proxy does not contain an upgrading function itself, and the entire upgradeability mechanism lives on the implementation side. Thus, on every deploy and upgrade we have to make sure to include it, otherwise we may permanently disable the upgradeability of the contract. + +The recommended way to include one or both of these functions is by inheriting the `UUPSUpgradeable` contract provided in OpenZeppelin Contracts, as shown below. This contract adds the required function(s), but also contains a built-in mechanism that will check on-chain, at the time of an upgrade, that the new implementation proposed also inherits `UUPSUpgradeable` or implements the same interface. In this way, when using the Upgrades Plugins there are two layers of mitigations to prevent accidentally disabling upgradeability: an off-chain check by the plugins, and an on-chain fallback in the contract itself. + +```solidity +import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; + +contract MyContract is Initializable, ..., UUPSUpgradeable { + ... +} +``` + +Read more about the differences with the Transparent Proxy Pattern in [Transparent vs UUPS](/contracts/5.x/api/proxy#transparent-vs-uups). + +## Can I use custom types like structs and enums? + +Past versions of the plugins did not support upgradeable contracts that used custom types like structs or enums in their code or linked libraries. This is no longer the case for current versions of the plugins, and structs and enums will be automatically checked for compatibility when upgrading a contract. + +Some users who have already deployed proxies with structs and/or enums and who need to upgrade those proxies may need to use the override flag `unsafeAllowCustomTypes` for their next upgrade, after which it will no longer be necessary. If the project contains the source code for the implementation currently in use by the proxy, the plugin will attempt to recover the metadata that it needs before the upgrade, falling back to the override flag if this is not possible. + +## How can I rename a variable, or change its type? + +Renaming a variable is disallowed by default because there is a chance that a renaming is actually an accidental reordering. For example, if variables `uint a; uint b;` are upgraded to `uint b; uint a;`, if renaming was simply allowed this would not be seen as a mistake, but it could have been an accident, especially when multiple inheritance is involved. + +It is possible to disable this check by passing the option `unsafeAllowRenames: true`. A more granular approach is to use a docstring comment `/// @custom:oz-renamed-from ` right above the variable that is being renamed, for example: + +```solidity +contract V1 { + uint x; +} +contract V2 { + /// @custom:oz-renamed-from x + uint y; +} +``` + +Changing the type of a variable is not allowed either, even in cases where the types have the same size and alignment, for the similar reason explained above. As long as we can guarantee that the rest of the layout is not affected by this type change, it is also possible to override this check by placing a docstring comment `/// @custom:oz-retyped-from `. + +```solidity +contract V1 { + bool x; +} +contract V2 { + /// @custom:oz-retyped-from bool + uint8 x; +} +``` + +Docstring comments don’t yet work for struct members, due to a current Solidity limitation. + +## How can I use internal functions in storage variables? + +Internal functions in storage variables are code pointers which will no longer be valid after an upgrade, because the code will move around and the pointer would change. To avoid this issue, you can declare those functions as external, or avoid code pointers in storage altogether and define an `enum` that you will use with a dispatcher function to select from the list of available functions. If you must use internal functions, those internal functions need to be reassigned during each upgrade. + +For example, the `messageFunction` variable in the following contract is not upgrade safe. Attempting to call `showMessage()` after an upgrade would likely result in a revert. +```solidity +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract V1 is Initializable { + function() internal pure returns (string memory) messageFunction; + + function initialize() initializer public { + messageFunction = hello; + } + + function hello() internal pure returns (string memory) { + return "Hello, World!"; + } + + function showMessage() public view returns (string memory) + return messageFunction(); + } + ... +} +``` + +To allow the above contract to be deployed by the Upgrades Plugins, you can disable the `internal-function-storage` check according to [How can I disable some of the checks?](#how-can-i-disable-some-of-the-checks?), but ensure you follow the steps below to reassign the internal function during upgrades. + +In new versions of this contract, assign the internal function in the storage variable again, for example by using a [reinitializer](/contracts/5.x/api/proxy#Initializable-reinitializer-uint64-): +```solidity +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "./V1.sol"; + +contract V2 is V1 { + function initializeV2() reinitializer(2) public { + messageFunction = hello; + } + ... +} +``` + +Then when upgrading, call the reinitializer function as part of the upgrade process, for example in Hardhat: +```ts +await upgrades.upgradeProxy(PROXY_ADDRESS, ContractFactoryV2, + call: 'initializeV2', + unsafeAllow: ['internal-function-storage'] +); +``` +or in Foundry: +```solidity +Upgrades.upgradeProxy( + PROXY_ADDRESS, + "V2.sol", + abi.encodeCall(V2.initializeV2, ()) +); +``` diff --git a/docs/content/upgrades-plugins/foundry-defender.mdx b/docs/content/upgrades-plugins/foundry-defender.mdx new file mode 100644 index 00000000..2cde3ce3 --- /dev/null +++ b/docs/content/upgrades-plugins/foundry-defender.mdx @@ -0,0 +1,4 @@ +--- +title: foundry-defender +--- + diff --git a/docs/content/upgrades-plugins/foundry/api/Defender.mdx b/docs/content/upgrades-plugins/foundry/api/Defender.mdx new file mode 100644 index 00000000..f7002fdd --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/api/Defender.mdx @@ -0,0 +1,232 @@ +--- +title: "Defender" +description: "Smart contract Defender utilities and implementations" +--- + + + +
+ +## `Defender` + + + + + +
+ +```solidity +import { Defender } from "openzeppelin-foundry-upgrades/Defender.sol"; +``` + +Library for interacting with OpenZeppelin Defender from Forge scripts or tests. + +
+

Functions

+
+- [deployContract(contractName)](#Defender-deployContract-string-) +- [deployContract(contractName, defenderOpts)](#Defender-deployContract-string-struct-DefenderOptions-) +- [deployContract(contractName, constructorData)](#Defender-deployContract-string-bytes-) +- [deployContract(contractName, constructorData, defenderOpts)](#Defender-deployContract-string-bytes-struct-DefenderOptions-) +- [proposeUpgrade(proxyAddress, newImplementationContractName, opts)](#Defender-proposeUpgrade-address-string-struct-Options-) +- [getDeployApprovalProcess()](#Defender-getDeployApprovalProcess--) +- [getUpgradeApprovalProcess()](#Defender-getUpgradeApprovalProcess--) +
+
+ + + +
+
+

deployContract(string contractName) → address

+
+

internal

+# +
+
+
+ +Deploys a contract to the current network using OpenZeppelin Defender. + + +Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe. + + +NOTE: If using an EOA or Safe to deploy, go to [Defender deploy](https://defender.openzeppelin.com/v2/#/deploy) to submit the pending deployment while the script is running. +The script waits for the deployment to complete before it continues. + +
+
+ + + +
+
+

deployContract(string contractName, struct DefenderOptions defenderOpts) → address

+
+

internal

+# +
+
+
+ +Deploys a contract to the current network using OpenZeppelin Defender. + + +Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe. + + +NOTE: If using an EOA or Safe to deploy, go to [Defender deploy](https://defender.openzeppelin.com/v2/#/deploy) to submit the pending deployment while the script is running. +The script waits for the deployment to complete before it continues. + +
+
+ + + +
+
+

deployContract(string contractName, bytes constructorData) → address

+
+

internal

+# +
+
+
+ +Deploys a contract with constructor arguments to the current network using OpenZeppelin Defender. + + +Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe. + + +NOTE: If using an EOA or Safe to deploy, go to [Defender deploy](https://defender.openzeppelin.com/v2/#/deploy) to submit the pending deployment while the script is running. +The script waits for the deployment to complete before it continues. + +
+
+ + + +
+
+

deployContract(string contractName, bytes constructorData, struct DefenderOptions defenderOpts) → address

+
+

internal

+# +
+
+
+ +Deploys a contract with constructor arguments to the current network using OpenZeppelin Defender. + + +Do not use this function directly if you are deploying an upgradeable contract. This function does not validate whether the contract is upgrade safe. + + +NOTE: If using an EOA or Safe to deploy, go to [Defender deploy](https://defender.openzeppelin.com/v2/#/deploy) to submit the pending deployment while the script is running. +The script waits for the deployment to complete before it continues. + +
+
+ + + +
+
+

proposeUpgrade(address proxyAddress, string newImplementationContractName, struct Options opts) → struct ProposeUpgradeResponse

+
+

internal

+# +
+
+
+ +Proposes an upgrade to an upgradeable proxy using OpenZeppelin Defender. + +This function validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract using Defender, +and proposes an upgrade to the new implementation contract using an upgrade approval process on Defender. + +Supported for UUPS or Transparent proxies. Not currently supported for beacon proxies or beacons. +For beacons, use `Upgrades.prepareUpgrade` along with a transaction proposal on Defender to upgrade the beacon to the deployed implementation. + +Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + + +Ensure that the reference contract is the same as the current implementation contract that the proxy is pointing to. +This function does not validate that the reference contract is the current implementation. + + +NOTE: If using an EOA or Safe to deploy, go to [Defender deploy](https://defender.openzeppelin.com/v2/#/deploy) to submit the pending deployment of the new implementation contract while the script is running. +The script waits for the deployment to complete before it continues. + +
+
+ + + +
+
+

getDeployApprovalProcess() → struct ApprovalProcessResponse

+
+

internal

+# +
+
+
+ +Gets the default deploy approval process configured for your deployment environment on OpenZeppelin Defender. + +
+
+ + + +
+
+

getUpgradeApprovalProcess() → struct ApprovalProcessResponse

+
+

internal

+# +
+
+
+ +Gets the default upgrade approval process configured for your deployment environment on OpenZeppelin Defender. +For example, this is useful for determining the default multisig wallet that you can use in your scripts to assign as the owner of your proxy. + +
+
+ + + +
+ +## `ProposeUpgradeResponse` + + + + + +
+ +```solidity +import { ProposeUpgradeResponse } from "openzeppelin-foundry-upgrades/Defender.sol"; +``` + + + +
+ +## `ApprovalProcessResponse` + + + + + +
+ +```solidity +import { ApprovalProcessResponse } from "openzeppelin-foundry-upgrades/Defender.sol"; +``` + diff --git a/docs/content/upgrades-plugins/foundry/api/LegacyUpgrades.mdx b/docs/content/upgrades-plugins/foundry/api/LegacyUpgrades.mdx new file mode 100644 index 00000000..1a512d97 --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/api/LegacyUpgrades.mdx @@ -0,0 +1,471 @@ +--- +title: "LegacyUpgrades" +description: "Smart contract LegacyUpgrades utilities and implementations" +--- + + + +
+ +## `Upgrades` + + + + + +
+ +```solidity +import { Upgrades } from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol"; +``` + +Library for managing upgradeable contracts from Forge scripts or tests. + +NOTE: Only for upgrading existing deployments using OpenZeppelin Contracts v4. +For new deployments, use OpenZeppelin Contracts v5 and Upgrades.sol. + +
+

Functions

+
+- [upgradeProxy(proxy, contractName, data, opts)](#Upgrades-upgradeProxy-address-string-bytes-struct-Options-) +- [upgradeProxy(proxy, contractName, data)](#Upgrades-upgradeProxy-address-string-bytes-) +- [upgradeProxy(proxy, contractName, data, opts, tryCaller)](#Upgrades-upgradeProxy-address-string-bytes-struct-Options-address-) +- [upgradeProxy(proxy, contractName, data, tryCaller)](#Upgrades-upgradeProxy-address-string-bytes-address-) +- [upgradeBeacon(beacon, contractName, opts)](#Upgrades-upgradeBeacon-address-string-struct-Options-) +- [upgradeBeacon(beacon, contractName)](#Upgrades-upgradeBeacon-address-string-) +- [upgradeBeacon(beacon, contractName, opts, tryCaller)](#Upgrades-upgradeBeacon-address-string-struct-Options-address-) +- [upgradeBeacon(beacon, contractName, tryCaller)](#Upgrades-upgradeBeacon-address-string-address-) +- [validateUpgrade(contractName, opts)](#Upgrades-validateUpgrade-string-struct-Options-) +- [prepareUpgrade(contractName, opts)](#Upgrades-prepareUpgrade-string-struct-Options-) +- [getAdminAddress(proxy)](#Upgrades-getAdminAddress-address-) +- [getImplementationAddress(proxy)](#Upgrades-getImplementationAddress-address-) +- [getBeaconAddress(proxy)](#Upgrades-getBeaconAddress-address-) +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data, struct Options opts)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data, struct Options opts, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName, struct Options opts)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName, struct Options opts, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

validateUpgrade(string contractName, struct Options opts)

+
+

internal

+# +
+
+
+ +Validates a new implementation contract in comparison with a reference contract, but does not deploy it. + +Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

prepareUpgrade(string contractName, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract, +and returns its address. + +Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + +Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from your deployment environment. + +
+
+ + + +
+
+

getAdminAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the admin address of a transparent proxy from its ERC1967 admin storage slot. + +
+
+ + + +
+
+

getImplementationAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the implementation address of a transparent or UUPS proxy from its ERC1967 implementation storage slot. + +
+
+ + + +
+
+

getBeaconAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the beacon address of a beacon proxy from its ERC1967 beacon storage slot. + +
+
+ + + +
+ +## `UnsafeUpgrades` + + + + + +
+ +```solidity +import { UnsafeUpgrades } from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol"; +``` + +Library for managing upgradeable contracts from Forge tests, without validations. + +Can be used with `forge coverage`. Requires implementation contracts to be instantiated first. +Does not require `--ffi` and does not require a clean compilation before each run. + +Not supported for OpenZeppelin Defender deployments. + + +Not recommended for use in Forge scripts. +`UnsafeUpgrades` does not validate whether your contracts are upgrade safe or whether new implementations are compatible with previous ones. +Use `Upgrades` if you want validations to be run. + + +NOTE: Only for upgrading existing deployments using OpenZeppelin Contracts v4. +For new deployments, use OpenZeppelin Contracts v5 and Upgrades.sol. + +
+

Functions

+
+- [upgradeProxy(proxy, newImpl, data)](#UnsafeUpgrades-upgradeProxy-address-address-bytes-) +- [upgradeProxy(proxy, newImpl, data, tryCaller)](#UnsafeUpgrades-upgradeProxy-address-address-bytes-address-) +- [upgradeBeacon(beacon, newImpl)](#UnsafeUpgrades-upgradeBeacon-address-address-) +- [upgradeBeacon(beacon, newImpl, tryCaller)](#UnsafeUpgrades-upgradeBeacon-address-address-address-) +- [getAdminAddress(proxy)](#UnsafeUpgrades-getAdminAddress-address-) +- [getImplementationAddress(proxy)](#UnsafeUpgrades-getImplementationAddress-address-) +- [getBeaconAddress(proxy)](#UnsafeUpgrades-getBeaconAddress-address-) +
+
+ + + +
+
+

upgradeProxy(address proxy, address newImpl, bytes data)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract address. Only supported for UUPS or transparent proxies. + +
+
+ + + +
+
+

upgradeProxy(address proxy, address newImpl, bytes data, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract address. Only supported for UUPS or transparent proxies. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, address newImpl)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract address. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, address newImpl, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract address. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

getAdminAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the admin address of a transparent proxy from its ERC1967 admin storage slot. + +
+
+ + + +
+
+

getImplementationAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the implementation address of a transparent or UUPS proxy from its ERC1967 implementation storage slot. + +
+
+ + + +
+
+

getBeaconAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the beacon address of a beacon proxy from its ERC1967 beacon storage slot. + +
+
+ diff --git a/docs/content/upgrades-plugins/foundry/api/Options.mdx b/docs/content/upgrades-plugins/foundry/api/Options.mdx new file mode 100644 index 00000000..0d2716fb --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/api/Options.mdx @@ -0,0 +1,53 @@ +--- +title: "Options" +description: "Smart contract Options utilities and implementations" +--- + + + +
+ +## `Options` + + + + + +
+ +```solidity +import { Options } from "openzeppelin-foundry-upgrades/Options.sol"; +``` + + + +
+ +## `DefenderOptions` + + + + + +
+ +```solidity +import { DefenderOptions } from "openzeppelin-foundry-upgrades/Options.sol"; +``` + + + +
+ +## `TxOverrides` + + + + + +
+ +```solidity +import { TxOverrides } from "openzeppelin-foundry-upgrades/Options.sol"; +``` + diff --git a/docs/content/upgrades-plugins/foundry/api/Upgrades.mdx b/docs/content/upgrades-plugins/foundry/api/Upgrades.mdx new file mode 100644 index 00000000..4bad6717 --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/api/Upgrades.mdx @@ -0,0 +1,721 @@ +--- +title: "Upgrades" +description: "Smart contract Upgrades utilities and implementations" +--- + + + +
+ +## `Upgrades` + + + + + +
+ +```solidity +import { Upgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +``` + +Library for deploying and managing upgradeable contracts from Forge scripts or tests. + +NOTE: Requires OpenZeppelin Contracts v5 or higher. + +
+

Functions

+
+- [deployUUPSProxy(contractName, initializerData, opts)](#Upgrades-deployUUPSProxy-string-bytes-struct-Options-) +- [deployUUPSProxy(contractName, initializerData)](#Upgrades-deployUUPSProxy-string-bytes-) +- [deployTransparentProxy(contractName, initialOwner, initializerData, opts)](#Upgrades-deployTransparentProxy-string-address-bytes-struct-Options-) +- [deployTransparentProxy(contractName, initialOwner, initializerData)](#Upgrades-deployTransparentProxy-string-address-bytes-) +- [upgradeProxy(proxy, contractName, data, opts)](#Upgrades-upgradeProxy-address-string-bytes-struct-Options-) +- [upgradeProxy(proxy, contractName, data)](#Upgrades-upgradeProxy-address-string-bytes-) +- [upgradeProxy(proxy, contractName, data, opts, tryCaller)](#Upgrades-upgradeProxy-address-string-bytes-struct-Options-address-) +- [upgradeProxy(proxy, contractName, data, tryCaller)](#Upgrades-upgradeProxy-address-string-bytes-address-) +- [deployBeacon(contractName, initialOwner, opts)](#Upgrades-deployBeacon-string-address-struct-Options-) +- [deployBeacon(contractName, initialOwner)](#Upgrades-deployBeacon-string-address-) +- [upgradeBeacon(beacon, contractName, opts)](#Upgrades-upgradeBeacon-address-string-struct-Options-) +- [upgradeBeacon(beacon, contractName)](#Upgrades-upgradeBeacon-address-string-) +- [upgradeBeacon(beacon, contractName, opts, tryCaller)](#Upgrades-upgradeBeacon-address-string-struct-Options-address-) +- [upgradeBeacon(beacon, contractName, tryCaller)](#Upgrades-upgradeBeacon-address-string-address-) +- [deployBeaconProxy(beacon, data)](#Upgrades-deployBeaconProxy-address-bytes-) +- [deployBeaconProxy(beacon, data, opts)](#Upgrades-deployBeaconProxy-address-bytes-struct-Options-) +- [validateImplementation(contractName, opts)](#Upgrades-validateImplementation-string-struct-Options-) +- [deployImplementation(contractName, opts)](#Upgrades-deployImplementation-string-struct-Options-) +- [validateUpgrade(contractName, opts)](#Upgrades-validateUpgrade-string-struct-Options-) +- [prepareUpgrade(contractName, opts)](#Upgrades-prepareUpgrade-string-struct-Options-) +- [getAdminAddress(proxy)](#Upgrades-getAdminAddress-address-) +- [getImplementationAddress(proxy)](#Upgrades-getImplementationAddress-address-) +- [getBeaconAddress(proxy)](#Upgrades-getBeaconAddress-address-) +
+
+ + + +
+
+

deployUUPSProxy(string contractName, bytes initializerData, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Deploys a UUPS proxy using the given contract as the implementation. + +
+
+ + + +
+
+

deployUUPSProxy(string contractName, bytes initializerData) → address

+
+

internal

+# +
+
+
+ +Deploys a UUPS proxy using the given contract as the implementation. + +
+
+ + + +
+
+

deployTransparentProxy(string contractName, address initialOwner, bytes initializerData, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Deploys a transparent proxy using the given contract as the implementation. + +
+
+ + + +
+
+

deployTransparentProxy(string contractName, address initialOwner, bytes initializerData) → address

+
+

internal

+# +
+
+
+ +Deploys a transparent proxy using the given contract as the implementation. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data, struct Options opts)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data, struct Options opts, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

upgradeProxy(address proxy, string contractName, bytes data, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

deployBeacon(string contractName, address initialOwner, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Deploys an upgradeable beacon using the given contract as the implementation. + +
+
+ + + +
+
+

deployBeacon(string contractName, address initialOwner) → address

+
+

internal

+# +
+
+
+ +Deploys an upgradeable beacon using the given contract as the implementation. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName, struct Options opts)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName, struct Options opts, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, string contractName, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract. + +Requires that either the `referenceContract` option is set, or the new implementation contract has a `@custom:oz-upgrades-from ` annotation. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

deployBeaconProxy(address beacon, bytes data) → address

+
+

internal

+# +
+
+
+ +Deploys a beacon proxy using the given beacon and call data. + +
+
+ + + +
+
+

deployBeaconProxy(address beacon, bytes data, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Deploys a beacon proxy using the given beacon and call data. + +
+
+ + + +
+
+

validateImplementation(string contractName, struct Options opts)

+
+

internal

+# +
+
+
+ +Validates an implementation contract, but does not deploy it. + +
+
+ + + +
+
+

deployImplementation(string contractName, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Validates and deploys an implementation contract, and returns its address. + +
+
+ + + +
+
+

validateUpgrade(string contractName, struct Options opts)

+
+

internal

+# +
+
+
+ +Validates a new implementation contract in comparison with a reference contract, but does not deploy it. + +Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + +
+
+ + + +
+
+

prepareUpgrade(string contractName, struct Options opts) → address

+
+

internal

+# +
+
+
+ +Validates a new implementation contract in comparison with a reference contract, deploys the new implementation contract, +and returns its address. + +Requires that either the `referenceContract` option is set, or the contract has a `@custom:oz-upgrades-from ` annotation. + +Use this method to prepare an upgrade to be run from an admin address you do not control directly or cannot use from your deployment environment. + +
+
+ + + +
+
+

getAdminAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the admin address of a transparent proxy from its ERC1967 admin storage slot. + +
+
+ + + +
+
+

getImplementationAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the implementation address of a transparent or UUPS proxy from its ERC1967 implementation storage slot. + +
+
+ + + +
+
+

getBeaconAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the beacon address of a beacon proxy from its ERC1967 beacon storage slot. + +
+
+ + + +
+ +## `UnsafeUpgrades` + + + + + +
+ +```solidity +import { UnsafeUpgrades } from "openzeppelin-foundry-upgrades/Upgrades.sol"; +``` + +Library for deploying and managing upgradeable contracts from Forge tests, without validations. + +Can be used with `forge coverage`. Requires implementation contracts to be instantiated first. +Does not require `--ffi` and does not require a clean compilation before each run. + +Not supported for OpenZeppelin Defender deployments. + + +Not recommended for use in Forge scripts. +`UnsafeUpgrades` does not validate whether your contracts are upgrade safe or whether new implementations are compatible with previous ones. +Use `Upgrades` if you want validations to be run. + + +NOTE: Requires OpenZeppelin Contracts v5 or higher. + +
+

Functions

+
+- [deployUUPSProxy(impl, initializerData)](#UnsafeUpgrades-deployUUPSProxy-address-bytes-) +- [deployTransparentProxy(impl, initialOwner, initializerData)](#UnsafeUpgrades-deployTransparentProxy-address-address-bytes-) +- [upgradeProxy(proxy, newImpl, data)](#UnsafeUpgrades-upgradeProxy-address-address-bytes-) +- [upgradeProxy(proxy, newImpl, data, tryCaller)](#UnsafeUpgrades-upgradeProxy-address-address-bytes-address-) +- [deployBeacon(impl, initialOwner)](#UnsafeUpgrades-deployBeacon-address-address-) +- [upgradeBeacon(beacon, newImpl)](#UnsafeUpgrades-upgradeBeacon-address-address-) +- [upgradeBeacon(beacon, newImpl, tryCaller)](#UnsafeUpgrades-upgradeBeacon-address-address-address-) +- [deployBeaconProxy(beacon, data)](#UnsafeUpgrades-deployBeaconProxy-address-bytes-) +- [getAdminAddress(proxy)](#UnsafeUpgrades-getAdminAddress-address-) +- [getImplementationAddress(proxy)](#UnsafeUpgrades-getImplementationAddress-address-) +- [getBeaconAddress(proxy)](#UnsafeUpgrades-getBeaconAddress-address-) +
+
+ + + +
+
+

deployUUPSProxy(address impl, bytes initializerData) → address

+
+

internal

+# +
+
+
+ +Deploys a UUPS proxy using the given contract address as the implementation. + +
+
+ + + +
+
+

deployTransparentProxy(address impl, address initialOwner, bytes initializerData) → address

+
+

internal

+# +
+
+
+ +Deploys a transparent proxy using the given contract address as the implementation. + +
+
+ + + +
+
+

upgradeProxy(address proxy, address newImpl, bytes data)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract address. Only supported for UUPS or transparent proxies. + +
+
+ + + +
+
+

upgradeProxy(address proxy, address newImpl, bytes data, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a proxy to a new implementation contract address. Only supported for UUPS or transparent proxies. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

deployBeacon(address impl, address initialOwner) → address

+
+

internal

+# +
+
+
+ +Deploys an upgradeable beacon using the given contract address as the implementation. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, address newImpl)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract address. + +
+
+ + + +
+
+

upgradeBeacon(address beacon, address newImpl, address tryCaller)

+
+

internal

+# +
+
+
+ +Upgrades a beacon to a new implementation contract address. + +This function provides an additional `tryCaller` parameter to test an upgrade using a specific caller address. +Use this if you encounter `OwnableUnauthorizedAccount` errors in your tests. + +
+
+ + + +
+
+

deployBeaconProxy(address beacon, bytes data) → address

+
+

internal

+# +
+
+
+ +Deploys a beacon proxy using the given beacon and call data. + +
+
+ + + +
+
+

getAdminAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the admin address of a transparent proxy from its ERC1967 admin storage slot. + +
+
+ + + +
+
+

getImplementationAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the implementation address of a transparent or UUPS proxy from its ERC1967 implementation storage slot. + +
+
+ + + +
+
+

getBeaconAddress(address proxy) → address

+
+

internal

+# +
+
+
+ +Gets the beacon address of a beacon proxy from its ERC1967 beacon storage slot. + +
+
+ diff --git a/docs/content/upgrades-plugins/foundry/api/index.mdx b/docs/content/upgrades-plugins/foundry/api/index.mdx new file mode 100644 index 00000000..d36a7617 --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/api/index.mdx @@ -0,0 +1,51 @@ +--- +title: OpenZeppelin Foundry Upgrades API +--- + +## Contract name formats + +Contract names must be provided in specific formats depending on context. The following are the required formats for each context: + +### Foundry artifact format + +Contexts: + +* `contractName` parameter +* `referenceContract` option if `referenceBuildInfoDir` option is not set + +Can be in any of the following forms according to Foundry's [getCode](https://book.getfoundry.sh/cheatcodes/get-code) cheatcode: + +* the Solidity file name, e.g. `ContractV1.sol` +* the Solidity file name and the contract name, e.g. `ContractV1.sol:ContractV1` +* the artifact path relative to the project root directory, e.g. `out/ContractV1.sol/ContractV1.json` + +### Annotation format + +Contexts: + +* `@custom:oz-upgrades-from ` annotation +* `referenceContract` option if `referenceBuildInfoDir` option is set + +Can be in any of the following forms according to the [OpenZeppelin Upgrades CLI](/upgrades-plugins/api-core#define-reference-contracts): + +* the contract name, e.g. `ContractV1` +* fully qualified contract name, e.g. `contracts/tokens/ContractV1.sol:ContractV1` + +If the `referenceBuildInfoDir` option is set, include the build info directory short name as a prefix, resulting in one of the following forms: + +* the build info directory short name and the contract name, e.g. `build-info-v1:ContractV1` +* the build info directory short name and the fully qualified contract name, e.g. `build-info-v1:contracts/tokens/ContractV1.sol:ContractV1` + +## Core Libraries + +### [Upgrades](/upgrades-plugins/foundry/api/Upgrades) +Library for deploying and managing upgradeable contracts from Forge scripts or tests. Requires OpenZeppelin Contracts v5 or higher. + +### [LegacyUpgrades](/upgrades-plugins/foundry/api/LegacyUpgrades) +Library for managing upgradeable contracts from Forge scripts or tests. Only for upgrading existing deployments using OpenZeppelin Contracts v4. + +### [Defender](/upgrades-plugins/foundry/api/Defender) +Library for interacting with OpenZeppelin Defender from Forge scripts or tests. + +### [Options](/upgrades-plugins/foundry/api/Options) +Configuration options and structs used throughout the Upgrades libraries. diff --git a/docs/content/upgrades-plugins/foundry/foundry-defender.mdx b/docs/content/upgrades-plugins/foundry/foundry-defender.mdx new file mode 100644 index 00000000..5a23eca5 --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/foundry-defender.mdx @@ -0,0 +1,180 @@ +--- +title: OpenZeppelin Defender with Foundry +--- + +OpenZeppelin Foundry Upgrades can be used for performing deployments through [OpenZeppelin Defender](/defender), which allows for features such as gas pricing estimation, resubmissions, and automated bytecode and source code verification. + + +Defender deployments are ***always*** broadcast to a live network, regardless of whether you are using the `broadcast` cheatcode. +The recommended pattern is to separate Defender scripts from scripts that rely on network forking and simulations, to avoid mixing simulation and live network data. + + +## Installation + +See [Using with Foundry - Installation](/upgrades-plugins/foundry/foundry-upgrades#installation). + +## Prerequisites +1. Install [Node.js](https://nodejs.org/). +2. Configure your `foundry.toml` to enable ffi, ast, build info and storage layout: + +```toml +[profile.default] +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] +``` + + +Metadata must also be included in the compiler output, which it is by default. + + +1. Set the following environment variables in your `.env` file at your project root, using your Team API key and secret from OpenZeppelin Defender: + +``` +DEFENDER_KEY= +DEFENDER_SECRET= +``` + + +The API key for the above must at least have the capability to Manage Deployments (optionally Manage Relayers is needed to create an approval process with a Relayer). You can configure your API keys at https://defender.openzeppelin.com/#/settings/api-keys. + + +## Network Selection + +The network that is used with OpenZeppelin Defender is determined by the network that Foundry is connected to. +If you want to ensure that a specific network is used with Defender, set the `DEFENDER_NETWORK` environment variable in your `.env` file, for example: + +``` +DEFENDER_NETWORK=my-mainnet-fork +``` +If set, this must be the name of a public, private or forked network in Defender. If Foundry is connected to a different network while this is set, the deployment will not occur and will throw an error instead. + + +This is required if you have multiple forked networks in Defender with the same chainId, in which case the one with name matching the `DEFENDER_NETWORK` environment variable will be used. + + +## Usage + +### Upgradeable Contracts + +If you are deploying upgradeable contracts, use the `Upgrades` library as described in [Using with Foundry - Installation](/upgrades-plugins/foundry/foundry-upgrades#installation) but set the option `defender.useDefenderDeploy = true` when calling functions to cause all deployments to occur through OpenZeppelin Defender. + +***Example 1 - Deploying a proxy***: +To deploy a UUPS proxy, create a script called `Defender.s.sol` like the following: +```solidity +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; + +import {Defender, ApprovalProcessResponse} from "openzeppelin-foundry-upgrades/Defender.sol"; +import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; + +import {MyContract} from "../src/MyContract.sol"; + +contract DefenderScript is Script { + function setUp() public {} + + function run() public { + ApprovalProcessResponse memory upgradeApprovalProcess = Defender.getUpgradeApprovalProcess(); + + if (upgradeApprovalProcess.via == address(0)) { + revert(string.concat("Upgrade approval process with id ", upgradeApprovalProcess.approvalProcessId, " has no assigned address")); + } + + Options memory opts; + opts.defender.useDefenderDeploy = true; + + address proxy = Upgrades.deployUUPSProxy( + "MyContract.sol", + abi.encodeCall(MyContract.initialize, ("Hello World", upgradeApprovalProcess.via)), + opts + ); + + console.log("Deployed proxy to address", proxy); + } +} +``` + +Then run the following command: +```console +forge script --force --rpc-url +``` + +The above example assumes the implementation contract takes an initial owner address as an argument for its `initialize` function. The script retrieves the address associated with the upgrade approval process configured in Defender (such as a multisig address), and uses that address as the initial owner so that it can have upgrade rights for the proxy. + +This example calls the `Upgrades.deployUUPSProxy` function with the `defender.useDefenderDeploy` option to deploy both the implementation contract and a UUPS proxy to the connected network using Defender. The function waits for the deployments to complete, which may take a few minutes per contract, then returns with the deployed proxy address. While the function is waiting, you can monitor your deployment status in OpenZeppelin Defender’s [Deploy module](https://defender.openzeppelin.com/v2/#/deploy). + + +If using an EOA or Safe to deploy, you must submit the pending deployments in Defender while the script is running. The script waits for each deployment to complete before it continues. + + +***Example 2 - Proposing an upgrade to a proxy***: +To propose an upgrade through Defender, create a script like the following: +```solidity +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; + +import {MyContractV2} from "../src/MyContractV2.sol"; + +import {ProposeUpgradeResponse, Defender, Options} from "openzeppelin-foundry-upgrades/Defender.sol"; + +contract DefenderScript is Script { + function setUp() public {} + + function run() public { + Options memory opts; + ProposeUpgradeResponse memory response = Defender.proposeUpgrade( + , + "MyContractV2.sol", + opts + ); + console.log("Proposal id", response.proposalId); + console.log("Url", response.url); + } +} +``` + +Then run the script as in Example 1, and go the resulting URL to review and approve the upgrade proposal. + +### Non-Upgradeable Contracts + +If you are deploying non-upgradeable contracts, import the `Defender` library from `Defender.sol` and use its functions to deploy contracts through OpenZeppelin Defender. + +***Example:*** + +To deploy a non-upgradeable contract, create a script called `Defender.s.sol` like the following: +```solidity +pragma solidity ^0.8.20; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; + +import {MyContract} from "../src/MyContract.sol"; + +import {Defender} from "openzeppelin-foundry-upgrades/Defender.sol"; + +contract DefenderScript is Script { + function setUp() public {} + + function run() public { + address deployed = Defender.deployContract("MyContract.sol", abi.encode("arguments for the constructor")); + console.log("Deployed contract to address", deployed); + } +} +``` + +Then run the following command: +```console +forge script --force --rpc-url +``` + +The above example calls the `Defender.deployContract` function to deploy the specified contract to the connected network using Defender. The function waits for the deployment to complete, which may take a few minutes, then returns with the deployed contract address. While the function is waiting, you can monitor your deployment status in OpenZeppelin Defender’s [Deploy module](https://defender.openzeppelin.com/v2/#/deploy). + + +If using an EOA or Safe to deploy, you must submit the pending deployment in Defender while the script is running. The script waits for the deployment to complete before it continues. + diff --git a/docs/content/upgrades-plugins/foundry/foundry-upgrades.mdx b/docs/content/upgrades-plugins/foundry/foundry-upgrades.mdx new file mode 100644 index 00000000..dae565ba --- /dev/null +++ b/docs/content/upgrades-plugins/foundry/foundry-upgrades.mdx @@ -0,0 +1,270 @@ +--- +title: Using with Foundry +--- + +Foundry library for deploying and managing upgradeable contracts, which includes upgrade safety validations. + +## Installation + +Follow one of the sections below depending on which version of OpenZeppelin Contracts you are using. OpenZeppelin Contracts v5 is required for new deployments. + +### Using OpenZeppelin Contracts v5 + +Run these commands: + +```console +forge install foundry-rs/forge-std +forge install OpenZeppelin/openzeppelin-foundry-upgrades +forge install OpenZeppelin/openzeppelin-contracts-upgradeable +``` + +Set the following in `remappings.txt`, replacing any previous definitions of these remappings: + +``` +@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +``` + + +The above remappings mean that both `@openzeppelin/contracts/` (including proxy contracts deployed by this library) and `@openzeppelin/contracts-upgradeable/` come from your installation of the `openzeppelin-contracts-upgradeable` submodule and its subdirectories, which includes its own transitive copy of `openzeppelin-contracts` of the same release version number. This format is needed for Etherscan verification to work. Particularly, any copies of `openzeppelin-contracts` that you install separately are NOT used. + + +### Using OpenZeppelin Contracts v4 + +Run these commands, replacing `v4.9.6` with the specific version of OpenZeppelin Contracts that you are using: + +```console +forge install foundry-rs/forge-std +forge install OpenZeppelin/openzeppelin-foundry-upgrades +forge install OpenZeppelin/openzeppelin-contracts@v4.9.6 +forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v4.9.6 +``` + +Set the following in `remappings.txt`: + +``` +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +``` + + +Use `LegacyUpgrades.sol` instead of `Upgrades.sol` to upgrade existing deployments that were created with OpenZeppelin Contracts v4. + + +### Optional: Alternative installation methods + +#### NPM + +Follow the steps above, but instead of running `forge install OpenZeppelin/openzeppelin-foundry-upgrades`, use this command instead: +```console +npm install @openzeppelin/foundry-upgrades +``` + +Then add the following additional line to `remappings.txt`, in addition to the ones described above: +```console +openzeppelin-foundry-upgrades/=node_modules/@openzeppelin/foundry-upgrades/src/ +``` + +#### Soldeer + +Follow the steps above, but instead of running `forge install OpenZeppelin/openzeppelin-foundry-upgrades`, use one of the install commands described in https://soldeer.xyz/project/openzeppelin-foundry-upgrades + +Then add the following additional line to `remappings.txt`, in addition to the ones described above (replace `0.3.6` with the version of the plugin that you installed): +```console +openzeppelin-foundry-upgrades/=dependencies/openzeppelin-foundry-upgrades-0.3.6/src/ +``` + +## Foundry Requirements + +This library requires [forge-std](https://github.com/foundry-rs/forge-std) version 1.9.5 or higher. + +## Before Running + +This library uses the [OpenZeppelin Upgrades CLI](/upgrades-plugins/api-core) for upgrade safety validations, which are run by default during deployments and upgrades. + +If you want to be able to run upgrade safety validations, the following are needed: + +1. Install [Node.js](https://nodejs.org/). +2. Configure your `foundry.toml` to enable ffi, ast, build info and storage layout: + +```toml +[profile.default] +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] +``` + +1. If you are upgrading your contract from a previous version, add the `@custom:oz-upgrades-from ` annotation to the new version of your contract according to [Define Reference Contracts](/upgrades-plugins/api-core#define-reference-contracts) or specify the `referenceContract` option when calling the library’s functions. +2. Run `forge clean` before running your Foundry script or tests, or include the `--force` option when running `forge script` or `forge test`. + +If you do not want to run upgrade safety validations, you can skip the above steps and use the [`unsafeSkipAllChecks` option](/upgrades-plugins/foundry/api/Options) when calling the `Upgrades` library’s functions, or use the `UnsafeUpgrades` library instead. Note that these are dangerous options meant to be used as a last resort. + +### Optional: Custom output directory + +By default, this library assumes your Foundry output directory is set to "out". + +If you want to use a custom output directory, set it in your `foundry.toml` and provide read permissions for the directory. For example (replace `my-output-dir` with the directory that you want to use): + +```toml +[profile.default] +out = "my-output-dir" +fs_permissions = [{ access = "read", path = "my-output-dir" }] +``` + +Then in a `.env` at your project root, set the `FOUNDRY_OUT` environment variable to match the custom output directory, for example: +``` +FOUNDRY_OUT=my-output-dir +``` + +### Windows environments + +If you are using Windows, set the `OPENZEPPELIN_BASH_PATH` environment variable to the fully qualified path of the `bash` executable. +For example, if you are using [Git for Windows](https://gitforwindows.org/), add the following line in the `.env` file of your project (using forward slashes): + +``` +OPENZEPPELIN_BASH_PATH="C:/Program Files/Git/bin/bash" +``` + +## Usage + +Depending on which major version of OpenZeppelin Contracts you are using, and whether you want to run upgrade safety validations and/or use OpenZeppelin Defender, use the table below to determine which library to import: + +| | OpenZeppelin Contracts v5 | OpenZeppelin Contracts v4 | +| --- | --- | --- | +| **Runs validations, supports Defender** | `import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";` | `import {Upgrades} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";` | +| **No validations, does not support Defender** | `import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";` | `import {UnsafeUpgrades} from "openzeppelin-foundry-upgrades/LegacyUpgrades.sol";` | + +Import one of the above libraries in your Foundry scripts or tests, for example: +```solidity +import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol"; +``` + +Also import the implementation contract that you want to validate, deploy, or upgrade to, for example: +```solidity +import {MyToken} from "src/MyToken.sol"; +``` + +Then call functions from the imported library to run validations, deployments, or upgrades. + +## Examples + +The following examples assume you are using OpenZeppelin Contracts v5 and want to run upgrade safety validations. + +### Deploy a proxy + +Deploy a UUPS proxy: +```solidity +address proxy = Upgrades.deployUUPSProxy( + "MyContract.sol", + abi.encodeCall(MyContract.initialize, ("arguments for the initialize function")) +); +``` + +Deploy a transparent proxy: +```solidity +address proxy = Upgrades.deployTransparentProxy( + "MyContract.sol", + INITIAL_OWNER_ADDRESS_FOR_PROXY_ADMIN, + abi.encodeCall(MyContract.initialize, ("arguments for the initialize function")) +); +``` + +Deploy an upgradeable beacon and a beacon proxy: +```solidity +address beacon = Upgrades.deployBeacon("MyContract.sol", INITIAL_OWNER_ADDRESS_FOR_BEACON); + +address proxy = Upgrades.deployBeaconProxy( + beacon, + abi.encodeCall(MyContract.initialize, ("arguments for the initialize function")) +); +``` + +### Use your contract + +Call your contract’s functions as normal, but remember to always use the proxy address: +```solidity +MyContract instance = MyContract(proxy); +instance.myFunction(); +``` + +### Upgrade a proxy or beacon + +Upgrade a transparent or UUPS proxy and call an arbitrary function (such as a reinitializer) during the upgrade process: +```solidity +Upgrades.upgradeProxy( + transparentProxy, + "MyContractV2.sol", + abi.encodeCall(MyContractV2.foo, ("arguments for foo")) +); +``` + +Upgrade a transparent or UUPS proxy without calling any additional function: +```solidity +Upgrades.upgradeProxy( + transparentProxy, + "MyContractV2.sol", + "" +); +``` + +Upgrade a beacon: +```solidity +Upgrades.upgradeBeacon(beacon, "MyContractV2.sol"); +``` + + +When upgrading a proxy or beacon, ensure that the new contract either has its `@custom:oz-upgrades-from ` annotation set to the name of the old implementation contract used by the proxy or beacon, or set it with the `referenceContract` option, for example: +```solidity +Options memory opts; +opts.referenceContract = "MyContractV1.sol"; +Upgrades.upgradeProxy(proxy, "MyContractV2.sol", "", opts); +// or Upgrades.upgradeBeacon(beacon, "MyContractV2.sol", opts); +``` + + + +If possible, keep the old version of the implementation contract’s source code somewhere in your project to use as a reference as above. This requires the new version to be in a different directory, Solidity file, or using a different contract name. Otherwise, if you want to use the same directory and name for the new version, keep the build info directory from the previous deployment (or build it from an older branch of your project repository) and reference it as follows: +```solidity +Options memory opts; +opts.referenceBuildInfoDir = "/old-builds/build-info-v1"; +opts.referenceContract = "build-info-v1:MyContract"; +Upgrades.upgradeProxy(proxy, "MyContract.sol", "", opts); +// or Upgrades.upgradeBeacon(beacon, "MyContract.sol", opts); +``` + + +## Coverage Testing + +To enable code coverage reports with `forge coverage`, use the following deployment pattern in your tests: instantiate your implementation contracts directly and use the `UnsafeUpgrades` library. For example: +```solidity +address implementation = address(new MyContract()); +address proxy = UnsafeUpgrades.deployUUPSProxy( + implementation, + abi.encodeCall(MyContract.initialize, ("arguments for the initialize function")) +); +``` + + +`UnsafeUpgrades` is not recommended for use in Forge scripts. It does not validate whether your contracts are upgrade safe or whether new implementations are compatible with previous ones. Ensure you run validations before any actual deployments or upgrades, such as by using the `Upgrades` library in scripts. + + +## Deploying and Verifying + +Run your script with `forge script` to broadcast and deploy. See Foundry’s [Solidity Scripting](https://book.getfoundry.sh/guides/scripting-with-solidity) guide. + + +Include the `--sender
` flag for the `forge script` command when performing upgrades, specifying an address that owns the proxy or proxy admin. Otherwise, `OwnableUnauthorizedAccount` errors will occur. + + + +Include the `--verify` flag for the `forge script` command if you want to verify source code such as on Etherscan. This will verify your implementation contracts along with any proxy contracts as part of the deployment. + + +## Usage with Defender + +If you are using OpenZeppelin Defender, see [OpenZeppelin Defender with Foundry](/upgrades-plugins/foundry/foundry-defender) for how to use it for deployments. + +## API + +See [Foundry Upgrades API](/upgrades-plugins/foundry/api) for the full API documentation. diff --git a/docs/content/upgrades-plugins/hardhat-upgrades.mdx b/docs/content/upgrades-plugins/hardhat-upgrades.mdx new file mode 100644 index 00000000..4fbe4f1b --- /dev/null +++ b/docs/content/upgrades-plugins/hardhat-upgrades.mdx @@ -0,0 +1,157 @@ +--- +title: Using with Hardhat +--- + +This package adds functions to your Hardhat scripts so you can deploy and upgrade proxies for your contracts. Depends on `ethers.js`. + + +Check out the [step by step tutorial](https://forum.openzeppelin.com/t/openzeppelin-buidler-upgrades-step-by-step-tutorial/3580), showing from creating, testing and deploying, all the way through to upgrading with Gnosis Safe. + + +## Installation + +```console +$ npm install --save-dev @openzeppelin/hardhat-upgrades +$ npm install --save-dev @nomicfoundation/hardhat-ethers ethers # peer dependencies +``` + +And register the plugin in your [`hardhat.config.js`](https://hardhat.org/config): + +```js +require('@openzeppelin/hardhat-upgrades'); +``` + +## Usage in scripts + +### Proxies + +You can use this plugin in a [Hardhat script](https://hardhat.org/guides/scripts.html) to deploy an upgradeable instance of one of your contracts via the `deployProxy` function: + +```js +// scripts/create-box.js +const ethers, upgrades = require("hardhat"); + +async function main() + const Box = await ethers.getContractFactory("Box"); + const box = await upgrades.deployProxy(Box, [42]); + await box.waitForDeployment(); + console.log("Box deployed to:", await box.getAddress()); + + +main(); +``` + +This will automatically check that the `Box` contract is upgrade-safe, deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy (along with a proxy admin if needed), and initialize it by calling `initialize(42)`. + +Then, in another script, you can use the `upgradeProxy` function to upgrade the deployed instance to a new version. The new version can be a different contract (such as `BoxV2`), or you can just modify the existing `Box` contract and recompile it - the plugin will note it changed. + +```js +// scripts/upgrade-box.js +const ethers, upgrades = require("hardhat"); + +async function main() + const BoxV2 = await ethers.getContractFactory("BoxV2"); + const box = await upgrades.upgradeProxy(BOX_ADDRESS, BoxV2); + console.log("Box upgraded"); + + +main(); +``` + +> Note: While this plugin keeps track of all the implementation contracts you have deployed per network, in order to reuse them and validate storage compatibilities, it does _not_ keep track of the proxies you have deployed. This means that you will need to manually keep track of each deployment address, to supply those to the upgrade function when needed. + +The plugin will take care of comparing `BoxV2` to the previous one to ensure they are compatible for the upgrade, deploy the new `BoxV2` implementation contract (unless there is one already from a previous deployment), and upgrade the existing proxy to the new implementation. + +### Beacon proxies + +You can also use this plugin to deploy an upgradeable beacon for your contract with the `deployBeacon` function, then deploy one or more beacon proxies that point to it by using the `deployBeaconProxy` function. + +```js +// scripts/create-box.js +const ethers, upgrades = require("hardhat"); + +async function main() + const Box = await ethers.getContractFactory("Box"); + + const beacon = await upgrades.deployBeacon(Box); + await beacon.waitForDeployment(); + console.log("Beacon deployed to:", await beacon.getAddress()); + + const box = await upgrades.deployBeaconProxy(beacon, Box, [42]); + await box.waitForDeployment(); + console.log("Box deployed to:", await box.getAddress()); + + +main(); +``` + +Then, in another script, you can use the `upgradeBeacon` function to upgrade the beacon to a new version. When the beacon is upgraded, all of the beacon proxies that point to it will use the new contract implementation. + +```js +// scripts/upgrade-box.js +const ethers, upgrades = require("hardhat"); + +async function main() + const BoxV2 = await ethers.getContractFactory("BoxV2"); + + await upgrades.upgradeBeacon(BEACON_ADDRESS, BoxV2); + console.log("Beacon upgraded"); + + const box = BoxV2.attach(BOX_ADDRESS); + + +main(); +``` + +## Usage in tests + +You can also use the plugin’s functions from your Hardhat tests, in case you want to add tests for upgrading your contracts (which you should!). The API is the same as in scripts. + +### Proxies + +```js +const expect = require("chai"); + +describe("Box", function() + it('works', async () => { + const Box = await ethers.getContractFactory("Box"); + const BoxV2 = await ethers.getContractFactory("BoxV2"); + + const instance = await upgrades.deployProxy(Box, [42]); + const upgraded = await upgrades.upgradeProxy(await instance.getAddress(), BoxV2); + + const value = await upgraded.value(); + expect(value.toString()).to.equal('42'); + ); +}); +``` + +### Beacon proxies + +```js +const expect = require("chai"); + +describe("Box", function() + it('works', async () => { + const Box = await ethers.getContractFactory("Box"); + const BoxV2 = await ethers.getContractFactory("BoxV2"); + + const beacon = await upgrades.deployBeacon(Box); + const instance = await upgrades.deployBeaconProxy(beacon, Box, [42]); + + await upgrades.upgradeBeacon(beacon, BoxV2); + const upgraded = BoxV2.attach(await instance.getAddress()); + + const value = await upgraded.value(); + expect(value.toString()).to.equal('42'); + ); +}); +``` + +## Usage with Defender + +If you are using OpenZeppelin Defender, see [OpenZeppelin Defender with Hardhat](/upgrades-plugins/defender-deploy) for how to use it for deployments. + +## API + +See [Hardhat Upgrades API](/upgrades-plugins/api-hardhat-upgrades) for the full API documentation. diff --git a/docs/content/upgrades-plugins/index.mdx b/docs/content/upgrades-plugins/index.mdx new file mode 100644 index 00000000..ce05b334 --- /dev/null +++ b/docs/content/upgrades-plugins/index.mdx @@ -0,0 +1,60 @@ +--- +title: Upgrades Plugins +--- + +***Integrate upgrades into your existing workflow.*** Plugins for [Hardhat](https://hardhat.org) and [Foundry](https://book.getfoundry.sh/) to deploy and manage upgradeable contracts on Ethereum. + +* Deploy upgradeable contracts. +* Upgrade deployed contracts. +* Manage proxy admin rights. +* Easily use in tests. + + +Upgrades Plugins are only a part of a comprehensive set of OpenZeppelin tools for deploying and securing upgradeable smart contracts. [Check out the full list of resources](upgrades). + + +## Overview + +### Installation and Usage + +See the documentation for [Hardhat Upgrades](upgrades-plugins/hardhat-upgrades) or [Foundry Upgrades](upgrades-plugins/foundry/foundry-upgrades). + +## How the plugins work + +The plugins provide functions which take care of managing upgradeable deployments of your contracts. + +For example, `deployProxy` does the following: + +1. Validates that the implementation is [upgrade safe](upgrades-plugins/faq#what-does-it-mean-for-a-contract-to-be-upgrade-safe). +2. Deploys the [implementation contract](upgrades-plugins/faq#what-is-an-implementation-contract). Note that the Hardhat plugin first checks if there is an implementation contract deployed with the same bytecode, and skips this step if one is already deployed. +3. Creates and initializes the proxy contract, along with a [proxy admin](upgrades-plugins/faq#what-is-a-proxy-admin) (if needed). + +And when you call `upgradeProxy`: + +1. Validates that the new implementation is [upgrade safe](upgrades-plugins/faq#what-does-it-mean-for-a-contract-to-be-upgrade-safe) and is [compatible](upgrades-plugins/faq#what-does-it-mean-for-an-implementation-to-be-compatible) with the previous one. +2. Deploys the new [implementation contract](upgrades-plugins/faq#what-is-an-implementation-contract). Note that the Hardhat plugin first checks if there is an implementation contract deployed with the same bytecode, and skips this step if one is already deployed. +3. Upgrades the proxy to use the new implementation contract. + +The Hardhat plugin keeps track of all the implementation contracts you have deployed in an `.openzeppelin` folder in the project root, as well as the proxy admin. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as `.openzeppelin/unknown-*.json`). + +The Foundry plugin does not keep track of implementation contracts, but requires you to [define reference contracts](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades?tab=readme-ov-file#before-running) in order to validate new versions of implementations for upgrade safety. + +## Proxy patterns + +The plugins support the UUPS, transparent, and beacon proxy patterns. UUPS and transparent proxies are upgraded individually, whereas any number of beacon proxies can be upgraded atomically at the same time by upgrading the beacon that they point to. For more details on the different proxy patterns available, see the documentation for [Proxies](/contracts/5.x/api/proxy). + +For UUPS and transparent proxies, use `deployProxy` and `upgradeProxy`. For beacon proxies, use `deployBeacon`, `deployBeaconProxy`, and `upgradeBeacon`. See the documentation for [Hardhat Upgrades](upgrades-plugins/hardhat-upgrades) and [Foundry Upgrades](upgrades-plugins/foundry/foundry-upgrades) for examples. + +## Managing ownership + +Transparent proxies define an _admin_ address which has the rights to upgrade them. By default, the admin is a [proxy admin contract](upgrades-plugins/faq#what-is-a-proxy-admin) deployed behind the scenes. Keep in mind that the _admin_ of a proxy can only upgrade it, but not interact with the implementation contract. Read [Transparent Proxies and Function Clashes](upgrades-plugins/proxies#transparent-proxies-and-function-clashes) for more info on this restriction. + +The proxy admin contract also defines an _owner_ address which has the rights to operate it. By default, the proxy admin’s owner is the `initialOwner` address used during deployment of the transparent proxy if provided, otherwise it is the externally owned account used during deployment. You can change the proxy admin owner by calling the `admin.transferProxyAdminOwnership` function in the Hardhat plugin, or the `transferOwnership` function of the proxy admin contract if using Foundry. + + +Do not reuse an already deployed `ProxyAdmin`. Before `@openzeppelin/contracts` version 5.x, the address provided to transparent proxies was an `initialAdmin` as opposed to an `initialOwner` of a newly deployed `ProxyAdmin`. Reusing a `ProxyAdmin` will disable upgradeability in your contract. + + +UUPS and beacon proxies do not use admin addresses. UUPS proxies rely on an [`_authorizeUpgrade`](/contracts/5.x/api/proxy#UUPSUpgradeable-_authorizeUpgrade-address-) function to be overridden to include access restriction to the upgrade mechanism, whereas beacon proxies are upgradable only by the owner of their corresponding beacon. + +Once you have transferred the rights to upgrade a proxy or beacon to another address, you can still use your local setup to validate and deploy the implementation contract. The plugins include a `prepareUpgrade` function that will validate that the new implementation is upgrade-safe and compatible with the previous one, and deploy it using your local Ethereum account. You can then execute the upgrade itself from the admin or owner address. You can also use the `defender.proposeUpgrade` or `defender.proposeUpgradeWithApproval` functions to automatically set up the upgrade in [OpenZeppelin Defender](/defender). diff --git a/docs/content/upgrades-plugins/migrate-from-cli.mdx b/docs/content/upgrades-plugins/migrate-from-cli.mdx new file mode 100644 index 00000000..f7632727 --- /dev/null +++ b/docs/content/upgrades-plugins/migrate-from-cli.mdx @@ -0,0 +1,226 @@ +--- +title: Migrate from OpenZeppelin CLI +--- + + +This guide is for migrating from an old CLI which is deprecated. + + +This guide will walk through migrating a project from the OpenZeppelin CLI to OpenZeppelin Upgrades Plugins for either Truffle or Hardhat. + + +If you’d like to try out the instructions on a sample OpenZeppelin CLI project, you can clone [OpenZeppelin/openzeppelin-upgrades-migration-example](https://github.com/OpenZeppelin/openzeppelin-upgrades-migration-example) and follow the setup instructions in the readme before continuing. + + +## Differences + +The main difference between the CLI and the plugins is that the former used to keep track of your upgradeable (and non-upgradeable) deployments for you. This was handy in some contexts since you didn’t need to worry too much about proxies, implementations or addresses and you could just focus on operations like upgrading or sending transactions to your contracts just by their name. + +But having the CLI keep track of your deployments came at the expense of limiting your freedom to integrate with different tools and workflows. And since the plugins were designed to work independently of the CLI, we lifted that restriction so now you have the flexibility to keep track of your proxies the way you think it’s best. + +Keeping that aside, everything else remains the same since both the CLI and plugins make use of the same known `Proxy` and `ProxyAdmin` contracts under the hood, making up two different interfaces to manage them. This means that migrating your project won’t touch ***anything*** on chain, everything is safe and local. + +## Installation + +[Install Hardhat](https://hardhat.org/tutorial/creating-a-new-hardhat-project.html) and when initializing it, select the `Create an empty hardhat.config.js` option. + +```bash +$ npm install --save-dev hardhat +$ npx hardhat +``` + +Then install the Upgrades plugin: + +```bash +$ npm install --save-dev @openzeppelin/hardhat-upgrades +$ npm install --save-dev @nomiclabs/hardhat-ethers ethers # peer dependencies +``` + +Once it is done, register the plugin in the Hardhat config file by adding these lines: + +```jsx +require('@openzeppelin/hardhat-upgrades'); + +module.exports = + // ... +; +``` + +Install Truffle, and initialize your project. + + +Choose not to overwrite contracts or test directories when Truffle asks. By not overwriting, you won’t get an [initial migration](https://www.trufflesuite.com/docs/truffle/getting-started/running-migrations#initial-migration). Make sure you create `Migrations.sol` and the initial migration. + + +```json +$ npm install --save-dev truffle +$ npx truffle init +``` + +Then install the Upgrades plugin: + +```bash +$ npm install --save-dev @openzeppelin/truffle-upgrades +``` + +## Migrating the CLI project + + +This is a one way process. Make sure you keep backups or version control copies of your `.openzeppelin/` folder. + + +Now, let’s migrate our project by running: + +```bash +$ npx migrate-oz-cli-project +``` + +```bash +✔ Successfully migrated .openzeppelin/rinkeby.json +✔ Migration data exported to openzeppelin-cli-export.json +✔ Deleting .openzeppelin/project.json + +These were your project’s compiler options: + + "compilerSettings": { + "optimizer": { + "enabled": false, + "runs": "200" + + }, + "typechain": + "enabled": false + , + "manager": "openzeppelin", + "solcVersion": "0.6.12", + "artifactsDir": "build/contracts", + "contractsDir": "contracts" +} +``` + +This script was installed along with the plugins and what it does is to delete the CLI project file and turn your old network files (all of which live under the `.openzeppelin` directory) into their Upgrades plugin equivalent. Again, nothing on chain is changed, only local files. Notice also that once you’ve run this you can no longer use the CLI to manage this project’s contracts unless you revert the changes through backups or version control. + +The migration script will also export a `openzeppelin-cli-export.json` file into your working directory containing all the data that the CLI used to manage for you and now you’re free to use however you think it’s best. This is including your compiler settings, which are also printed at the end of the migration for convenience. Let’s add them to our new project config: + +Copy compiler settings into the [`solidity` field](https://hardhat.org/config/#available-config-options) in the Hardhat config file + +```json + +module.exports = + // ... + solidity: { + version: "0.6.12", + settings: { + optimizer: { + enabled: false, + runs: 200 + + } + } +} +``` + +Copy compiler settings into the [`compilers` field](https://www.trufflesuite.com/docs/truffle/reference/configuration#compiler-configuration) of our Truffle config file + +```json + +module.exports = + // ... + compilers: { + solc: { + version: "0.6.12", + settings: { + optimizer: { + enabled: false, + runs: 200 + + } + } + } +} +``` + + +The solidity compiler configuration format is different in `truffle-config.js` and `hardhat.config.js` files + + +And that’s it, you have successfully migrated your CLI project. Let’s now try your new setup upgrading one of your migrated contracts. + +## Upgrade to a new version + +Let’s say we had a `Box` contract in our CLI project, deployed to the Rinkeby network. Then if we open our export file, we’ll see something like this: + +```json + + "networks": { + "rinkeby": { + "proxies": { + "openzeppelin-upgrades-migration-example/Box": [ + { + "address": "0x500D1d6A4c7D8Ae28240b47c8FCde034D827fD5e", + "version": "1.0.0", + "implementation": "0x038B86d9d8FAFdd0a02ebd1A476432877b0107C8", + "admin": "0x1A1FEe7EeD918BD762173e4dc5EfDB8a78C924A8", + "kind": "Upgradeable" + + ] + } + } + }, + "compiler": + // we’ll ignore compiler settings for this + +} +``` + +What we’re seeing in here is the JSON representation of our upgradeable contract in terms of addresses: + +* `address`: the proxy address (the proxy contract contains your upgradeable contract state) +* `implementation`: the implementation address (your upgradeable contract logic) +* `admin`: the address of the proxy admin, which will probably belong to a `ProxyAdmin` contract unless you set up otherwise + +And this is how it would look like if we decided to upgrade our `Box` contract to a `BoxV2` contract using the plugins and this export file: + +These scripts are just examples of how to use the exported data. We make no suggestions on whether to keep that file as it is or how to handle its data. This is up to the user now. + +With Hardhat, we would write a script (**you can read more about Hardhat scripts [here](https://hardhat.org/guides/scripts.html) and about using the `hardhat-upgrades` plugin [here](/upgrades-plugins/hardhat-upgrades)**): + +```jsx + +const ethers, upgrades = require("hardhat"); +const OZ_SDK_EXPORT = require("../openzeppelin-cli-export.json"); + +async function main() + const [ Box ] = OZ_SDK_EXPORT.networks.rinkeby.proxies["openzeppelin-upgrades-migration-example/Box"]; + const BoxV2 = await ethers.getContractFactory("BoxV2"); + await upgrades.upgradeProxy(Box.address, BoxV2); + + +main(); +``` + +```bash +$ npx hardhat run scripts/upgradeBoxToV2.js --network rinkeby +``` + +With Truffle, we would write a migration (**you can read more about Truffle migrations [here](https://www.trufflesuite.com/docs/truffle/getting-started/running-migrations) and about using the `truffle-upgrades` plugin [here](/upgrades-plugins/truffle-upgrades)**): + +```jsx + +const upgradeProxy = require('@openzeppelin/truffle-upgrades'); +const OZ_SDK_EXPORT = require("../openzeppelin-cli-export.json"); + +const BoxV2 = artifacts.require('BoxV2'); + +module.exports = async function (deployer) + const [ Box ] = OZ_SDK_EXPORT.networks.rinkeby.proxies["openzeppelin-upgrades-migration-example/Box"]; + const instance = await upgradeProxy(Box.address, BoxV2, { deployer ); + console.log("Upgraded", instance.address); +}; +``` + +```bash +$ npx truffle migrate --network rinkeby +``` + +And that’s it! You have migrated your OpenZeppelin CLI project to Truffle or Hardhat and performed an upgrade using the plugins. diff --git a/docs/content/upgrades-plugins/network-files.mdx b/docs/content/upgrades-plugins/network-files.mdx new file mode 100644 index 00000000..3bcebce7 --- /dev/null +++ b/docs/content/upgrades-plugins/network-files.mdx @@ -0,0 +1,72 @@ +--- +title: Network Files +--- + +OpenZeppelin Hardhat Upgrades, by default, keeps track of all the contract versions you have deployed in an `.openzeppelin` folder in the project root. You will find one file per network there. It is advised that you commit to source control the files for all networks except the development ones (you may see them as `.openzeppelin/unknown-*.json`). + + +The format of the files within the `.openzeppelin` folder is not compatible with those of the OpenZeppelin CLI. If you want to use these plugins for an existing OpenZeppelin CLI project, you have to migrate it first. See [Migrate from CLI](/upgrades-plugins/migrate-from-cli) for instructions. + + +## `.json` + +OpenZeppelin Hardhat Upgrades will generate a file for each of the networks you work on (`mainnet`, `sepolia`, etc.). These files share the same structure: + +```json +// .openzeppelin/.json + + "manifestVersion": "3.0", + "impls": { + "...": { + "address": "...", + "txHash": "...", + "layout": { + "storage": [...], + "types": {... + } + }, + "...": + "address": "...", + "txHash": "...", + "layout": { + "storage": [...], + "types": {... + } + } + } +} +``` + +For every logic contract, besides the deployment address, the following info is also tracked: + +* `types` keeps track of all the types used in the contract or its ancestors, from basic types like `uint256` to custom `struct` types +* `storage` tracks the storage layout of the linearized contract, referencing the types defined in the `types` section, and is used for verifying that any [storage layout changes between subsequent versions are compatible](/upgrades-plugins/faq#what-does-it-mean-for-an-implementation-to-be-compatible) + +The naming of the file will be `.json`, but note that `` is not taken from the name of the network’s entry in the Hardhat configuration file, but is instead inferred from the chain id associated to the entry. + +There is a limited set of public chains; Chains not on the list such as Ethereum Classic will have network files named `unknown-.json`. + +## Configuration Files in Version Control + +Public network files like `mainnet.json` or `sepolia.json` should be tracked in version control. These contain valuable information about your project’s status in the corresponding network, like the addresses of the contract versions that have been deployed. Such files should be identical for all the contributors of a project. + +Network files may also appear as `unknown-.json` if the network name is not known to the Hardhat plugin. If the chain ID corresponds to a public network, then it should also be tracked in version control. However, if the chain ID is for a temporary local network such as a development network, then it is only relevant to a single contributor of the project and should not be tracked in version control. + +## Temporary Files + +Hardhat development networks using Hardhat version 2.12.3 or later, and Anvil development networks connected from Hardhat, will have network files written to an `openzeppelin-upgrades` folder under the operating system’s temporary directory. These files are named `hardhat--.json` or `anvil--.json`, where `` is a 0x-prefixed hex id that uniquely identifies an instance/run of the Hardhat or Anvil network. Files in this temporary folder can be safely deleted when their corresponding Hardhat or Anvil network instance is no longer active, for example after testing is finished. + +You can determine the location of a temporary network file by doing the following: + +1. Run `export DEBUG=@openzeppelin:*` to enable debug logging. +2. Run your Hardhat test or script. +3. Find the log message containing the text `development manifest file:`. + +## Custom Network Files Location + +To customize the location of the `.openzeppelin` folder, set the `MANIFEST_DEFAULT_DIR` environment variable to an absolute path or a path relative to the root of your project. This variable allows you to specify different directories for storing network files, enabling you to deploy the same contracts to the same networks for different environments without conflicts. This can be used with private networks to avoid chain ID conflicts. + +For example: + +* Run `export MANIFEST_DEFAULT_DIR=.openzeppelin/tests` to configure OpenZeppelin Hardhat Upgrades to use `.openzeppelin/tests/.json` for test deployments. +* Run `export MANIFEST_DEFAULT_DIR=.openzeppelin/production` to configure OpenZeppelin Hardhat Upgrades to use `.openzeppelin/production/.json` for production deployments. diff --git a/docs/content/upgrades-plugins/proxies.mdx b/docs/content/upgrades-plugins/proxies.mdx new file mode 100644 index 00000000..8b9064cc --- /dev/null +++ b/docs/content/upgrades-plugins/proxies.mdx @@ -0,0 +1,193 @@ +--- +title: Proxy Upgrade Pattern +--- + +This article describes the "unstructured storage" proxy pattern, the fundamental building block of OpenZeppelin Upgrades. + + +For a more in depth read, please see [our proxy-patterns blog post](https://blog.openzeppelin.com/proxy-patterns/), which discusses the need for proxies, goes into more technical detail on the subject, elaborates on other possible proxy patterns that were considered for OpenZeppelin Upgrades, and more. + + +## Why Upgrade a Contract? + +By design, smart contracts are immutable. On the other hand, software quality heavily depends on the ability to upgrade and patch source code in order to produce iterative releases. Even though blockchain based software profits significantly from the technology’s immutability, still a certain degree of mutability is needed for bug fixing and potential product improvements. OpenZeppelin Upgrades solves this apparent contradiction by providing an easy to use, simple, robust, and opt-in upgrade mechanism for smart contracts that can be controlled by any type of governance, be it a multi-sig wallet, a simple address or a complex DAO. + +## Upgrading via the Proxy Pattern + +The basic idea is using a proxy for upgrades. The first contract is a simple wrapper or "proxy" which users interact with directly and is in charge of forwarding transactions to and from the second contract, which contains the logic. The key concept to understand is that the logic contract can be replaced while the proxy, or the access point is never changed. Both contracts are still immutable in the sense that their code cannot be changed, but the logic contract can simply be swapped by another contract. The wrapper can thus point to a different logic implementation and in doing so, the software is "upgraded". + +``` +User ---- tx ---> Proxy ----------> Implementation_v0 + | + ------------> Implementation_v1 + | + ------------> Implementation_v2 +``` + +## Proxy Forwarding + +The most immediate problem that proxies need to solve is how the proxy exposes the entire interface of the logic contract without requiring a one to one mapping of the entire logic contract’s interface. That would be difficult to maintain, prone to errors, and would make the interface itself not upgradeable. Hence, a dynamic forwarding mechanism is required. The basics of such a mechanism are presented in the code below: + +```solidity +// This code is for "illustration" purposes. To implement this functionality in production it +// is recommended to use the `Proxy` contract from the `@openzeppelin/contracts` library. +// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.8.2/contracts/proxy/Proxy.sol + +assembly { + // (1) copy incoming call data + calldatacopy(0, 0, calldatasize()) + + // (2) forward call to logic contract + let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) + + // (3) retrieve return data + returndatacopy(0, 0, returndatasize()) + + // (4) forward return data back to caller + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } +} +``` + +This code can be put in the [fallback function](https://docs.soliditylang.org/en/latest/contracts.html#fallback-function) of a proxy, and will forward any call to any function with any set of parameters to the logic contract without it needing to know anything in particular of the logic contract’s interface. In essence, (1) the `calldata` is copied to memory, (2) the call is forwarded to the logic contract, (3) the return data from the call to the logic contract is retrieved, and (4) the returned data is forwarded back to the caller. + +A very important thing to note is that the code makes use of the EVM’s `delegatecall` opcode which executes the callee’s code in the context of the caller’s state. That is, the logic contract controls the proxy’s state and the logic contract’s state is meaningless. Thus, the proxy doesn’t only forward transactions to and from the logic contract, but also represents the pair’s state. The state is in the proxy and the logic is in the particular implementation that the proxy points to. + +## Unstructured Storage Proxies + +A problem that quickly comes up when using proxies has to do with the way in which variables are stored in the proxy contract. Suppose that the proxy stores the logic contract’s address in its only variable `address public _implementation;`. Now, suppose that the logic contract is a basic token whose first variable is `address public _owner`. Both variables are 32 byte in size, and as far as the EVM knows, occupy the first slot of the resulting execution flow of a proxied call. When the logic contract writes to `_owner`, it does so in the scope of the proxy’s state, and in reality writes to `_implementation`. This problem can be referred to as a "storage collision". + +``` +|Proxy |Implementation | +|--------------------------|-------------------------| +|address _implementation |address _owner | <=== Storage collision! +|... |mapping _balances | +| |uint256 _supply | +| |... | +``` + +There are many ways to overcome this problem, and the "unstructured storage" approach which OpenZeppelin Upgrades implements works as follows. Instead of storing the `_implementation` address at the proxy’s first storage slot, it chooses a pseudo random slot instead. This slot is sufficiently random, that the probability of a logic contract declaring a variable at the same slot is negligible. The same principle of randomizing slot positions in the proxy’s storage is used in any other variables the proxy may have, such as an admin address (that is allowed to update the value of `_implementation`), etc. + +``` +|Proxy |Implementation | +|--------------------------|-------------------------| +|... |address _owner | +|... |mapping _balances | +|... |uint256 _supply | +|... |... | +|... | | +|... | | +|... | | +|... | | +|address _implementation | | <=== Randomized slot. +|... | | +|... | | +``` + +An example of how the randomized storage is achieved, following [EIP 1967](http://eips.ethereum.org/EIPS/eip-1967): + +```solidity +bytes32 private constant implementationPosition = bytes32(uint256( + keccak256('eip1967.proxy.implementation')) - 1 +)); +``` + +As a result, a logic contract doesn’t need to care about overwriting any of the proxy’s variables. Other proxy implementations that face this problem usually imply having the proxy know about the logic contract’s storage structure and adapt to it, or instead having the logic contract know about the proxy’s storage structure and adapt to it. This is why this approach is called "unstructured storage"; neither of the contracts needs to care about the structure of the other. + +## Storage Collisions Between Implementation Versions + +As discussed, the unstructured approach avoids storage collisions between the logic contract and the proxy. However, storage collisions between different versions of the logic contract can occur. In this case, imagine that the first implementation of the logic contract stores `address public _owner` at the first storage slot and an upgraded logic contract stores `address public _lastContributor` at the same first slot. When the updated logic contract attempts to write to the `_lastContributor` variable, it will be using the same storage position where the previous value for `_owner` was being stored, and overwrite it! + +Incorrect storage preservation: + +``` +|Implementation_v0 |Implementation_v1 | +|--------------------|-------------------------| +|address _owner |address _lastContributor | <=== Storage collision! +|mapping _balances |address _owner | +|uint256 _supply |mapping _balances | +|... |uint256 _supply | +| |... | +``` + +Correct storage preservation: + +``` +|Implementation_v0 |Implementation_v1 | +|--------------------|-------------------------| +|address _owner |address _owner | +|mapping _balances |mapping _balances | +|uint256 _supply |uint256 _supply | +|... |address _lastContributor | <=== Storage extension. +| |... | +``` + +The unstructured storage proxy mechanism doesn’t safeguard against this situation. It is up to the user to have new versions of a logic contract extend previous versions, or otherwise guarantee that the storage hierarchy is always appended to but not modified. However, OpenZeppelin Upgrades detects such collisions and warns the developer appropriately. + +## The Constructor Caveat + +In Solidity, code that is inside a constructor or part of a global variable declaration is not part of a deployed contract’s runtime bytecode. This code is executed only once, when the contract instance is deployed. As a consequence of this, the code within a logic contract’s constructor will never be executed in the context of the proxy’s state. To rephrase, proxies are completely oblivious to the storage trie changes that are performed by the constructor. It’s simply as if they weren’t there for the proxy. (Note that `immutable` variables can be reflected in a proxy contract but [should be used with caution](/upgrades-plugins/faq#why-cant-i-use-immutable-variables).) + +The problem is easily solved though. Logic contracts should move the code within the constructor to a regular 'initializer' function, and have this function be called whenever the proxy links to this logic contract. Special care needs to be taken with this initializer function so that it can only be called once, which is one of the properties of constructors in general programming. + +This is why when we create a proxy using OpenZeppelin Upgrades, you can provide the name of the initializer function and pass parameters. + +To ensure that the `initialize` function can only be called once, a simple modifier is used. OpenZeppelin Upgrades provides this functionality via a contract that can be extended: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract MyContract is Initializable { + function initialize( + address arg1, + uint256 arg2, + bytes memory arg3 + ) public payable initializer { + // "constructor" code... + + } +} +``` + +Notice how the contract extends `Initializable` and implements the `initializer` provided by it. + +## Transparent Proxies and Function Clashes + +As described in the previous sections, upgradeable contract instances (or proxies) work by delegating all calls to a logic contract. However, the proxies need some functions of their own, such as `upgradeTo(address)` to upgrade to a new implementation. This begs the question of how to proceed if the logic contract also has a function named `upgradeTo(address)`: upon a call to that function, did the caller intend to call the proxy or the logic contract? + + +Clashing can also happen among functions with different names. Every function that is part of a contract’s public ABI is identified, at the bytecode level, by a 4-byte identifier. This identifier depends on the name and arity of the function, but since it’s only 4 bytes, there is a possibility that two different functions with different names may end up having the same identifier. The Solidity compiler tracks when this happens within the same contract, but not when the collision happens across different ones, such as between a proxy and its logic contract. Read [this article](https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357) for more info on this. + + +The way OpenZeppelin Upgrades deals with this problem is via the _transparent proxy_ pattern. A transparent proxy will decide which calls are delegated to the underlying logic contract based on the caller address (i.e., the `msg.sender`): + +* If the caller is the admin of the proxy (the address with rights to upgrade the proxy), then the proxy will **not** delegate any calls, and only answer any messages it understands. +* If the caller is any other address, the proxy will **always** delegate a call, no matter if it matches one of the proxy’s functions. + +Assuming a proxy with an `owner()` and an `upgradeTo()` function, that delegates calls to an ERC20 contract with an `owner()` and a `transfer()` function, the following table covers all scenarios: + +| msg.sender | owner() | upgradeTo() | transfer() | +|------------|---------|-------------|------------| +| Owner | returns proxy.owner() | returns proxy.upgradeTo() | fails | +| Other | returns erc20.owner() | fails | returns erc20.transfer() | + +Fortunately, OpenZeppelin Upgrades accounts for this situation, and uses an intermediary ProxyAdmin contract for each transparent proxy. Even if you call the `deploy` command from your node’s default account, the ProxyAdmin contracts will be the actual admins of your transparent proxies. This means that you will be able to interact with the proxies from any of your node’s accounts, without having to worry about the nuances of the transparent proxy pattern. Only advanced users that create proxies from Solidity need to be aware of the transparent proxies pattern. + +## Summary + +Any developer using upgradeable contracts should be familiar with proxies in the ways that are described in this article. In the end, the concept is very simple, and OpenZeppelin Upgrades is designed to encapsulate all the proxy mechanics in a way that the amount of things you need to keep in mind when developing projects are reduced to an absolute minimum. It all comes down to the following list: + +* Have a basic understanding of what a proxy is +* Always extend storage instead of modifying it +* Make sure your contracts use initializer functions instead of constructors + +Furthermore, the OpenZeppelin Upgrades will let you know when something goes wrong with one of the items in this list. diff --git a/docs/content/upgrades-plugins/truffle-upgrades.mdx b/docs/content/upgrades-plugins/truffle-upgrades.mdx new file mode 100644 index 00000000..fef53fef --- /dev/null +++ b/docs/content/upgrades-plugins/truffle-upgrades.mdx @@ -0,0 +1,147 @@ +--- +title: Using with Truffle +--- + + +This package is deprecated. The recommended alternative is to use [Hardhat](https://hardhat.org/) along with the [Hardhat Upgrades](/upgrades-plugins/hardhat-upgrades) plugin. + + +This package adds functions to your Truffle migrations and tests so you can deploy and upgrade proxies for your contracts. + + +Usage from [Truffle external scripts](https://www.trufflesuite.com/docs/truffle/getting-started/writing-external-scripts) is not yet supported. + + + +Check out the [step by step tutorial](https://forum.openzeppelin.com/t/openzeppelin-truffle-upgrades-step-by-step-tutorial/3579), showing from creating, testing and deploying, all the way through to upgrading with Gnosis Safe. + + +## Installation + +```console +$ npm install --save-dev @openzeppelin/truffle-upgrades +``` + +This package requires Truffle [version 5.1.35](https://github.com/trufflesuite/truffle/releases/tag/v5.1.35) or greater. + +## Usage in migrations + +### Proxies + +To deploy an upgradeable instance of one of your contracts in your migrations, use the `deployProxy` function: + +```js +// migrations/NN_deploy_upgradeable_box.js +const deployProxy = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); + +module.exports = async function (deployer) + const instance = await deployProxy(Box, [42], { deployer ); + console.log('Deployed', instance.address); +}; +``` + +This will automatically check that the `Box` contract is upgrade-safe, set up a proxy admin (if needed), deploy an implementation contract for the `Box` contract (unless there is one already from a previous deployment), create a proxy, and initialize it by calling `initialize(42)`. + +Then, in a future migration, you can use the `upgradeProxy` function to upgrade the deployed instance to a new version. The new version can be a different contract (such as `BoxV2`), or you can just modify the existing `Box` contract and recompile it - the plugin will note it changed. + +```js +// migrations/MM_upgrade_box_contract.js +const upgradeProxy = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); +const BoxV2 = artifacts.require('BoxV2'); + +module.exports = async function (deployer) + const existing = await Box.deployed(); + const instance = await upgradeProxy(existing.address, BoxV2, { deployer ); + console.log("Upgraded", instance.address); +}; +``` + +The plugin will take care of comparing `BoxV2` to the previous one to ensure they are compatible for the upgrade, deploy the new `BoxV2` implementation contract (unless there is one already from a previous deployment), and upgrade the existing proxy to the new implementation. + +### Beacon proxies + +You can also use this plugin to deploy an upgradable beacon for your contract with the `deployBeacon` function, then deploy one or more beacon proxies that point to it by using the `deployBeaconProxy` function. + +```js +// migrations/NN_deploy_upgradeable_box.js +const deployBeacon, deployBeaconProxy = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); + +module.exports = async function (deployer) + const beacon = await deployBeacon(Box); + console.log('Beacon deployed', beacon.address); + + const instance = await deployBeaconProxy(beacon, Box, [42], { deployer ); + console.log('Box deployed', instance.address); +}; +``` + +In a future migration, you can use the `erc1967.getBeaconAddress` function to get the beacon address from a deployed beacon proxy instance, then call the `upgradeBeacon` function to upgrade that beacon to a new version. When the beacon is upgraded, all of the beacon proxies that point to it will use the new contract implementation. + +```js +// migrations/MM_upgrade_box_contract.js +const erc1967, upgradeBeacon = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); +const BoxV2 = artifacts.require('BoxV2'); + +module.exports = async function (deployer) + const existing = await Box.deployed(); + + const beaconAddress = await erc1967.getBeaconAddress(existing.address); + await upgradeBeacon(beaconAddress, BoxV2, { deployer ); + console.log("Beacon upgraded", beaconAddress); + + const instance = await BoxV2.at(existing.address); +}; +``` + +## Usage in tests + +You can also use the plugin’s functions from your Truffle tests, in case you want to add tests for upgrading your contracts (which you should!). The API is the same as in the migrations, only that without a `deployer` parameter. + +### Proxies + +```js +const deployProxy, upgradeProxy = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); +const BoxV2 = artifacts.require('BoxV2'); + +describe('upgrades', () => + it('works', async () => { + const box = await deployProxy(Box, [42]); + const box2 = await upgradeProxy(box.address, BoxV2); + + const value = await box2.value(); + assert.equal(value.toString(), '42'); + ); +}); +``` + +### Beacon proxies + +```js +const deployBeacon, deployBeaconProxy, upgradeBeacon = require('@openzeppelin/truffle-upgrades'); + +const Box = artifacts.require('Box'); +const BoxV2 = artifacts.require('BoxV2'); + +describe('upgrades', () => + it('works', async () => { + const beacon = await deployBeacon(Box); + const box = await deployBeaconProxy(beacon, Box, [42]); + + await upgradeBeacon(beacon, BoxV2); + const box2 = await BoxV2.at(box.address); + + const value = await box2.value(); + assert.equal(value.toString(), '42'); + ); +}); +``` diff --git a/docs/content/upgrades-plugins/writing-upgradeable.mdx b/docs/content/upgrades-plugins/writing-upgradeable.mdx new file mode 100644 index 00000000..e2b01605 --- /dev/null +++ b/docs/content/upgrades-plugins/writing-upgradeable.mdx @@ -0,0 +1,441 @@ +--- +title: Writing Upgradeable Contracts +--- + +When working with upgradeable contracts using OpenZeppelin Upgrades, there are a few minor caveats to keep in mind when writing your Solidity code. + +It’s worth mentioning that these restrictions have their roots in how the Ethereum VM works, and apply to all projects that work with upgradeable contracts, not just OpenZeppelin Upgrades. + +## Initializers + +You can use your Solidity contracts with OpenZeppelin Upgrades without any modifications, except for their _constructors_. Due to a requirement of the proxy-based upgradeability system, no constructors can be used in upgradeable contracts. To learn about the reasons behind this restriction, head to [Proxies](/upgrades-plugins/proxies#the-constructor-caveat). + +This means that, when using a contract with the OpenZeppelin Upgrades, you need to change its constructor into a regular function, typically named `initialize`, where you run all the setup logic: + +```solidity +// NOTE: Do not use this code snippet, it's incomplete and has a critical vulnerability! + +pragma solidity ^0.6.0; + +contract MyContract { + uint256 public x; + + function initialize(uint256 _x) public { + x = _x; + } +} +``` +However, while Solidity ensures that a `constructor` is called only once in the lifetime of a contract, a regular function can be called many times. To prevent a contract from being _initialized_ multiple times, you need to add a check to ensure the `initialize` function is called only once: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +contract MyContract { + uint256 public x; + bool private initialized; + + function initialize(uint256 _x) public { + require(!initialized, "Contract instance has already been initialized"); + initialized = true; + x = _x; + } +} +``` + +Since this pattern is very common when writing upgradeable contracts, OpenZeppelin Contracts provides an `Initializable` base contract that has an `initializer` modifier that takes care of this: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract MyContract is Initializable { + uint256 public x; + + function initialize(uint256 _x) public initializer { + x = _x; + } +} +``` + +Another difference between a `constructor` and a regular function is that Solidity takes care of automatically invoking the constructors of all ancestors of a contract. When writing an initializer, you need to take special care to manually call the initializers of all parent contracts. Note that the `initializer` modifier can only be called once even when using inheritance, so parent contracts should use the `onlyInitializing` modifier: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +contract BaseContract is Initializable { + uint256 public y; + + function initialize() public onlyInitializing { + y = 42; + } +} + +contract MyContract is BaseContract { + uint256 public x; + + function initialize(uint256 _x) public initializer { + BaseContract.initialize(); // Do not forget this call! + x = _x; + } +} +``` + +### Using Upgradeable Smart Contract Libraries + +Keep in mind that this restriction affects not only your contracts, but also the contracts you import from a library. Consider for example [`ERC20`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.7.3/contracts/token/ERC20/ERC20.sol) from OpenZeppelin Contracts: the contract initializes the token's name and symbol in its constructor. + +```solidity +// @openzeppelin/contracts/token/ERC20/ERC20.sol +pragma solidity ^0.8.0; + +... + +contract ERC20 is Context, IERC20 { + + ... + + string private _name; + string private _symbol; + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + ... +} +``` + +This means you should not be using these contracts in your OpenZeppelin Upgrades project. Instead, make sure to use `@openzeppelin/contracts-upgradeable`, which is an official fork of OpenZeppelin Contracts that has been modified to use initializers instead of constructors. Take a look at what [ERC20Upgradeable](https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/v4.7.3/contracts/token/ERC20/ERC20Upgradeable.sol) looks like in `@openzeppelin/contracts-upgradeable`: + +```solidity +// @openzeppelin/contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol +pragma solidity ^0.8.0; + +... + +contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable { + ... + + string private _name; + string private _symbol; + + function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { + __ERC20_init_unchained(name_, symbol_); + } + + function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { + _name = name_; + _symbol = symbol_; + } + + ... +} +``` + +Whether using OpenZeppelin Contracts or another smart contract library, always make sure that the package is set up to handle upgradeable contracts. + +Learn more about OpenZeppelin Contracts Upgradeable in [Contracts: Using with Upgrades](/contracts/5.x/upgradeable). + +### Avoiding Initial Values in Field Declarations + +Solidity allows defining initial values for fields when declaring them in a contract. + +```solidity +contract MyContract { + uint256 public hasInitialValue = 42; // equivalent to setting in the constructor +} +``` + +This is equivalent to setting these values in the constructor, and as such, will not work for upgradeable contracts. Make sure that all initial values are set in an initializer function as shown below; otherwise, any upgradeable instances will not have these fields set. + +```solidity +contract MyContract is Initializable { + uint256 public hasInitialValue; + + function initialize() public initializer { + hasInitialValue = 42; // set initial value in initializer + } +} +``` + + +It is still ok to define _constant_ state variables, because the compiler [does not reserve a storage slot for these variables](https://solidity.readthedocs.io/en/latest/contracts.html#constant-state-variables), and every occurrence is replaced by the respective constant expression. So the following still works with OpenZeppelin Upgrades: + + +```solidity +contract MyContract { + uint256 public constant hasInitialValue = 42; // define as constant +} +``` + +### Initializing the Implementation Contract + +Do not leave an implementation contract uninitialized. An uninitialized implementation contract can be taken over by an attacker, which may impact the proxy. To prevent the implementation contract from being used, you should invoke the `_disableInitializers` function in the constructor to automatically lock it when it is deployed: + +```solidity +/// @custom:oz-upgrades-unsafe-allow constructor +constructor() { + _disableInitializers(); +} +``` + +### Validating Initializers + +The OpenZeppelin Upgrades plugins will automatically detect specific issues with initializers in implementation contracts. These include checking if your implementation contract is missing an initializer when there are parent initializers that need to be called, if a parent initializer is not called, or if a parent initializer is called more than once. The plugins will also warn if parent initializers are not called in linearized order. + +Reinitializers are not included in validations by default, because the Upgrades plugins cannot determine whether they are intended to be used for new deployments. If you want to validate a reinitializer function as an initializer that can be used for new deployments, annotate it with `@custom:oz-upgrades-validate-as-initializer`. Note that functions which cannot possibly be initializers are always ignored, such as private functions which cannot be called externally or by child contracts. + +## Creating New Instances From Your Contract Code + +When creating a new instance of a contract from your contract's code, these creations are handled directly by Solidity and not by OpenZeppelin Upgrades, which means that **these contracts will not be upgradeable**. + +For instance, in the following example, even if `MyContract` is deployed as upgradeable, the `token` contract created is not: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract MyContract is Initializable { + ERC20 public token; + + function initialize() public initializer { + token = new ERC20("Test", "TST"); // This contract will not be upgradeable + } +} +``` + +If you would like the `ERC20` instance to be upgradeable, the easiest way to achieve that is to simply accept an instance of that contract as a parameter, and inject it after creating it: + +```solidity +// contracts/MyContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.6.0; + +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; + +contract MyContract is Initializable { + IERC20Upgradeable public token; + + function initialize(IERC20Upgradeable _token) public initializer { + token = _token; + } +} +``` + +## Potentially Unsafe Operations + +When working with upgradeable smart contracts, you will always interact with the contract instance, and never with the underlying logic contract. However, nothing prevents a malicious actor from sending transactions to the logic contract directly. This does not pose a threat, since any changes to the state of the logic contracts do not affect your contract instances, as the storage of the logic contracts is never used in your project. + +There is, however, an exception. If the direct call to the logic contract triggers a `selfdestruct` operation, then the logic contract will be destroyed, and all your contract instances will end up delegating all calls to an address without any code. This would effectively break all contract instances in your project. + +A similar effect can be achieved if the logic contract contains a `delegatecall` operation. If the contract can be made to `delegatecall` into a malicious contract that contains a `selfdestruct`, then the calling contract will be destroyed. + +As such, it is not allowed to use either `selfdestruct` or `delegatecall` in your contracts. + +## Modifying Your Contracts + +When writing new versions of your contracts, either due to new features or bug fixing, there is an additional restriction to observe: you cannot change the order in which the contract state variables are declared, nor their type. You can read more about the reasons behind this restriction by learning about our [Proxies](/upgrades-plugins/proxies). + + +Violating any of these storage layout restrictions will cause the upgraded version of the contract to have its storage values mixed up, and can lead to critical errors in your application. + + +This means that if you have an initial contract that looks like this: + +```solidity +contract MyContract { + uint256 private x; + string private y; +} +``` + +Then you cannot change the type of a variable: + +```solidity +contract MyContract { + string private x; + string private y; +} +``` + +Or change the order in which they are declared: + +```solidity +contract MyContract { + string private y; + uint256 private x; +} +``` + +Or introduce a new variable before existing ones: + +```solidity +contract MyContract { + bytes private a; + uint256 private x; + string private y; +} +``` + +Or remove an existing variable: + +```solidity +contract MyContract { + string private y; +} +``` + +If you need to introduce a new variable, make sure you always do so at the end: + +```solidity +contract MyContract { + uint256 private x; + string private y; + bytes private z; +} +``` + +Keep in mind that if you rename a variable, then it will keep the same value as before after upgrading. This may be the desired behavior if the new variable is semantically the same as the old one: + +```solidity +contract MyContract { + uint256 private x; + string private z; // starts with the value from `y` +} +``` + +And if you remove a variable from the end of the contract, note that the storage will not be cleared. A subsequent update that adds a new variable will cause that variable to read the leftover value from the deleted one. + +```solidity +contract MyContract { + uint256 private x; +} +``` + +Then upgraded to: +```solidity +contract MyContract { + uint256 private x; + string private z; // starts with the value from `y` +} +``` + +Note that you may also be inadvertently changing the storage variables of your contract by changing its parent contracts. For instance, if you have the following contracts: + +```solidity +contract A { + uint256 a; +} + +contract B { + uint256 b; +} + +contract MyContract is A, B {} +``` + +Then modifying `MyContract` by swapping the order in which the base contracts are declared, or introducing new base contracts, will change how the variables are actually stored: + +```solidity +contract MyContract is B, A {} +``` + +You also cannot add new variables to base contracts, if the child has any variables of its own. Given the following scenario: + +```solidity +contract Base { + uint256 base1; +} + +contract Child is Base { + uint256 child; +} +``` + +If `Base` is modified to add an extra variable: + +```solidity +contract Base { + uint256 base1; + uint256 base2; +} +``` + +Then the variable `base2` would be assigned the slot that `child` had in the previous version. A workaround for this is to declare unused variables or storage gaps in base contracts that you may want to extend in the future, as a means of "reserving" those slots. Note that this trick does not involve increased gas usage. + +### Storage Gaps + +Storage gaps are a convention for reserving storage slots in a base contract, allowing future versions of that contract to use up those slots without affecting the storage layout of child contracts. + +To create a storage gap, declare a fixed-size array in the base contract with an initial number of slots. This can be an array of `uint256` so that each element reserves a 32 byte slot. Use the name `__gap` or a name starting with `__gap_` for the array so that OpenZeppelin Upgrades will recognize the gap: + +```solidity +contract Base { + uint256 base1; + uint256[49] __gap; +} + +contract Child is Base { + uint256 child; +} +``` + +If `Base` is later modified to add extra variable(s), reduce the appropriate number of slots from the storage gap, keeping in mind [Solidity's rules on how contiguous items are packed](https://docs.soliditylang.org/en/latest/internals/layout_in_storage.html#layout-of-state-variables-in-storage). For example: + +```solidity +contract Base { + uint256 base1; + uint256 base2; // 32 bytes + uint256[48] __gap; +} +``` + +Or: + +```solidity +contract Base { + uint256 base1; + address base2; // 20 bytes + uint256[48] __gap; // array always starts at a new slot +} +``` + +Or: + +```solidity +contract Base { + uint256 base1; + uint128 base2a; // 16 bytes + uint128 base2b; // 16 bytes - continues from the same slot as above + uint256[48] __gap; +} +``` + +To help determine the proper storage gap size in the new version of your contract, you can simply attempt an upgrade using `upgradeProxy` or just run the validations with `validateUpgrade` (see docs for [Hardhat Upgrades](/upgrades-plugins/api-hardhat-upgrades) or [Foundry Upgrades](/upgrades-plugins/foundry/api)). If a storage gap is not being reduced properly, you will see an error message indicating the expected size of the storage gap. + +### Namespaced Storage Layout + +[ERC-7201: Namespaced Storage Layout](https://eips.ethereum.org/EIPS/eip-7201) is another convention that can be used to avoid storage layout errors when modifying base contracts or when changing the inheritance order of contracts. This convention is used in the upgradeable variant of OpenZeppelin Contracts starting with version 5.0. + +This convention involves placing all storage variables of a contract into one or more structs and annotating those structs with `@custom:storage-location erc7201:`. A namespace id is a string that uniquely identifies each namespace in a contract, so the same id must not be defined more than once in a contract or any of its base contracts. + +When using namespaced storage layouts, the OpenZeppelin Upgrades plugins will automatically detect the namespace ids and validate that each change within a namespace during an upgrade is safe according to the same rules as described in [Modifying Your Contracts](#modifying-your-contracts). + + +Solidity version 0.8.20 or higher is required in order to use the Upgrades plugins with namespaced storage layouts. The plugins will give an error if they detect `@custom:storage-location` annotations with an older version of Solidity, because older versions of the compiler do not produce sufficient information for validation of namespaced storage layouts. + diff --git a/docs/content/upgrades.mdx b/docs/content/upgrades.mdx new file mode 100644 index 00000000..05875a72 --- /dev/null +++ b/docs/content/upgrades.mdx @@ -0,0 +1,49 @@ +--- +title: Upgrades +--- + +OpenZeppelin provides tooling for deploying and securing upgradeable smart contracts. + +* [Upgrades Plugins](/upgrades-plugins) to deploy upgradeable contracts with automated security checks. +* [Upgradeable Contracts](/contracts/5.x/upgradeable) to build your contract using our Solidity components. + +Find all of our resources related to upgradeability below. + + +If you don't know where to start we suggest to start with [**Learn: Upgrading Smart Contracts**](/contracts/5.x/learn/upgrading-smart-contracts). + + +## Resources + + + + When working with upgradeable contracts using OpenZeppelin Upgrades, there are a few minor caveats to keep in mind when writing your Solidity code. + + + A complete list of all available proxy contracts and related utilities, with documentation relevant for low-level use without Upgrades Plugins. + + + A survey of upgrade patterns, and good practices and recommendations for upgrades management and governance. + + + Explaining the differences between the Transparent Proxy Pattern and the newly available UUPS Proxies. + + + A tutorial on using the UUPS proxy pattern: what the Solidity code should look like, and how to use the Upgrades Plugins with this new proxy pattern. + + diff --git a/docs/content/wizard.mdx b/docs/content/wizard.mdx new file mode 100644 index 00000000..4a7777c5 --- /dev/null +++ b/docs/content/wizard.mdx @@ -0,0 +1,36 @@ +--- +title: Contracts Wizard +description: A tool for building smart contracts +--- + +Contracts Wizard is a web application to interactively build a contract out of components from OpenZeppelin Contracts. Select the kind of contract that you want, set your parameters and desired features, and the Wizard will generate all of the code necessary. The resulting code is ready to be compiled and deployed, or it can serve as a starting point and customized further with application specific logic. + + + +## Usage + +Use the Contracts Wizard here in the docs or at [wizard.openzeppelin.com](https://wizard.openzeppelin.com) + +## TypeScript API + +You can use the programmatic TypeScript API to generate contracts from your own applications. + +View the API documentation for each smart contract language: +- [Solidity](https://github.com/OpenZeppelin/contracts-wizard/blob/master/packages/core/solidity/README.md) +- [Cairo](https://github.com/OpenZeppelin/contracts-wizard/blob/master/packages/core/cairo/README.md) +- [Stellar](https://github.com/OpenZeppelin/contracts-wizard/blob/master/packages/core/stellar/README.md) +- [Stylus](https://github.com/OpenZeppelin/contracts-wizard/blob/master/packages/core/stylus/README.md) + +## Embedding + +To embed Contracts Wizard on your site, first include the script tag: + +```html + +``` + +Then place `` in the body where you want Contracts Wizard to load. + +Optionally focus on specific tab with the `data-tab` attribute as in ``. + +For languages other than Solidity, use the `data-lang` attribute, for example: ``. diff --git a/docs/docgen/config-md.js b/docs/docgen/config-md.js new file mode 100644 index 00000000..afbf7c6a --- /dev/null +++ b/docs/docgen/config-md.js @@ -0,0 +1,20 @@ +const path = require('path'); +const fs = require('fs'); + +/** @type import('solidity-docgen/dist/config').UserConfig */ +module.exports = { + outputDir: 'docs/modules/api/pages', + templates: 'docs/templates-md', + exclude: ['mocks'], + pageExtension: '.mdx', + pages: (_, file, config) => { + const sourcesDir = path.resolve(config.root, config.sourcesDir); + let dir = path.resolve(config.root, file.absolutePath); + while (dir.startsWith(sourcesDir)) { + dir = path.dirname(dir); + if (fs.existsSync(path.join(dir, 'README.adoc'))) { + return path.relative(sourcesDir, dir) + config.pageExtension; + } + } + }, +}; diff --git a/docs/docgen/templates-md/contract.hbs b/docs/docgen/templates-md/contract.hbs new file mode 100644 index 00000000..f96adce8 --- /dev/null +++ b/docs/docgen/templates-md/contract.hbs @@ -0,0 +1,158 @@ +{{reset-function-counts}} + +
+ +## `{{{name}}}` + + + + + +
+ +```solidity +import "@openzeppelin/{{__item_context.file.absolutePath}}"; +``` + +{{{process-natspec natspec.dev}}} + +{{#if modifiers}} +
+

Modifiers

+
+{{#each modifiers}} +- [{{{name}}}({{names params}})](#{{anchor}}) +{{/each}} +
+
+{{/if}} + + +{{#if has-functions}} +
+

Functions

+
+{{#each inherited-functions}} +{{#unless @first}} +#### {{contract.name}} [!toc] +{{/unless}} +{{#each functions}} +- [{{{name}}}({{names params}})](#{{anchor}}) +{{/each}} +{{/each}} +
+
+{{/if}} + +{{#if has-events}} +
+

Events

+
+{{#each inheritance}} +{{#unless @first}} +#### {{name}} [!toc] +{{/unless}} +{{#each events}} +- [{{{name}}}({{names params}})](#{{anchor}}) +{{/each}} +{{/each}} +
+
+{{/if}} + +{{#if has-errors}} +
+

Errors

+
+{{#each inheritance}} +{{#unless @first}} +#### {{name}} [!toc] +{{/unless}} +{{#each errors}} +- [{{{name}}}({{names params}})](#{{anchor}}) +{{/each}} +{{/each}} +
+
+{{/if}} + +{{#each modifiers}} + + +
+
+

{{{name}}}({{typed-params params}})

+
+

{{visibility}}

+# +
+
+ +
+ +{{{process-natspec natspec.dev}}} + +
+
+ +{{/each}} + +{{#each functions}} + + +
+
+

{{{name}}}({{typed-params params}}){{#if returns2}} → {{typed-params returns2}}{{/if}}

+
+

{{visibility}}

+# +
+
+
+ +{{{process-natspec natspec.dev}}} + +
+
+ +{{/each}} + +{{#each events}} + + +
+
+

{{{name}}}({{typed-params params}})

+
+

event

+# +
+
+ +
+ +{{{process-natspec natspec.dev}}} + +
+
+{{/each}} + +{{#each errors}} + + +
+
+

{{{name}}}({{typed-params params}})

+
+

error

+# +
+
+
+ +{{{process-natspec natspec.dev}}} + +
+
+ +{{/each}} diff --git a/docs/docgen/templates-md/helpers.js b/docs/docgen/templates-md/helpers.js new file mode 100644 index 00000000..21468281 --- /dev/null +++ b/docs/docgen/templates-md/helpers.js @@ -0,0 +1,439 @@ +const { version } = require('../../package.json'); +const fs = require('fs'); +const path = require('path'); +const { execSync } = require('child_process'); +const os = require('os'); + +const API_DOCS_PATH = 'contracts/5.x/api'; + +module.exports['oz-version'] = () => version; + +module.exports['readme-path'] = opts => { + const pageId = opts.data.root.id; + const basePath = pageId.replace(/\.(adoc|mdx)$/, ''); + return 'contracts/' + basePath + '/README.adoc'; +}; + +module.exports.readme = readmePath => { + try { + if (fs.existsSync(readmePath)) { + const readmeContent = fs.readFileSync(readmePath, 'utf8'); + return processAdocContent(readmeContent); + } + } catch (error) { + console.warn(`Warning: Could not process README at ${readmePath}:`, error.message); + } + return ''; +}; + +module.exports.names = params => params?.map(p => p.name).join(', '); + +// Simple function counter for unique IDs +const functionNameCounts = {}; + +module.exports['simple-id'] = function (name) { + if (!functionNameCounts[name]) { + functionNameCounts[name] = 1; + return name; + } else { + functionNameCounts[name]++; + return `${name}-${functionNameCounts[name]}`; + } +}; + +module.exports['reset-function-counts'] = function () { + Object.keys(functionNameCounts).forEach(key => delete functionNameCounts[key]); + return ''; +}; + +module.exports.eq = (a, b) => a === b; +module.exports['starts-with'] = (str, prefix) => str && str.startsWith(prefix); + +// Process natspec content with {REF} and link replacement +module.exports['process-natspec'] = function (natspec, opts) { + if (!natspec) return ''; + + const currentPage = opts.data.root.__item_context?.page || opts.data.root.id; + const links = getAllLinks(opts.data.site.items, currentPage); + + const processed = processReferences(natspec, links); + return processCallouts(processed); // Add callout processing at the end +}; + +module.exports['typed-params'] = params => { + return params?.map(p => `${p.type}${p.indexed ? ' indexed' : ''}${p.name ? ' ' + p.name : ''}`).join(', '); +}; + +const slug = (module.exports.slug = str => { + if (str === undefined) { + throw new Error('Missing argument'); + } + return str.replace(/\W/g, '-'); +}); + +// Link generation and caching +const linksCache = new WeakMap(); + +function getAllLinks(items, currentPage) { + if (currentPage) { + const cacheKey = currentPage; + let cache = linksCache.get(items); + if (!cache) { + cache = new Map(); + linksCache.set(items, cache); + } + + if (cache.has(cacheKey)) { + return cache.get(cacheKey); + } + } + + const res = {}; + const currentPagePath = currentPage ? currentPage.replace(/\.mdx$/, '') : ''; + + for (const item of items) { + const pagePath = item.__item_context.page.replace(/\.mdx$/, ''); + const linkPath = generateLinkPath(pagePath, currentPagePath, item.anchor); + + // Generate xref keys for legacy compatibility + res[`xref-${item.anchor}`] = linkPath; + + // Generate original case xref keys + if (item.__item_context && item.__item_context.contract) { + let originalAnchor = item.__item_context.contract.name + '-' + item.name; + if ('parameters' in item) { + const signature = item.parameters.parameters.map(v => v.typeName.typeDescriptions.typeString).join(','); + originalAnchor += slug('(' + signature + ')'); + } + res[`xref-${originalAnchor}`] = linkPath; + } + + res[slug(item.fullName)] = `[\`${item.fullName}\`](${linkPath})`; + } + + if (currentPage) { + let cache = linksCache.get(items); + if (!cache) { + cache = new Map(); + linksCache.set(items, cache); + } + cache.set(currentPage, res); + } + + return res; +} + +function generateLinkPath(pagePath, currentPagePath, anchor) { + if ( + currentPagePath && + (pagePath === currentPagePath || pagePath.split('/').pop() === currentPagePath.split('/').pop()) + ) { + return `#${anchor}`; + } + + if (currentPagePath) { + const currentParts = currentPagePath.split('/'); + const targetParts = pagePath.split('/'); + + // Find common base + let i = 0; + while (i < currentParts.length && i < targetParts.length && currentParts[i] === targetParts[i]) { + i++; + } + + const upLevels = Math.max(0, currentParts.length - 1 - i); + const downPath = targetParts.slice(i); + + if (upLevels === 0 && downPath.length === 1) { + return `${downPath[0]}#${anchor}`; + } else if (upLevels === 0) { + return `${downPath.join('/')}#${anchor}`; + } else { + const relativePath = '../'.repeat(upLevels) + downPath.join('/'); + return `${relativePath}#${anchor}`; + } + } + + return `${pagePath}#${anchor}`; +} + +// Process {REF} and other references +function processReferences(content, links) { + let result = content; + + // Handle {REF:Contract.method} patterns + result = result.replace(/\{REF:([^}]+)\}/g, (match, refId) => { + const resolvedRef = resolveReference(refId, links); + return resolvedRef || match; + }); + + // Handle AsciiDoc-style {xref-...}[text] patterns + result = result.replace(/\{(xref-[-._a-z0-9]+)\}\[([^\]]*)\]/gi, (match, key, linkText) => { + const replacement = links[key]; + return replacement ? `[${linkText}](${replacement})` : match; + }); + + // Handle cross-references in format {Contract-function-parameters} + result = result.replace( + /\{([A-Z][a-zA-Z0-9]*)-([a-zA-Z_][a-zA-Z0-9]*)-([^-}]+)\}/g, + (match, contract, func, params) => { + const commaParams = params + .replace(/-bytes\[\]/g, ',bytes[]') + .replace(/-uint[0-9]*/g, ',uint$1') + .replace(/-address/g, ',address') + .replace(/-bool/g, ',bool') + .replace(/-string/g, ',string'); + const slugifiedParams = commaParams.replace(/\W/g, '-'); + const xrefKey = `xref-${contract}-${func}-${slugifiedParams}`; + const replacement = links[xrefKey]; + if (replacement) { + return `[\`${contract}.${func}\`](${replacement})`; + } + return match; + }, + ); + + // Handle cross-references in format {Contract-function-parameters} + result = result.replace( + /\{([A-Z][a-zA-Z0-9]*)-([a-zA-Z_][a-zA-Z0-9]*)-([^}]+)\}/g, + (match, contract, func, params) => { + const commaParams = params + .replace(/-bytes\[\]/g, ',bytes[]') + .replace(/-uint[0-9]*/g, ',uint$1') + .replace(/-address/g, ',address') + .replace(/-bool/g, ',bool') + .replace(/-string/g, ',string'); + const slugifiedParams = `(${commaParams})`.replace(/\W/g, '-'); + const xrefKey = `xref-${contract}-${func}${slugifiedParams}`; + const replacement = links[xrefKey]; + if (replacement) { + return `[\`${contract}.${func}\`](${replacement})`; + } + return match; + }, + ); + + // Replace {link-key} placeholders with markdown links + result = result.replace(/\{([-._a-z0-9]+)\}/gi, (match, key) => { + const replacement = findBestMatch(key, links); + return replacement || `\`${key}\``; + }); + + return cleanupContent(result); +} + +function resolveReference(refId, links) { + // Try direct match first + const directKey = `xref-${refId.replace(/\./g, '-')}`; + if (links[directKey]) { + const parts = refId.split('.'); + const displayText = parts.length > 1 ? `${parts[0]}.${parts[1]}` : refId; + return `[\`${displayText}\`](${links[directKey]})`; + } + + // Try fuzzy matching + const matchingKeys = Object.keys(links).filter(key => { + const normalizedKey = key.replace('xref-', '').toLowerCase(); + const normalizedRef = refId.replace(/\./g, '-').toLowerCase(); + return normalizedKey.includes(normalizedRef) || normalizedRef.includes(normalizedKey); + }); + + if (matchingKeys.length > 0) { + const bestMatch = matchingKeys[0]; + const parts = refId.split('.'); + const displayText = parts.length > 1 ? `${parts[0]}.${parts[1]}` : refId; + return `[\`${displayText}\`](${links[bestMatch]})`; + } + + return null; +} + +function findBestMatch(key, links) { + let replacement = links[key]; + + if (!replacement) { + // Strategy 1: Look for keys that end with this key + let matchingKeys = Object.keys(links).filter(linkKey => { + const parts = linkKey.split('-'); + return parts.length >= 2 && parts[parts.length - 1] === key; + }); + + // Strategy 2: Try with different separators + if (matchingKeys.length === 0) { + const keyWithDashes = key.replace(/\./g, '-'); + matchingKeys = Object.keys(links).filter(linkKey => linkKey.includes(keyWithDashes)); + } + + // Strategy 3: Try partial matches + if (matchingKeys.length === 0) { + matchingKeys = Object.keys(links).filter(linkKey => { + return linkKey === key || linkKey.endsWith('-' + key) || linkKey.includes(key); + }); + } + + if (matchingKeys.length > 0) { + const nonXrefMatches = matchingKeys.filter(k => !k.startsWith('xref-')); + const bestMatch = nonXrefMatches.length > 0 ? nonXrefMatches[0] : matchingKeys[0]; + replacement = links[bestMatch]; + } + } + + return replacement; +} + +function cleanupContent(content) { + return content + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(///g, '/') + .replace(/`/g, '`') + .replace(/=/g, '=') + .replace(/&/g, '&') + .replace(/\{(\[`[^`]+`\]\([^)]+\))\}/g, '$1') + .replace(/https?:\/\/[^\s[]+\[[^\]]+\]/g, match => { + const urlMatch = match.match(/^(https?:\/\/[^[]+)\[([^\]]+)\]$/); + return urlMatch ? `[${urlMatch[2]}](${urlMatch[1]})` : match; + }); +} + +function processAdocContent(content) { + try { + const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'adoc-process-')); + const tempAdocFile = path.join(tempDir, 'temp.adoc'); + const tempMdFile = path.join(tempDir, 'temp.md'); + + // Preprocess AsciiDoc content - only handle non-admonition transformations here + const processedContent = content + .replace( + /```solidity\s*\ninclude::api:example\$([^[\]]+)\[\]\s*\n```/g, + "./examples/$1", + ) + .replace( + /\[source,solidity\]\s*\n----\s*\ninclude::api:example\$([^[\]]+)\[\]\s*\n----/g, + "./examples/$1", + ); + + fs.writeFileSync(tempAdocFile, processedContent, 'utf8'); + + execSync(`npx downdoc "${tempAdocFile}"`, { + stdio: 'pipe', + cwd: process.cwd(), + }); + + let mdContent = fs.readFileSync(tempMdFile, 'utf8'); + + // Clean up and transform markdown, then process callouts once at the end + mdContent = cleanupContent(mdContent) + .replace(/\(api:([^)]+)\.adoc([^)]*)\)/g, `(${API_DOCS_PATH}/$1.mdx$2)`) + .replace(/!\[([^\]]*)\]\(([^/)][^)]*\.(png|jpg|jpeg|gif|svg|webp))\)/g, '![$1](/$2)') + .replace(/^#+\s+.+$/m, '') + .replace(/^\n+/, ''); + + // Process callouts once at the very end + mdContent = processCallouts(mdContent); + + // Cleanup temp files + try { + fs.unlinkSync(tempAdocFile); + fs.unlinkSync(tempMdFile); + fs.rmdirSync(tempDir); + } catch (cleanupError) { + console.warn('Warning: Could not clean up temp files:', cleanupError.message); + } + + return mdContent; + } catch (error) { + console.warn('Warning: Failed to process AsciiDoc content:', error.message); + return content; + } +} + +function processCallouts(content) { + // First, normalize whitespace around block delimiters to make patterns more consistent + let result = content.replace(/\s*\n====\s*\n/g, '\n====\n').replace(/\n====\s*\n/g, '\n====\n'); + + // Handle AsciiDoc block admonitions (with ====) + result = result.replace(/^\[(NOTE|TIP)\]\s*\n====\s*\n([\s\S]*?)\n====$/gm, '\n$2\n'); + result = result.replace( + /^\[(IMPORTANT|WARNING|CAUTION)\]\s*\n====\s*\n([\s\S]*?)\n====$/gm, + '\n$2\n', + ); + + // Handle simple single-line admonitions + result = result.replace(/^(NOTE|TIP):\s*(.+)$/gm, '\n$2\n'); + result = result.replace(/^(IMPORTANT|WARNING):\s*(.+)$/gm, '\n$2\n'); + + // Handle markdown-style bold admonitions (the ones you're seeing) + result = result.replace( + /^\*\*⚠️ WARNING\*\*\\\s*\n([\s\S]*?)(?=\n\n|\n\*\*|$)/gm, + '\n$1\n', + ); + result = result.replace( + /^\*\*❗ IMPORTANT\*\*\\\s*\n([\s\S]*?)(?=\n\n|\n\*\*|$)/gm, + '\n$1\n', + ); + result = result.replace(/^\*\*📌 NOTE\*\*\\\s*\n([\s\S]*?)(?=\n\n|\n\*\*|$)/gm, '\n$1\n'); + result = result.replace(/^\*\*💡 TIP\*\*\\\s*\n([\s\S]*?)(?=\n\n|\n\*\*|$)/gm, '\n$1\n'); + + // Handle any remaining HTML-style admonitions from downdoc conversion + result = result.replace( + /
(?:💡|📌|ℹ️)?\s*(TIP|NOTE|INFO)<\/strong><\/dt>
\s*([\s\S]*?)\s*<\/dd><\/dl>/g, + '\n$2\n', + ); + result = result.replace( + /
(?:⚠️|❗)?\s*(WARNING|IMPORTANT)<\/strong><\/dt>
\s*([\s\S]*?)\s*<\/dd><\/dl>/g, + '\n$2\n', + ); + + // Fix prematurely closed callouts - move to after all paragraph text + // This handles cases where was inserted after the first line but there's more text + result = result.replace(/(]*>\n[^<]+)\n<\/Callout>\n([^<\n]+(?:\n[^<\n]+)*)/g, '$1\n$2\n'); + + // Clean up "better viewed at" notices (keep these at the end) + result = result.replace(/^\*\*📌 NOTE\*\*\\\s*\nThis document is better viewed at [^\n]*\n*/gm, ''); + result = result.replace(/^\*\*⚠️ WARNING\*\*\\\s*\nThis document is better viewed at [^\n]*\n*/gm, ''); + result = result.replace(/^\*\*❗ IMPORTANT\*\*\\\s*\nThis document is better viewed at [^\n]*\n*/gm, ''); + result = result.replace(/^\*\*💡 TIP\*\*\\\s*\nThis document is better viewed at [^\n]*\n*/gm, ''); + + // More generic cleanup for "better viewed at" notices + result = result.replace(/This document is better viewed at https:\/\/docs\.openzeppelin\.com[^\n]*\n*/g, ''); + + // Remove any resulting callouts that only contain the "better viewed at" message + result = result.replace(/]*>\s*This document is better viewed at [^\n]*\s*<\/Callout>\s*/g, ''); + result = result.replace(/]*>\s*<\/Callout>/g, ''); + + // Remove callouts that only contain whitespace/newlines + result = result.replace(/]*>\s*\n\s*<\/Callout>/g, ''); + + return result; +} + +module.exports.title = opts => { + const pageId = opts.data.root.id; + const basePath = pageId.replace(/\.(adoc|mdx)$/, ''); + const parts = basePath.split('/'); + const dirName = parts[parts.length - 1] || 'Contracts'; + return dirName + .split('-') + .map(word => word.charAt(0).toUpperCase() + word.slice(1)) + .join(' '); +}; + +module.exports.description = opts => { + const pageId = opts.data.root.id; + const basePath = pageId.replace(/\.(adoc|mdx)$/, ''); + const parts = basePath.split('/'); + const dirName = parts[parts.length - 1] || 'contracts'; + return `Smart contract ${dirName.replace('-', ' ')} utilities and implementations`; +}; + +module.exports['with-prelude'] = opts => { + const currentPage = opts.data.root.id; + const links = getAllLinks(opts.data.site.items, currentPage); + const contents = opts.fn(); + + const processed = processReferences(contents, links); + return processCallouts(processed); // Add callout processing here too +}; diff --git a/docs/docgen/templates-md/page.hbs b/docs/docgen/templates-md/page.hbs new file mode 100644 index 00000000..11c79513 --- /dev/null +++ b/docs/docgen/templates-md/page.hbs @@ -0,0 +1,13 @@ +--- +title: "{{title}}" +description: "{{description}}" +--- + +{{#with-prelude}} +{{readme (readme-path)}} +{{/with-prelude}} + +{{#each items}} +{{>contract}} + +{{/each}} diff --git a/docs/docgen/templates-md/properties.js b/docs/docgen/templates-md/properties.js new file mode 100644 index 00000000..f2453b63 --- /dev/null +++ b/docs/docgen/templates-md/properties.js @@ -0,0 +1,91 @@ +const { isNodeType, findAll } = require('solidity-ast/utils'); +const { slug } = require('./helpers'); + +module.exports.anchor = function anchor({ item, contract }) { + let res = ''; + if (contract) { + res += contract.name + '-'; + } + res += item.name; + if ('parameters' in item) { + const signature = item.parameters.parameters.map(v => v.typeName.typeDescriptions.typeString).join(','); + res += slug('(' + signature + ')'); + } + if (isNodeType('VariableDeclaration', item)) { + res += '-' + slug(item.typeName.typeDescriptions.typeString); + } + return res; +}; + +module.exports.fullname = function fullname({ item }) { + let res = ''; + res += item.name; + if ('parameters' in item) { + const signature = item.parameters.parameters.map(v => v.typeName.typeDescriptions.typeString).join(','); + res += slug('(' + signature + ')'); + } + if (isNodeType('VariableDeclaration', item)) { + res += '-' + slug(item.typeName.typeDescriptions.typeString); + } + if (res.charAt(res.length - 1) === '-') { + return res.slice(0, -1); + } + return res; +}; + +module.exports.inheritance = function ({ item, build }) { + if (!isNodeType('ContractDefinition', item)) { + // Return empty array for non-contracts (interfaces, libraries) + return []; + } + + return item.linearizedBaseContracts + .map(id => build.deref('ContractDefinition', id)) + .filter((c, i) => c.name !== 'Context' || i === 0); +}; + +module.exports['has-functions'] = function ({ item }) { + return item.inheritance && item.inheritance.some(c => c.functions.length > 0); +}; + +module.exports['has-events'] = function ({ item }) { + return item.inheritance && item.inheritance.some(c => c.events.length > 0); +}; + +module.exports['has-errors'] = function ({ item }) { + return item.inheritance && item.inheritance.some(c => c.errors.length > 0); +}; + +module.exports['internal-variables'] = function ({ item }) { + return item.variables ? item.variables.filter(({ visibility }) => visibility === 'internal') : []; +}; + +module.exports['has-internal-variables'] = function ({ item }) { + return module.exports['internal-variables']({ item }).length > 0; +}; + +module.exports.functions = function ({ item }) { + return [ + ...[...findAll('FunctionDefinition', item)].filter(f => f.visibility !== 'private'), + ...[...findAll('VariableDeclaration', item)].filter(f => f.visibility === 'public'), + ]; +}; + +module.exports.returns2 = function ({ item }) { + if (isNodeType('VariableDeclaration', item)) { + return [{ type: item.typeDescriptions.typeString }]; + } else { + return item.returns; + } +}; + +module.exports['inherited-functions'] = function ({ item }) { + const { inheritance } = item; + if (!inheritance) return []; + + const baseFunctions = new Set(inheritance.flatMap(c => c.functions.flatMap(f => f.baseFunctions ?? []))); + return inheritance.map((contract, i) => ({ + contract, + functions: contract.functions.filter(f => !baseFunctions.has(f.id) && (f.name !== 'constructor' || i === 0)), + })); +}; diff --git a/docs/examples/ConfidentialFungibleTokenMintableBurnable.sol b/docs/examples/ConfidentialFungibleTokenMintableBurnable.sol new file mode 100644 index 00000000..659c4966 --- /dev/null +++ b/docs/examples/ConfidentialFungibleTokenMintableBurnable.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, externalEuint64, ebool, euint64} from "@fhevm/solidity/lib/FHE.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/token/ConfidentialFungibleToken.sol"; + +contract ConfidentialFungibleTokenMintableBurnable is ConfidentialFungibleToken, Ownable { + using FHE for *; + + constructor( + address owner, + string memory name, + string memory symbol, + string memory uri + ) ConfidentialFungibleToken(name, symbol, uri) Ownable(owner) {} + + function mint(address to, externalEuint64 amount, bytes memory inputProof) public onlyOwner { + _mint(to, amount.fromExternal(inputProof)); + } + + function burn(address from, externalEuint64 amount, bytes memory inputProof) public onlyOwner { + _burn(from, amount.fromExternal(inputProof)); + } +} diff --git a/docs/examples/ERC20WithAutoMinerReward.sol b/docs/examples/ERC20WithAutoMinerReward.sol new file mode 100644 index 00000000..800a90a5 --- /dev/null +++ b/docs/examples/ERC20WithAutoMinerReward.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20WithAutoMinerReward is ERC20 { + constructor() ERC20("Reward", "RWD") { + _mintMinerReward(); + } + + function _mintMinerReward() internal { + _mint(block.coinbase, 1000); + } + + function _update(address from, address to, uint256 value) internal virtual override { + if (!(from == address(0) && to == block.coinbase)) { + _mintMinerReward(); + } + super._update(from, to, value); + } +} diff --git a/docs/examples/ERC20WithAutoMinerRewardUpgradeable.sol b/docs/examples/ERC20WithAutoMinerRewardUpgradeable.sol new file mode 100644 index 00000000..07f97efc --- /dev/null +++ b/docs/examples/ERC20WithAutoMinerRewardUpgradeable.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract ERC20WithAutoMinerRewardUpgradeable is Initializable, ERC20Upgradeable { + function __ERC20WithAutoMinerReward_init() internal onlyInitializing { + __ERC20_init_unchained("Reward", "RWD"); + __ERC20WithAutoMinerReward_init_unchained(); + } + + function __ERC20WithAutoMinerReward_init_unchained() internal onlyInitializing { + _mintMinerReward(); + } + + function _mintMinerReward() internal { + _mint(block.coinbase, 1000); + } + + function _update(address from, address to, uint256 value) internal virtual override { + if (!(from == address(0) && to == block.coinbase)) { + _mintMinerReward(); + } + super._update(from, to, value); + } +} diff --git a/docs/examples/ERC4626Fees.sol b/docs/examples/ERC4626Fees.sol new file mode 100644 index 00000000..1c2f79ec --- /dev/null +++ b/docs/examples/ERC4626Fees.sol @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC4626} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; + +/// @dev ERC-4626 vault with entry/exit fees expressed in https://en.wikipedia.org/wiki/Basis_point[basis point (bp)]. +/// +/// NOTE: The contract charges fees in terms of assets, not shares. This means that the fees are calculated based on the +/// amount of assets that are being deposited or withdrawn, and not based on the amount of shares that are being minted or +/// redeemed. This is an opinionated design decision that should be taken into account when integrating this contract. +/// +/// WARNING: This contract has not been audited and shouldn't be considered production ready. Consider using it with caution. +abstract contract ERC4626Fees is ERC4626 { + using Math for uint256; + + uint256 private constant _BASIS_POINT_SCALE = 1e4; + + // === Overrides === + + /// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}. + function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); + return super.previewDeposit(assets - fee); + } + + /// @dev Preview adding an entry fee on mint. See {IERC4626-previewMint}. + function previewMint(uint256 shares) public view virtual override returns (uint256) { + uint256 assets = super.previewMint(shares); + return assets + _feeOnRaw(assets, _entryFeeBasisPoints()); + } + + /// @dev Preview adding an exit fee on withdrawal. See {IERC4626-previewWithdraw}. + function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); + return super.previewWithdraw(assets + fee); + } + + /// @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}. + function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + uint256 assets = super.previewRedeem(shares); + return assets - _feeOnTotal(assets, _exitFeeBasisPoints()); + } + + /// @dev Send entry fee to {_entryFeeRecipient}. See {IERC4626-_deposit}. + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { + uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); + address recipient = _entryFeeRecipient(); + + super._deposit(caller, receiver, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}. + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual override { + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); + address recipient = _exitFeeRecipient(); + + super._withdraw(caller, receiver, owner, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + // === Fee configuration === + + function _entryFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + function _exitFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + function _entryFeeRecipient() internal view virtual returns (address) { + return address(0); // replace with e.g. a treasury address + } + + function _exitFeeRecipient() internal view virtual returns (address) { + return address(0); // replace with e.g. a treasury address + } + + // === Fee operations === + + /// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees. + /// Used in {IERC4626-mint} and {IERC4626-withdraw} operations. + function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { + return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil); + } + + /// @dev Calculates the fee part of an amount `assets` that already includes fees. + /// Used in {IERC4626-deposit} and {IERC4626-redeem} operations. + function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { + return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil); + } +} diff --git a/docs/examples/ERC4626FeesUpgradeable.sol b/docs/examples/ERC4626FeesUpgradeable.sol new file mode 100644 index 00000000..ad41bc45 --- /dev/null +++ b/docs/examples/ERC4626FeesUpgradeable.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC4626Upgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +/// @dev ERC-4626 vault with entry/exit fees expressed in https://en.wikipedia.org/wiki/Basis_point[basis point (bp)]. +/// +/// NOTE: The contract charges fees in terms of assets, not shares. This means that the fees are calculated based on the +/// amount of assets that are being deposited or withdrawn, and not based on the amount of shares that are being minted or +/// redeemed. This is an opinionated design decision that should be taken into account when integrating this contract. +/// +/// WARNING: This contract has not been audited and shouldn't be considered production ready. Consider using it with caution. +abstract contract ERC4626FeesUpgradeable is Initializable, ERC4626Upgradeable { + using Math for uint256; + + uint256 private constant _BASIS_POINT_SCALE = 1e4; + + function __ERC4626Fees_init() internal onlyInitializing { + } + + function __ERC4626Fees_init_unchained() internal onlyInitializing { + } + // === Overrides === + + /// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}. + function previewDeposit(uint256 assets) public view virtual override returns (uint256) { + uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); + return super.previewDeposit(assets - fee); + } + + /// @dev Preview adding an entry fee on mint. See {IERC4626-previewMint}. + function previewMint(uint256 shares) public view virtual override returns (uint256) { + uint256 assets = super.previewMint(shares); + return assets + _feeOnRaw(assets, _entryFeeBasisPoints()); + } + + /// @dev Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}. + function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); + return super.previewWithdraw(assets + fee); + } + + /// @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}. + function previewRedeem(uint256 shares) public view virtual override returns (uint256) { + uint256 assets = super.previewRedeem(shares); + return assets - _feeOnTotal(assets, _exitFeeBasisPoints()); + } + + /// @dev Send entry fee to {_entryFeeRecipient}. See {IERC4626-_deposit}. + function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { + uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints()); + address recipient = _entryFeeRecipient(); + + super._deposit(caller, receiver, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}. + function _withdraw( + address caller, + address receiver, + address owner, + uint256 assets, + uint256 shares + ) internal virtual override { + uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints()); + address recipient = _exitFeeRecipient(); + + super._withdraw(caller, receiver, owner, assets, shares); + + if (fee > 0 && recipient != address(this)) { + SafeERC20.safeTransfer(IERC20(asset()), recipient, fee); + } + } + + // === Fee configuration === + + function _entryFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + function _exitFeeBasisPoints() internal view virtual returns (uint256) { + return 0; // replace with e.g. 100 for 1% + } + + function _entryFeeRecipient() internal view virtual returns (address) { + return address(0); // replace with e.g. a treasury address + } + + function _exitFeeRecipient() internal view virtual returns (address) { + return address(0); // replace with e.g. a treasury address + } + + // === Fee operations === + + /// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees. + /// Used in {IERC4626-mint} and {IERC4626-withdraw} operations. + function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { + return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil); + } + + /// @dev Calculates the fee part of an amount `assets` that already includes fees. + /// Used in {IERC4626-deposit} and {IERC4626-redeem} operations. + function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) { + return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil); + } +} diff --git a/docs/examples/ERC7984MintableBurnable.sol b/docs/examples/ERC7984MintableBurnable.sol new file mode 100644 index 00000000..a337e35d --- /dev/null +++ b/docs/examples/ERC7984MintableBurnable.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, externalEuint64} from "@fhevm/solidity/lib/FHE.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {ERC7984} from "@openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol"; + +contract ERC7984MintableBurnable is ERC7984, Ownable { + constructor( + address owner, + string memory name, + string memory symbol, + string memory uri + ) ERC7984(name, symbol, uri) Ownable(owner) {} + + function mint(address to, externalEuint64 amount, bytes memory inputProof) public onlyOwner { + _mint(to, FHE.fromExternal(amount, inputProof)); + } + + function burn(address from, externalEuint64 amount, bytes memory inputProof) public onlyOwner { + _burn(from, FHE.fromExternal(amount, inputProof)); + } +} diff --git a/docs/examples/MyNFT.sol b/docs/examples/MyNFT.sol new file mode 100644 index 00000000..a9daa234 --- /dev/null +++ b/docs/examples/MyNFT.sol @@ -0,0 +1,9 @@ +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; + +contract MyNFT is ERC721 { + constructor() ERC721("MyNFT", "MNFT") {} +} diff --git a/docs/examples/MyNFTUpgradeable.sol b/docs/examples/MyNFTUpgradeable.sol new file mode 100644 index 00000000..dadfdb19 --- /dev/null +++ b/docs/examples/MyNFTUpgradeable.sol @@ -0,0 +1,14 @@ +// contracts/MyNFT.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC721Upgradeable} from "@openzeppelin/contracts/token/ERC721/ERC721Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyNFTUpgradeable is Initializable, ERC721Upgradeable { + function __MyNFT_init() internal onlyInitializing { + __ERC721_init_unchained("MyNFT", "MNFT"); + } + + function __MyNFT_init_unchained() internal onlyInitializing {} +} diff --git a/docs/examples/MyStablecoinAllowlist.sol b/docs/examples/MyStablecoinAllowlist.sol new file mode 100644 index 00000000..10e10553 --- /dev/null +++ b/docs/examples/MyStablecoinAllowlist.sol @@ -0,0 +1,18 @@ +// contracts/MyStablecoinAllowlist.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +import {ERC20Allowlist, ERC20} from "@openzeppelin/community-contracts/token/ERC20/extensions/ERC20Allowlist.sol"; + +contract MyStablecoinAllowlist is ERC20Allowlist, AccessManaged { + constructor(address initialAuthority) ERC20("MyStablecoin", "MST") AccessManaged(initialAuthority) {} + + function allowUser(address user) public restricted { + _allowUser(user); + } + + function disallowUser(address user) public restricted { + _disallowUser(user); + } +} diff --git a/docs/examples/SwapConfidentialFungibleTokenToConfidentialFungibleToken.sol b/docs/examples/SwapConfidentialFungibleTokenToConfidentialFungibleToken.sol new file mode 100644 index 00000000..572bb590 --- /dev/null +++ b/docs/examples/SwapConfidentialFungibleTokenToConfidentialFungibleToken.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol"; +import {IConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleToken.sol"; + +contract SwapConfidentialFungibleTokenToConfidentialFungibleToken { + function swapConfidentialForConfidential( + IConfidentialFungibleToken fromToken, + IConfidentialFungibleToken toToken, + externalEuint64 amountInput, + bytes calldata inputProof + ) public virtual { + require(fromToken.isOperator(msg.sender, address(this))); + + euint64 amount = FHE.fromExternal(amountInput, inputProof); + + FHE.allowTransient(amount, address(fromToken)); + euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount); + + FHE.allowTransient(amountTransferred, address(toToken)); + toToken.confidentialTransfer(msg.sender, amountTransferred); + } +} diff --git a/docs/examples/SwapConfidentialToERC20.sol b/docs/examples/SwapConfidentialToERC20.sol new file mode 100644 index 00000000..e852cf63 --- /dev/null +++ b/docs/examples/SwapConfidentialToERC20.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol"; +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; + +import {IConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleToken.sol"; + +contract SwapConfidentialToERC20 { + using FHE for *; + + error SwapConfidentialToERC20InvalidGatewayRequest(uint256 requestId); + + mapping(uint256 requestId => address) private _receivers; + IConfidentialFungibleToken private _fromToken; + IERC20 private _toToken; + + constructor(IConfidentialFungibleToken fromToken, IERC20 toToken) { + _fromToken = fromToken; + _toToken = toToken; + } + + function swapConfidentialToERC20(externalEuint64 encryptedInput, bytes memory inputProof) public { + euint64 amount = encryptedInput.fromExternal(inputProof); + amount.allowTransient(address(_fromToken)); + euint64 amountTransferred = _fromToken.confidentialTransferFrom(msg.sender, address(this), amount); + + bytes32[] memory cts = new bytes32[](1); + cts[0] = euint64.unwrap(amountTransferred); + uint256 requestID = FHE.requestDecryption(cts, this.finalizeSwap.selector); + + // register who is getting the tokens + _receivers[requestID] = msg.sender; + } + + function finalizeSwap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual { + FHE.checkSignatures(requestID, signatures); + address to = _receivers[requestID]; + require(to != address(0), SwapConfidentialToERC20InvalidGatewayRequest(requestID)); + delete _receivers[requestID]; + + if (amount != 0) { + SafeERC20.safeTransfer(_toToken, to, amount); + } + } +} diff --git a/docs/examples/SwapERC7984ToERC20.sol b/docs/examples/SwapERC7984ToERC20.sol new file mode 100644 index 00000000..920dc710 --- /dev/null +++ b/docs/examples/SwapERC7984ToERC20.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol"; +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC7984} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol"; + +contract SwapConfidentialToERC20 { + error SwapConfidentialToERC20InvalidGatewayRequest(uint256 requestId); + + mapping(uint256 requestId => address) private _receivers; + IERC7984 private _fromToken; + IERC20 private _toToken; + + constructor(IERC7984 fromToken, IERC20 toToken) { + _fromToken = fromToken; + _toToken = toToken; + } + + function swapConfidentialToERC20(externalEuint64 encryptedInput, bytes memory inputProof) public { + euint64 amount = FHE.fromExternal(encryptedInput, inputProof); + FHE.allowTransient(amount, address(_fromToken)); + euint64 amountTransferred = _fromToken.confidentialTransferFrom(msg.sender, address(this), amount); + + bytes32[] memory cts = new bytes32[](1); + cts[0] = euint64.unwrap(amountTransferred); + uint256 requestID = FHE.requestDecryption(cts, this.finalizeSwap.selector); + + // register who is getting the tokens + _receivers[requestID] = msg.sender; + } + + function finalizeSwap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual { + FHE.checkSignatures(requestID, signatures); + address to = _receivers[requestID]; + require(to != address(0), SwapConfidentialToERC20InvalidGatewayRequest(requestID)); + delete _receivers[requestID]; + + if (amount != 0) { + SafeERC20.safeTransfer(_toToken, to, amount); + } + } +} diff --git a/docs/examples/SwapERC7984ToERC7984.sol b/docs/examples/SwapERC7984ToERC7984.sol new file mode 100644 index 00000000..4a1f9153 --- /dev/null +++ b/docs/examples/SwapERC7984ToERC7984.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol"; +import {IERC7984} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol"; + +contract SwapERC7984ToERC7984 { + function swapConfidentialForConfidential( + IERC7984 fromToken, + IERC7984 toToken, + externalEuint64 amountInput, + bytes calldata inputProof + ) public virtual { + require(fromToken.isOperator(msg.sender, address(this))); + + euint64 amount = FHE.fromExternal(amountInput, inputProof); + + FHE.allowTransient(amount, address(fromToken)); + euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount); + + FHE.allowTransient(amountTransferred, address(toToken)); + toToken.confidentialTransfer(msg.sender, amountTransferred); + } +} diff --git a/docs/examples/access-control/AccessControlERC20MintBase.sol b/docs/examples/access-control/AccessControlERC20MintBase.sol new file mode 100644 index 00000000..f9e0859a --- /dev/null +++ b/docs/examples/access-control/AccessControlERC20MintBase.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract AccessControlERC20MintBase is ERC20, AccessControl { + // Create a new role identifier for the minter role + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + error CallerNotMinter(address caller); + + constructor(address minter) ERC20("MyToken", "TKN") { + // Grant the minter role to a specified account + _grantRole(MINTER_ROLE, minter); + } + + function mint(address to, uint256 amount) public { + // Check that the calling account has the minter role + if (!hasRole(MINTER_ROLE, msg.sender)) { + revert CallerNotMinter(msg.sender); + } + _mint(to, amount); + } +} diff --git a/docs/examples/access-control/AccessControlERC20MintBaseUpgradeable.sol b/docs/examples/access-control/AccessControlERC20MintBaseUpgradeable.sol new file mode 100644 index 00000000..58146b71 --- /dev/null +++ b/docs/examples/access-control/AccessControlERC20MintBaseUpgradeable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControlUpgradeable} from "@openzeppelin/contracts/access/AccessControlUpgradeable.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract AccessControlERC20MintBaseUpgradeable is Initializable, ERC20Upgradeable, AccessControlUpgradeable { + // Create a new role identifier for the minter role + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + + error CallerNotMinter(address caller); + + function __AccessControlERC20MintBase_init(address minter) internal onlyInitializing { + __ERC20_init_unchained("MyToken", "TKN"); + __AccessControlERC20MintBase_init_unchained(minter); + } + + function __AccessControlERC20MintBase_init_unchained(address minter) internal onlyInitializing { + // Grant the minter role to a specified account + _grantRole(MINTER_ROLE, minter); + } + + function mint(address to, uint256 amount) public { + // Check that the calling account has the minter role + if (!hasRole(MINTER_ROLE, msg.sender)) { + revert CallerNotMinter(msg.sender); + } + _mint(to, amount); + } +} diff --git a/docs/examples/access-control/AccessControlERC20MintMissing.sol b/docs/examples/access-control/AccessControlERC20MintMissing.sol new file mode 100644 index 00000000..1e96123c --- /dev/null +++ b/docs/examples/access-control/AccessControlERC20MintMissing.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract AccessControlERC20MintMissing is ERC20, AccessControl { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor() ERC20("MyToken", "TKN") { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { + _burn(from, amount); + } +} diff --git a/docs/examples/access-control/AccessControlERC20MintMissingUpgradeable.sol b/docs/examples/access-control/AccessControlERC20MintMissingUpgradeable.sol new file mode 100644 index 00000000..602f9056 --- /dev/null +++ b/docs/examples/access-control/AccessControlERC20MintMissingUpgradeable.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControlUpgradeable} from "@openzeppelin/contracts/access/AccessControlUpgradeable.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract AccessControlERC20MintMissingUpgradeable is Initializable, ERC20Upgradeable, AccessControlUpgradeable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + function __AccessControlERC20MintMissing_init() internal onlyInitializing { + __ERC20_init_unchained("MyToken", "TKN"); + __AccessControlERC20MintMissing_init_unchained(); + } + + function __AccessControlERC20MintMissing_init_unchained() internal onlyInitializing { + // Grant the contract deployer the default admin role: it will be able + // to grant and revoke any roles + _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { + _burn(from, amount); + } +} diff --git a/docs/examples/access-control/AccessControlERC20MintOnlyRole.sol b/docs/examples/access-control/AccessControlERC20MintOnlyRole.sol new file mode 100644 index 00000000..69b181b2 --- /dev/null +++ b/docs/examples/access-control/AccessControlERC20MintOnlyRole.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract AccessControlERC20Mint is ERC20, AccessControl { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + constructor(address minter, address burner) ERC20("MyToken", "TKN") { + _grantRole(MINTER_ROLE, minter); + _grantRole(BURNER_ROLE, burner); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { + _burn(from, amount); + } +} diff --git a/docs/examples/access-control/AccessControlERC20MintOnlyRoleUpgradeable.sol b/docs/examples/access-control/AccessControlERC20MintOnlyRoleUpgradeable.sol new file mode 100644 index 00000000..66add5d9 --- /dev/null +++ b/docs/examples/access-control/AccessControlERC20MintOnlyRoleUpgradeable.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControlUpgradeable} from "@openzeppelin/contracts/access/AccessControlUpgradeable.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract AccessControlERC20MintUpgradeable is Initializable, ERC20Upgradeable, AccessControlUpgradeable { + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + + function __AccessControlERC20Mint_init(address minter, address burner) internal onlyInitializing { + __ERC20_init_unchained("MyToken", "TKN"); + __AccessControlERC20Mint_init_unchained(minter, burner); + } + + function __AccessControlERC20Mint_init_unchained(address minter, address burner) internal onlyInitializing { + _grantRole(MINTER_ROLE, minter); + _grantRole(BURNER_ROLE, burner); + } + + function mint(address to, uint256 amount) public onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + function burn(address from, uint256 amount) public onlyRole(BURNER_ROLE) { + _burn(from, amount); + } +} diff --git a/docs/examples/access-control/AccessControlModified.sol b/docs/examples/access-control/AccessControlModified.sol new file mode 100644 index 00000000..a08546a1 --- /dev/null +++ b/docs/examples/access-control/AccessControlModified.sol @@ -0,0 +1,14 @@ +// contracts/AccessControlModified.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; + +contract AccessControlModified is AccessControl { + error AccessControlNonRevocable(); + + // Override the revokeRole function + function revokeRole(bytes32, address) public pure override { + revert AccessControlNonRevocable(); + } +} diff --git a/docs/examples/access-control/AccessControlModifiedUpgradeable.sol b/docs/examples/access-control/AccessControlModifiedUpgradeable.sol new file mode 100644 index 00000000..4d77e6cc --- /dev/null +++ b/docs/examples/access-control/AccessControlModifiedUpgradeable.sol @@ -0,0 +1,20 @@ +// contracts/AccessControlModified.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessControlUpgradeable} from "@openzeppelin/contracts/access/AccessControlUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract AccessControlModifiedUpgradeable is Initializable, AccessControlUpgradeable { + error AccessControlNonRevocable(); + + function __AccessControlModified_init() internal onlyInitializing { + } + + function __AccessControlModified_init_unchained() internal onlyInitializing { + } + // Override the revokeRole function + function revokeRole(bytes32, address) public pure override { + revert AccessControlNonRevocable(); + } +} diff --git a/docs/examples/access-control/AccessManagedERC20MintBase.sol b/docs/examples/access-control/AccessManagedERC20MintBase.sol new file mode 100644 index 00000000..2e3cf94d --- /dev/null +++ b/docs/examples/access-control/AccessManagedERC20MintBase.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract AccessManagedERC20Mint is ERC20, AccessManaged { + constructor(address manager) ERC20("MyToken", "TKN") AccessManaged(manager) {} + + // Minting is restricted according to the manager rules for this function. + // The function is identified by its selector: 0x40c10f19. + // Calculated with bytes4(keccak256('mint(address,uint256)')) + function mint(address to, uint256 amount) public restricted { + _mint(to, amount); + } +} diff --git a/docs/examples/access-control/AccessManagedERC20MintBaseUpgradeable.sol b/docs/examples/access-control/AccessManagedERC20MintBaseUpgradeable.sol new file mode 100644 index 00000000..23d47026 --- /dev/null +++ b/docs/examples/access-control/AccessManagedERC20MintBaseUpgradeable.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AccessManagedUpgradeable} from "@openzeppelin/contracts/access/manager/AccessManagedUpgradeable.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract AccessManagedERC20MintUpgradeable is Initializable, ERC20Upgradeable, AccessManagedUpgradeable { + function __AccessManagedERC20Mint_init(address manager) internal onlyInitializing { + __ERC20_init_unchained("MyToken", "TKN"); + __AccessManaged_init_unchained(manager); + } + + function __AccessManagedERC20Mint_init_unchained(address) internal onlyInitializing {} + + // Minting is restricted according to the manager rules for this function. + // The function is identified by its selector: 0x40c10f19. + // Calculated with bytes4(keccak256('mint(address,uint256)')) + function mint(address to, uint256 amount) public restricted { + _mint(to, amount); + } +} diff --git a/docs/examples/access-control/MyContractOwnable.sol b/docs/examples/access-control/MyContractOwnable.sol new file mode 100644 index 00000000..c06dc9f9 --- /dev/null +++ b/docs/examples/access-control/MyContractOwnable.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + +contract MyContract is Ownable { + constructor(address initialOwner) Ownable(initialOwner) {} + + function normalThing() public { + // anyone can call this normalThing() + } + + function specialThing() public onlyOwner { + // only the owner can call specialThing()! + } +} diff --git a/docs/examples/access-control/MyContractOwnableUpgradeable.sol b/docs/examples/access-control/MyContractOwnableUpgradeable.sol new file mode 100644 index 00000000..8a1a3b88 --- /dev/null +++ b/docs/examples/access-control/MyContractOwnableUpgradeable.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {OwnableUpgradeable} from "@openzeppelin/contracts/access/OwnableUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyContractUpgradeable is Initializable, OwnableUpgradeable { + function __MyContract_init(address initialOwner) internal onlyInitializing { + __Ownable_init_unchained(initialOwner); + } + + function __MyContract_init_unchained(address) internal onlyInitializing {} + + function normalThing() public { + // anyone can call this normalThing() + } + + function specialThing() public onlyOwner { + // only the owner can call specialThing()! + } +} diff --git a/docs/examples/account/MyAccountERC7579.sol b/docs/examples/account/MyAccountERC7579.sol new file mode 100644 index 00000000..06dfa5d4 --- /dev/null +++ b/docs/examples/account/MyAccountERC7579.sol @@ -0,0 +1,15 @@ +// contracts/MyAccountERC7579.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AccountERC7579} from "@openzeppelin/contracts/account/extensions/draft-AccountERC7579.sol"; +import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import {MODULE_TYPE_VALIDATOR} from "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyAccountERC7579 is Initializable, AccountERC7579 { + function initializeAccount(address validator, bytes calldata validatorData) public initializer { + // Install a validator module to handle signature verification + _installModule(MODULE_TYPE_VALIDATOR, validator, validatorData); + } +} diff --git a/docs/examples/account/MyAccountERC7702.sol b/docs/examples/account/MyAccountERC7702.sol new file mode 100644 index 00000000..7346ec30 --- /dev/null +++ b/docs/examples/account/MyAccountERC7702.sol @@ -0,0 +1,20 @@ +// contracts/MyAccountERC7702.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Account} from "@openzeppelin/contracts/account/Account.sol"; +import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +import {SignerEIP7702} from "@openzeppelin/contracts/utils/cryptography/signers/SignerEIP7702.sol"; + +contract MyAccountERC7702 is Account, SignerEIP7702, ERC7821, ERC721Holder, ERC1155Holder { + /// @dev Allows the entry point as an authorized executor. + function _erc7821AuthorizedExecutor( + address caller, + bytes32 mode, + bytes calldata executionData + ) internal view virtual override returns (bool) { + return caller == address(entryPoint()) || super._erc7821AuthorizedExecutor(caller, mode, executionData); + } +} diff --git a/docs/examples/account/MyAccountERC7702Upgradeable.sol b/docs/examples/account/MyAccountERC7702Upgradeable.sol new file mode 100644 index 00000000..455e0781 --- /dev/null +++ b/docs/examples/account/MyAccountERC7702Upgradeable.sol @@ -0,0 +1,26 @@ +// contracts/MyAccountERC7702.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Account} from "@openzeppelin/contracts/account/Account.sol"; +import {ERC721HolderUpgradeable} from "@openzeppelin/contracts/token/ERC721/utils/ERC721HolderUpgradeable.sol"; +import {ERC1155HolderUpgradeable} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155HolderUpgradeable.sol"; +import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +import {SignerERC7702} from "@openzeppelin/contracts/utils/cryptography/signers/SignerERC7702.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyAccountERC7702Upgradeable is Initializable, Account, SignerERC7702, ERC7821, ERC721HolderUpgradeable, ERC1155HolderUpgradeable { + function __MyAccountERC7702_init() internal onlyInitializing { + } + + function __MyAccountERC7702_init_unchained() internal onlyInitializing { + } + /// @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); + } +} diff --git a/docs/examples/account/MyAccountERC7913.sol b/docs/examples/account/MyAccountERC7913.sol new file mode 100644 index 00000000..6dff41fc --- /dev/null +++ b/docs/examples/account/MyAccountERC7913.sol @@ -0,0 +1,30 @@ +// contracts/MyAccountERC7913.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {Account} from "@openzeppelin/contracts/account/Account.sol"; +import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol"; +import {SignerERC7913} from "@openzeppelin/contracts/utils/cryptography/signers/SignerERC7913.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 {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyAccountERC7913 is Account, SignerERC7913, ERC7739, ERC7821, ERC721Holder, ERC1155Holder, Initializable { + constructor(bytes memory signer) EIP712("MyAccount7913", "1") SignerERC7913(signer) {} + + function initialize(bytes memory signer) public initializer { + _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); + } +} diff --git a/docs/examples/account/MyAccountMultiSigner.sol b/docs/examples/account/MyAccountMultiSigner.sol new file mode 100644 index 00000000..056cca62 --- /dev/null +++ b/docs/examples/account/MyAccountMultiSigner.sol @@ -0,0 +1,54 @@ +// contracts/MyAccountERC7913.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Account} from "@openzeppelin/contracts/account/Account.sol"; +import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol"; +import {MultiSignerERC7913} from "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913.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 {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyAccountMultiSigner is + Account, + MultiSignerERC7913, + ERC7739, + ERC7821, + ERC721Holder, + ERC1155Holder, + Initializable +{ + constructor( + bytes[] memory signers, + uint64 threshold + ) EIP712("MyAccountMultiSigner", "1") MultiSignerERC7913(signers, threshold) {} + + function initialize(bytes[] memory signers, uint64 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(uint64 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); + } +} diff --git a/docs/examples/account/MyAccountMultiSignerWeighted.sol b/docs/examples/account/MyAccountMultiSignerWeighted.sol new file mode 100644 index 00000000..211290a8 --- /dev/null +++ b/docs/examples/account/MyAccountMultiSignerWeighted.sol @@ -0,0 +1,62 @@ +// contracts/MyAccountERC7913.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.27; + +import {Account} from "@openzeppelin/contracts/account/Account.sol"; +import {ERC7821} from "@openzeppelin/contracts/account/extensions/draft-ERC7821.sol"; +import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol"; +import { + MultiSignerERC7913Weighted +} from "@openzeppelin/contracts/utils/cryptography/signers/MultiSignerERC7913Weighted.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 {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyAccountMultiSignerWeighted is + Account, + MultiSignerERC7913Weighted, + ERC7739, + ERC7821, + ERC721Holder, + ERC1155Holder, + Initializable +{ + constructor( + bytes[] memory signers, + uint64[] memory weights, + uint64 threshold + ) EIP712("MyAccountMultiSignerWeighted", "1") MultiSignerERC7913Weighted(signers, weights, threshold) {} + + function initialize(bytes[] memory signers, uint64[] memory weights, uint64 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(uint64 threshold) public onlyEntryPointOrSelf { + _setThreshold(threshold); + } + + function setSignerWeights(bytes[] memory signers, uint64[] 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); + } +} diff --git a/docs/examples/account/MyFactoryAccount.sol b/docs/examples/account/MyFactoryAccount.sol new file mode 100644 index 00000000..d095260c --- /dev/null +++ b/docs/examples/account/MyFactoryAccount.sol @@ -0,0 +1,37 @@ +// contracts/MyFactoryAccount.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; + +/** + * @dev A factory contract to create accounts on demand. + */ +contract MyFactoryAccount { + using Clones for address; + using Address for address; + + address private immutable _impl; + + constructor(address impl_) { + require(impl_.code.length > 0); + _impl = impl_; + } + + /// @dev Predict the address of the account + function predictAddress(bytes calldata callData) public view returns (address) { + return _impl.predictDeterministicAddress(keccak256(callData), address(this)); + } + + /// @dev Create clone accounts on demand + function cloneAndInitialize(bytes calldata callData) public returns (address) { + address predicted = predictAddress(callData); + if (predicted.code.length == 0) { + _impl.cloneDeterministic(keccak256(callData)); + predicted.functionCall(callData); + } + return predicted; + } +} diff --git a/docs/examples/account/MyFactoryAccountUpgradeable.sol b/docs/examples/account/MyFactoryAccountUpgradeable.sol new file mode 100644 index 00000000..c29bf528 --- /dev/null +++ b/docs/examples/account/MyFactoryAccountUpgradeable.sol @@ -0,0 +1,42 @@ +// contracts/MyFactoryAccount.sol +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +/** + * @dev A factory contract to create accounts on demand. + */ +contract MyFactoryAccountUpgradeable is Initializable { + using Clones for address; + using Address for address; + + address private _impl; + + function __MyFactoryAccount_init(address impl_) internal onlyInitializing { + __MyFactoryAccount_init_unchained(impl_); + } + + function __MyFactoryAccount_init_unchained(address impl_) internal onlyInitializing { + require(impl_.code.length > 0); + _impl = impl_; + } + + /// @dev Predict the address of the account + function predictAddress(bytes calldata callData) public view returns (address) { + return _impl.predictDeterministicAddress(keccak256(callData), address(this)); + } + + /// @dev Create clone accounts on demand + function cloneAndInitialize(bytes calldata callData) public returns (address) { + address predicted = predictAddress(callData); + if (predicted.code.length == 0) { + _impl.cloneDeterministic(keccak256(callData)); + predicted.functionCall(callData); + } + return predicted; + } +} diff --git a/docs/examples/account/modules/MyERC7579DelayedSocialRecovery.sol b/docs/examples/account/modules/MyERC7579DelayedSocialRecovery.sol new file mode 100644 index 00000000..a891da99 --- /dev/null +++ b/docs/examples/account/modules/MyERC7579DelayedSocialRecovery.sol @@ -0,0 +1,57 @@ +// contracts/MyERC7579DelayedSocialRecovery.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC7579Executor} from "@openzeppelin/community-contracts/account/modules/ERC7579Executor.sol"; +import {ERC7579Validator} from "@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol"; +import {Calldata} from "@openzeppelin/contracts/utils/Calldata.sol"; +import {ERC7579DelayedExecutor} from "@openzeppelin/community-contracts/account/modules/ERC7579DelayedExecutor.sol"; +import {ERC7579Multisig} from "@openzeppelin/community-contracts/account/modules/ERC7579Multisig.sol"; + +abstract contract MyERC7579DelayedSocialRecovery is EIP712, ERC7579DelayedExecutor, ERC7579Multisig { + bytes32 private constant RECOVER_TYPEHASH = + keccak256("Recover(address account,bytes32 salt,bytes32 mode,bytes executionCalldata)"); + + function isModuleType(uint256 moduleTypeId) public pure override(ERC7579Executor, ERC7579Validator) returns (bool) { + return ERC7579Executor.isModuleType(moduleTypeId) || ERC7579Executor.isModuleType(moduleTypeId); + } + + // Data encoding: [uint16(executorArgsLength), executorArgs, uint16(multisigArgsLength), multisigArgs] + function onInstall(bytes calldata data) public override(ERC7579DelayedExecutor, ERC7579Multisig) { + uint16 executorArgsLength = uint16(bytes2(data[0:2])); // First 2 bytes are the length + bytes calldata executorArgs = data[2:2 + executorArgsLength]; // Next bytes are the args + uint16 multisigArgsLength = uint16(bytes2(data[2 + executorArgsLength:4 + executorArgsLength])); // Next 2 bytes are the length + bytes calldata multisigArgs = data[4 + executorArgsLength:4 + executorArgsLength + multisigArgsLength]; // Next bytes are the args + + ERC7579DelayedExecutor.onInstall(executorArgs); + ERC7579Multisig.onInstall(multisigArgs); + } + + function onUninstall(bytes calldata) public override(ERC7579DelayedExecutor, ERC7579Multisig) { + ERC7579DelayedExecutor.onUninstall(Calldata.emptyBytes()); + ERC7579Multisig.onUninstall(Calldata.emptyBytes()); + } + + // Data encoding: [uint16(executionCalldataLength), executionCalldata, signature] + function _validateSchedule( + address account, + bytes32 salt, + bytes32 mode, + bytes calldata data + ) internal view override { + uint16 executionCalldataLength = uint16(bytes2(data[0:2])); // First 2 bytes are the length + bytes calldata executionCalldata = data[2:2 + executionCalldataLength]; // Next bytes are the calldata + bytes calldata signature = data[2 + executionCalldataLength:]; // Remaining bytes are the signature + require(_rawERC7579Validation(account, _getExecuteTypeHash(account, salt, mode, executionCalldata), signature)); + } + + function _getExecuteTypeHash( + address account, + bytes32 salt, + bytes32 mode, + bytes calldata executionCalldata + ) internal view returns (bytes32) { + return _hashTypedDataV4(keccak256(abi.encode(RECOVER_TYPEHASH, account, salt, mode, executionCalldata))); + } +} diff --git a/docs/examples/account/modules/MyERC7579Modules.sol b/docs/examples/account/modules/MyERC7579Modules.sol new file mode 100644 index 00000000..947a1d44 --- /dev/null +++ b/docs/examples/account/modules/MyERC7579Modules.sol @@ -0,0 +1,20 @@ +// contracts/MyERC7579Modules.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {IERC7579Module, IERC7579Hook} from "@openzeppelin/contracts/interfaces/draft-IERC7579.sol"; +import {ERC7579Executor} from "@openzeppelin/community-contracts/account/modules/ERC7579Executor.sol"; +import {ERC7579Validator} from "@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol"; + +// Basic validator module +abstract contract MyERC7579RecoveryValidator is ERC7579Validator {} + +// Basic executor module +abstract contract MyERC7579RecoveryExecutor is ERC7579Executor {} + +// Basic fallback handler +abstract contract MyERC7579RecoveryFallback is IERC7579Module {} + +// Basic hook +abstract contract MyERC7579RecoveryHook is IERC7579Hook {} diff --git a/docs/examples/account/modules/MyERC7579SocialRecovery.sol b/docs/examples/account/modules/MyERC7579SocialRecovery.sol new file mode 100644 index 00000000..fcfd53ea --- /dev/null +++ b/docs/examples/account/modules/MyERC7579SocialRecovery.sol @@ -0,0 +1,44 @@ +// contracts/MyERC7579SocialRecovery.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; +import {ERC7579Executor} from "@openzeppelin/community-contracts/account/modules/ERC7579Executor.sol"; +import {ERC7579Validator} from "@openzeppelin/community-contracts/account/modules/ERC7579Validator.sol"; +import {ERC7579Multisig} from "@openzeppelin/community-contracts/account/modules/ERC7579Multisig.sol"; + +abstract contract MyERC7579SocialRecovery is EIP712, ERC7579Executor, ERC7579Multisig, Nonces { + bytes32 private constant RECOVER_TYPEHASH = + keccak256("Recover(address account,bytes32 salt,uint256 nonce,bytes32 mode,bytes executionCalldata)"); + + function isModuleType(uint256 moduleTypeId) public pure override(ERC7579Executor, ERC7579Validator) returns (bool) { + return ERC7579Executor.isModuleType(moduleTypeId) || ERC7579Executor.isModuleType(moduleTypeId); + } + + // Data encoding: [uint16(executionCalldataLength), executionCalldata, signature] + function _validateExecution( + address account, + bytes32 salt, + bytes32 mode, + bytes calldata data + ) internal override returns (bytes calldata) { + uint16 executionCalldataLength = uint16(bytes2(data[0:2])); // First 2 bytes are the length + bytes calldata executionCalldata = data[2:2 + executionCalldataLength]; // Next bytes are the calldata + bytes calldata signature = data[2 + executionCalldataLength:]; // Remaining bytes are the signature + require(_rawERC7579Validation(account, _getExecuteTypeHash(account, salt, mode, executionCalldata), signature)); + return executionCalldata; + } + + function _getExecuteTypeHash( + address account, + bytes32 salt, + bytes32 mode, + bytes calldata executionCalldata + ) internal returns (bytes32) { + return + _hashTypedDataV4( + keccak256(abi.encode(RECOVER_TYPEHASH, account, salt, _useNonce(account), mode, executionCalldata)) + ); + } +} diff --git a/docs/examples/account/paymaster/PaymasterECDSASigner.sol b/docs/examples/account/paymaster/PaymasterECDSASigner.sol new file mode 100644 index 00000000..af244cf2 --- /dev/null +++ b/docs/examples/account/paymaster/PaymasterECDSASigner.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {PackedUserOperation} from "@openzeppelin/contracts/interfaces/draft-IERC4337.sol"; +import {SignerECDSA} from "@openzeppelin/contracts/utils/cryptography/signers/SignerECDSA.sol"; +import {PaymasterSigner, EIP712} from "@openzeppelin/community-contracts/account/paymaster/PaymasterSigner.sol"; + +contract PaymasterECDSASigner is PaymasterSigner, SignerECDSA, Ownable { + constructor(address signerAddr) EIP712("MyPaymasterECDSASigner", "1") Ownable(signerAddr) SignerECDSA(signerAddr) {} + + function _authorizeWithdraw() internal virtual override onlyOwner {} +} diff --git a/docs/examples/crosschain/MyCustomAxelarGatewayDestination.sol b/docs/examples/crosschain/MyCustomAxelarGatewayDestination.sol new file mode 100644 index 00000000..b7368cbf --- /dev/null +++ b/docs/examples/crosschain/MyCustomAxelarGatewayDestination.sol @@ -0,0 +1,11 @@ +// contracts/MyCustomAxelarGatewayDestination.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AxelarGatewayDestination, AxelarExecutable} from "@openzeppelin/community-contracts/crosschain/axelar/AxelarGatewayDestination.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; + +abstract contract MyCustomAxelarGatewayDestination is AxelarGatewayDestination { + /// @dev Initializes the contract with the Axelar gateway and the initial owner. + constructor(IAxelarGateway gateway, address initialOwner) AxelarExecutable(address(gateway)) {} +} diff --git a/docs/examples/crosschain/MyCustomAxelarGatewayDuplex.sol b/docs/examples/crosschain/MyCustomAxelarGatewayDuplex.sol new file mode 100644 index 00000000..3bc70122 --- /dev/null +++ b/docs/examples/crosschain/MyCustomAxelarGatewayDuplex.sol @@ -0,0 +1,11 @@ +// contracts/MyCustomAxelarGatewayDuplex.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AxelarGatewayDuplex, AxelarExecutable} from "@openzeppelin/community-contracts/crosschain/axelar/AxelarGatewayDuplex.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; + +abstract contract MyCustomAxelarGatewayDuplex is AxelarGatewayDuplex { + /// @dev Initializes the contract with the Axelar gateway and the initial owner. + constructor(IAxelarGateway gateway, address initialOwner) AxelarGatewayDuplex(gateway, initialOwner) {} +} diff --git a/docs/examples/crosschain/MyCustomAxelarGatewaySource.sol b/docs/examples/crosschain/MyCustomAxelarGatewaySource.sol new file mode 100644 index 00000000..a21af652 --- /dev/null +++ b/docs/examples/crosschain/MyCustomAxelarGatewaySource.sol @@ -0,0 +1,13 @@ +// contracts/MyERC7786ReceiverContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {AxelarGatewaySource} from "@openzeppelin/community-contracts/crosschain/axelar/AxelarGatewaySource.sol"; +import {AxelarGatewayBase} from "@openzeppelin/community-contracts/crosschain/axelar/AxelarGatewayBase.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; + +abstract contract MyCustomAxelarGatewaySource is AxelarGatewaySource { + /// @dev Initializes the contract with the Axelar gateway and the initial owner. + constructor(IAxelarGateway gateway, address initialOwner) Ownable(initialOwner) AxelarGatewayBase(gateway) {} +} diff --git a/docs/examples/crosschain/MyERC7786GatewaySource.sol b/docs/examples/crosschain/MyERC7786GatewaySource.sol new file mode 100644 index 00000000..95f3127f --- /dev/null +++ b/docs/examples/crosschain/MyERC7786GatewaySource.sol @@ -0,0 +1,43 @@ +// contracts/MyERC7786GatewaySource.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IERC7786GatewaySource} from "@openzeppelin/community-contracts/interfaces/IERC7786.sol"; +import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol"; + +abstract contract MyERC7786GatewaySource is IERC7786GatewaySource { + error UnsupportedNativeTransfer(); + + /// @inheritdoc IERC7786GatewaySource + function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { + return false; + } + + /// @inheritdoc IERC7786GatewaySource + function sendMessage( + bytes calldata recipient, // Binary Interoperable Address + bytes calldata payload, + bytes[] calldata attributes + ) external payable returns (bytes32 sendId) { + require(msg.value == 0, UnsupportedNativeTransfer()); + // Use of `if () revert` syntax to avoid accessing attributes[0] if it's empty + if (attributes.length > 0) + revert UnsupportedAttribute(attributes[0].length < 0x04 ? bytes4(0) : bytes4(attributes[0][0:4])); + + // Emit event + sendId = bytes32(0); // Explicitly set to 0. Can be used for post-processing + emit MessageSent( + sendId, + InteroperableAddress.formatEvmV1(block.chainid, msg.sender), + recipient, + payload, + 0, + attributes + ); + + // Optionally: If this is an adapter, send the message to a protocol gateway for processing + // This may require the logic for tracking destination gateway addresses and chain identifiers + + return sendId; + } +} diff --git a/docs/examples/crosschain/MyERC7786ReceiverContract.sol b/docs/examples/crosschain/MyERC7786ReceiverContract.sol new file mode 100644 index 00000000..d314eace --- /dev/null +++ b/docs/examples/crosschain/MyERC7786ReceiverContract.sol @@ -0,0 +1,25 @@ +// contracts/MyERC7786ReceiverContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.27; + +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +import {ERC7786Receiver} from "@openzeppelin/community-contracts/crosschain/utils/ERC7786Receiver.sol"; + +contract MyERC7786ReceiverContract is ERC7786Receiver, AccessManaged { + constructor(address initialAuthority) AccessManaged(initialAuthority) {} + + /// @dev Check if the given instance is a known gateway. + function _isKnownGateway(address /* instance */) internal view virtual override returns (bool) { + return true; + } + + /// @dev Internal endpoint for receiving cross-chain message. + function _processMessage( + address gateway, + bytes32 receiveId, + bytes calldata sender, + bytes calldata payload + ) internal virtual override restricted { + // Process the message here + } +} diff --git a/docs/examples/governance/MyGovernor.sol b/docs/examples/governance/MyGovernor.sol new file mode 100644 index 00000000..8f733517 --- /dev/null +++ b/docs/examples/governance/MyGovernor.sol @@ -0,0 +1,80 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +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 override returns (uint256) { + return 7200; // 1 day + } + + function votingPeriod() public pure override returns (uint256) { + return 50400; // 1 week + } + + function proposalThreshold() public pure override returns (uint256) { + return 0; + } + + // The functions below are overrides required by Solidity. + + function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(Governor, GovernorTimelockControl) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(Governor, GovernorTimelockControl) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) { + return super._executor(); + } +} diff --git a/docs/examples/governance/MyGovernorUpgradeable.sol b/docs/examples/governance/MyGovernorUpgradeable.sol new file mode 100644 index 00000000..347eb107 --- /dev/null +++ b/docs/examples/governance/MyGovernorUpgradeable.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {GovernorUpgradeable} from "@openzeppelin/contracts/governance/GovernorUpgradeable.sol"; +import {GovernorCountingSimpleUpgradeable} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimpleUpgradeable.sol"; +import {GovernorVotesUpgradeable} from "@openzeppelin/contracts/governance/extensions/GovernorVotesUpgradeable.sol"; +import {GovernorVotesQuorumFractionUpgradeable} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol"; +import {GovernorTimelockControlUpgradeable} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControlUpgradeable.sol"; +import {TimelockControllerUpgradeable} from "@openzeppelin/contracts/governance/TimelockControllerUpgradeable.sol"; +import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyGovernorUpgradeable is + Initializable, GovernorUpgradeable, + GovernorCountingSimpleUpgradeable, + GovernorVotesUpgradeable, + GovernorVotesQuorumFractionUpgradeable, + GovernorTimelockControlUpgradeable +{ + function __MyGovernor_init( + IVotes _token, + TimelockControllerUpgradeable _timelock + ) internal onlyInitializing { + __EIP712_init_unchained("MyGovernor", version()); + __Governor_init_unchained("MyGovernor"); + __GovernorVotes_init_unchained(_token); + __GovernorVotesQuorumFraction_init_unchained(4); + __GovernorTimelockControl_init_unchained(_timelock); + } + + function __MyGovernor_init_unchained( + IVotes, + TimelockControllerUpgradeable + ) internal onlyInitializing {} + + function votingDelay() public pure override returns (uint256) { + return 7200; // 1 day + } + + function votingPeriod() public pure override returns (uint256) { + return 50400; // 1 week + } + + function proposalThreshold() public pure override returns (uint256) { + return 0; + } + + // The functions below are overrides required by Solidity. + + function state(uint256 proposalId) public view override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (ProposalState) { + return super.state(proposalId); + } + + function proposalNeedsQueuing( + uint256 proposalId + ) public view virtual override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (bool) { + return super.proposalNeedsQueuing(proposalId); + } + + function _queueOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint48) { + return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _executeOperations( + uint256 proposalId, + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) { + super._executeOperations(proposalId, targets, values, calldatas, descriptionHash); + } + + function _cancel( + address[] memory targets, + uint256[] memory values, + bytes[] memory calldatas, + bytes32 descriptionHash + ) internal override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (uint256) { + return super._cancel(targets, values, calldatas, descriptionHash); + } + + function _executor() internal view override(GovernorUpgradeable, GovernorTimelockControlUpgradeable) returns (address) { + return super._executor(); + } +} diff --git a/docs/examples/governance/MyToken.sol b/docs/examples/governance/MyToken.sol new file mode 100644 index 00000000..8f906ab9 --- /dev/null +++ b/docs/examples/governance/MyToken.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol"; + +contract MyToken is ERC20, ERC20Permit, ERC20Votes { + constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {} + + // The functions below are overrides required by Solidity. + + function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/docs/examples/governance/MyTokenTimestampBased.sol b/docs/examples/governance/MyTokenTimestampBased.sol new file mode 100644 index 00000000..c5cc13bf --- /dev/null +++ b/docs/examples/governance/MyTokenTimestampBased.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol"; + +contract MyTokenTimestampBased is ERC20, ERC20Permit, ERC20Votes { + constructor() ERC20("MyTokenTimestampBased", "MTK") ERC20Permit("MyTokenTimestampBased") {} + + // Overrides IERC6372 functions to make the token & governor timestamp-based + + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public pure override returns (string memory) { + return "mode=timestamp"; + } + + // The functions below are overrides required by Solidity. + + function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/docs/examples/governance/MyTokenTimestampBasedUpgradeable.sol b/docs/examples/governance/MyTokenTimestampBasedUpgradeable.sol new file mode 100644 index 00000000..05aab3f9 --- /dev/null +++ b/docs/examples/governance/MyTokenTimestampBasedUpgradeable.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {ERC20PermitUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import {ERC20VotesUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; +import {NoncesUpgradeable} from "@openzeppelin/contracts/utils/NoncesUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyTokenTimestampBasedUpgradeable is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable { + function __MyTokenTimestampBased_init() internal onlyInitializing { + __ERC20_init_unchained("MyTokenTimestampBased", "MTK"); + __EIP712_init_unchained("MyTokenTimestampBased", "1"); + __ERC20Permit_init_unchained("MyTokenTimestampBased"); + } + + function __MyTokenTimestampBased_init_unchained() internal onlyInitializing {} + + // Overrides IERC6372 functions to make the token & governor timestamp-based + + function clock() public view override returns (uint48) { + return uint48(block.timestamp); + } + + // solhint-disable-next-line func-name-mixedcase + function CLOCK_MODE() public pure override returns (string memory) { + return "mode=timestamp"; + } + + // The functions below are overrides required by Solidity. + + function _update(address from, address to, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20PermitUpgradeable, NoncesUpgradeable) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/docs/examples/governance/MyTokenUpgradeable.sol b/docs/examples/governance/MyTokenUpgradeable.sol new file mode 100644 index 00000000..550d371d --- /dev/null +++ b/docs/examples/governance/MyTokenUpgradeable.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {ERC20PermitUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import {ERC20VotesUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; +import {NoncesUpgradeable} from "@openzeppelin/contracts/utils/NoncesUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyTokenUpgradeable is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable { + function __MyToken_init() internal onlyInitializing { + __ERC20_init_unchained("MyToken", "MTK"); + __EIP712_init_unchained("MyToken", "1"); + __ERC20Permit_init_unchained("MyToken"); + } + + function __MyToken_init_unchained() internal onlyInitializing {} + + // The functions below are overrides required by Solidity. + + function _update(address from, address to, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20PermitUpgradeable, NoncesUpgradeable) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/docs/examples/governance/MyTokenWrapped.sol b/docs/examples/governance/MyTokenWrapped.sol new file mode 100644 index 00000000..b803b889 --- /dev/null +++ b/docs/examples/governance/MyTokenWrapped.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {IERC20, ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; +import {ERC20Wrapper} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol"; + +contract MyTokenWrapped is ERC20, ERC20Permit, ERC20Votes, ERC20Wrapper { + constructor( + IERC20 wrappedToken + ) ERC20("MyTokenWrapped", "MTK") ERC20Permit("MyTokenWrapped") ERC20Wrapper(wrappedToken) {} + + // The functions below are overrides required by Solidity. + + function decimals() public view override(ERC20, ERC20Wrapper) returns (uint8) { + return super.decimals(); + } + + function _update(address from, address to, uint256 amount) internal override(ERC20, ERC20Votes) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20Permit, Nonces) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/docs/examples/governance/MyTokenWrappedUpgradeable.sol b/docs/examples/governance/MyTokenWrappedUpgradeable.sol new file mode 100644 index 00000000..922014b3 --- /dev/null +++ b/docs/examples/governance/MyTokenWrappedUpgradeable.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {ERC20PermitUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import {ERC20VotesUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesUpgradeable.sol"; +import {ERC20WrapperUpgradeable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20WrapperUpgradeable.sol"; +import {NoncesUpgradeable} from "@openzeppelin/contracts/utils/NoncesUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyTokenWrappedUpgradeable is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20WrapperUpgradeable { + function __MyTokenWrapped_init( + IERC20 wrappedToken + ) internal onlyInitializing { + __ERC20_init_unchained("MyTokenWrapped", "MTK"); + __EIP712_init_unchained("MyTokenWrapped", "1"); + __ERC20Permit_init_unchained("MyTokenWrapped"); + __ERC20Wrapper_init_unchained(wrappedToken); + } + + function __MyTokenWrapped_init_unchained( + IERC20 + ) internal onlyInitializing {} + + // The functions below are overrides required by Solidity. + + function decimals() public view override(ERC20Upgradeable, ERC20WrapperUpgradeable) returns (uint8) { + return super.decimals(); + } + + function _update(address from, address to, uint256 amount) internal override(ERC20Upgradeable, ERC20VotesUpgradeable) { + super._update(from, to, amount); + } + + function nonces(address owner) public view virtual override(ERC20PermitUpgradeable, NoncesUpgradeable) returns (uint256) { + return super.nonces(owner); + } +} diff --git a/docs/examples/token/ERC1155/GameItems.sol b/docs/examples/token/ERC1155/GameItems.sol new file mode 100644 index 00000000..c47ea940 --- /dev/null +++ b/docs/examples/token/ERC1155/GameItems.sol @@ -0,0 +1,21 @@ +// contracts/GameItems.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; + +contract GameItems is ERC1155 { + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + constructor() ERC1155("https://game.example/api/item/{id}.json") { + _mint(msg.sender, GOLD, 10 ** 18, ""); + _mint(msg.sender, SILVER, 10 ** 27, ""); + _mint(msg.sender, THORS_HAMMER, 1, ""); + _mint(msg.sender, SWORD, 10 ** 9, ""); + _mint(msg.sender, SHIELD, 10 ** 9, ""); + } +} diff --git a/docs/examples/token/ERC1155/GameItemsUpgradeable.sol b/docs/examples/token/ERC1155/GameItemsUpgradeable.sol new file mode 100644 index 00000000..d0546999 --- /dev/null +++ b/docs/examples/token/ERC1155/GameItemsUpgradeable.sol @@ -0,0 +1,27 @@ +// contracts/GameItems.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155Upgradeable} from "@openzeppelin/contracts/token/ERC1155/ERC1155Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract GameItemsUpgradeable is Initializable, ERC1155Upgradeable { + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + function __GameItems_init() internal onlyInitializing { + __ERC1155_init_unchained("https://game.example/api/item/{id}.json"); + __GameItems_init_unchained(); + } + + function __GameItems_init_unchained() internal onlyInitializing { + _mint(msg.sender, GOLD, 10 ** 18, ""); + _mint(msg.sender, SILVER, 10 ** 27, ""); + _mint(msg.sender, THORS_HAMMER, 1, ""); + _mint(msg.sender, SWORD, 10 ** 9, ""); + _mint(msg.sender, SHIELD, 10 ** 9, ""); + } +} diff --git a/docs/examples/token/ERC1155/MyERC115HolderContract.sol b/docs/examples/token/ERC1155/MyERC115HolderContract.sol new file mode 100644 index 00000000..9aad77cc --- /dev/null +++ b/docs/examples/token/ERC1155/MyERC115HolderContract.sol @@ -0,0 +1,7 @@ +// contracts/MyERC115HolderContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; + +contract MyERC115HolderContract is ERC1155Holder {} diff --git a/docs/examples/token/ERC1155/MyERC115HolderContractUpgradeable.sol b/docs/examples/token/ERC1155/MyERC115HolderContractUpgradeable.sol new file mode 100644 index 00000000..3f4a8d3a --- /dev/null +++ b/docs/examples/token/ERC1155/MyERC115HolderContractUpgradeable.sol @@ -0,0 +1,13 @@ +// contracts/MyERC115HolderContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC1155HolderUpgradeable} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155HolderUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract MyERC115HolderContractUpgradeable is Initializable, ERC1155HolderUpgradeable { function __MyERC115HolderContract_init() internal onlyInitializing { + } + + function __MyERC115HolderContract_init_unchained() internal onlyInitializing { + } +} diff --git a/docs/examples/token/ERC20/GLDToken.sol b/docs/examples/token/ERC20/GLDToken.sol new file mode 100644 index 00000000..358ae19e --- /dev/null +++ b/docs/examples/token/ERC20/GLDToken.sol @@ -0,0 +1,11 @@ +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract GLDToken is ERC20 { + constructor(uint256 initialSupply) ERC20("Gold", "GLD") { + _mint(msg.sender, initialSupply); + } +} diff --git a/docs/examples/token/ERC20/GLDTokenUpgradeable.sol b/docs/examples/token/ERC20/GLDTokenUpgradeable.sol new file mode 100644 index 00000000..ec80cc72 --- /dev/null +++ b/docs/examples/token/ERC20/GLDTokenUpgradeable.sol @@ -0,0 +1,17 @@ +// contracts/GLDToken.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC20Upgradeable} from "@openzeppelin/contracts/token/ERC20/ERC20Upgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract GLDTokenUpgradeable is Initializable, ERC20Upgradeable { + function __GLDToken_init(uint256 initialSupply) internal onlyInitializing { + __ERC20_init_unchained("Gold", "GLD"); + __GLDToken_init_unchained(initialSupply); + } + + function __GLDToken_init_unchained(uint256 initialSupply) internal onlyInitializing { + _mint(msg.sender, initialSupply); + } +} diff --git a/docs/examples/token/ERC6909/ERC6909GameItems.sol b/docs/examples/token/ERC6909/ERC6909GameItems.sol new file mode 100644 index 00000000..425040e2 --- /dev/null +++ b/docs/examples/token/ERC6909/ERC6909GameItems.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC6909Metadata} from "@openzeppelin/contracts/token/ERC6909/extensions/ERC6909Metadata.sol"; + +contract ERC6909GameItems is ERC6909Metadata { + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + constructor() { + _setDecimals(GOLD, 18); + _setDecimals(SILVER, 18); + // Default decimals is 0 + _setDecimals(SWORD, 9); + _setDecimals(SHIELD, 9); + + _mint(msg.sender, GOLD, 10 ** 18); + _mint(msg.sender, SILVER, 10_000 ** 18); + _mint(msg.sender, THORS_HAMMER, 1); + _mint(msg.sender, SWORD, 10 ** 9); + _mint(msg.sender, SHIELD, 10 ** 9); + } +} diff --git a/docs/examples/token/ERC6909/ERC6909GameItemsUpgradeable.sol b/docs/examples/token/ERC6909/ERC6909GameItemsUpgradeable.sol new file mode 100644 index 00000000..2a36a925 --- /dev/null +++ b/docs/examples/token/ERC6909/ERC6909GameItemsUpgradeable.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC6909MetadataUpgradeable} from "@openzeppelin/contracts/token/ERC6909/extensions/draft-ERC6909MetadataUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract ERC6909GameItemsUpgradeable is Initializable, ERC6909MetadataUpgradeable { + uint256 public constant GOLD = 0; + uint256 public constant SILVER = 1; + uint256 public constant THORS_HAMMER = 2; + uint256 public constant SWORD = 3; + uint256 public constant SHIELD = 4; + + function __ERC6909GameItems_init() internal onlyInitializing { + __ERC6909GameItems_init_unchained(); + } + + function __ERC6909GameItems_init_unchained() internal onlyInitializing { + _setDecimals(GOLD, 18); + _setDecimals(SILVER, 18); + // Default decimals is 0 + _setDecimals(SWORD, 9); + _setDecimals(SHIELD, 9); + + _mint(msg.sender, GOLD, 10 ** 18); + _mint(msg.sender, SILVER, 10_000 ** 18); + _mint(msg.sender, THORS_HAMMER, 1); + _mint(msg.sender, SWORD, 10 ** 9); + _mint(msg.sender, SHIELD, 10 ** 9); + } +} diff --git a/docs/examples/token/ERC721/GameItem.sol b/docs/examples/token/ERC721/GameItem.sol new file mode 100644 index 00000000..54ee6e33 --- /dev/null +++ b/docs/examples/token/ERC721/GameItem.sol @@ -0,0 +1,19 @@ +// contracts/GameItem.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {ERC721URIStorage, ERC721} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; + +contract GameItem is ERC721URIStorage { + uint256 private _nextTokenId; + + constructor() ERC721("GameItem", "ITM") {} + + function awardItem(address player, string memory tokenURI) public returns (uint256) { + uint256 tokenId = _nextTokenId++; + _mint(player, tokenId); + _setTokenURI(tokenId, tokenURI); + + return tokenId; + } +} diff --git a/docs/examples/token/ERC721/GameItemUpgradeable.sol b/docs/examples/token/ERC721/GameItemUpgradeable.sol new file mode 100644 index 00000000..aa4e601d --- /dev/null +++ b/docs/examples/token/ERC721/GameItemUpgradeable.sol @@ -0,0 +1,24 @@ +// contracts/GameItem.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ERC721URIStorageUpgradeable, ERC721Upgradeable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract GameItemUpgradeable is Initializable, ERC721URIStorageUpgradeable { + uint256 private _nextTokenId; + + function __GameItem_init() internal onlyInitializing { + __ERC721_init_unchained("GameItem", "ITM"); + } + + function __GameItem_init_unchained() internal onlyInitializing {} + + function awardItem(address player, string memory tokenURI) public returns (uint256) { + uint256 tokenId = _nextTokenId++; + _mint(player, tokenId); + _setTokenURI(tokenId, tokenURI); + + return tokenId; + } +} diff --git a/docs/examples/utilities/Base64NFT.sol b/docs/examples/utilities/Base64NFT.sol new file mode 100644 index 00000000..f738cc39 --- /dev/null +++ b/docs/examples/utilities/Base64NFT.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.24; + +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; + +contract Base64NFT is ERC721 { + using Strings for uint256; + + constructor() ERC721("Base64NFT", "MTK") {} + + // ... + + function tokenURI(uint256 tokenId) public pure override returns (string memory) { + // Equivalent to: + // { + // "name": "Base64NFT #1", + // // Replace with extra ERC-721 Metadata properties + // } + // prettier-ignore + string memory dataURI = string.concat("{\"name\": \"Base64NFT #", tokenId.toString(), "\"}"); + + return string.concat("data:application/json;base64,", Base64.encode(bytes(dataURI))); + } +} diff --git a/docs/examples/utilities/Base64NFTUpgradeable.sol b/docs/examples/utilities/Base64NFTUpgradeable.sol new file mode 100644 index 00000000..c3683c35 --- /dev/null +++ b/docs/examples/utilities/Base64NFTUpgradeable.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {ERC721Upgradeable} from "@openzeppelin/contracts/token/ERC721/ERC721Upgradeable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {Base64} from "@openzeppelin/contracts/utils/Base64.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract Base64NFTUpgradeable is Initializable, ERC721Upgradeable { + using Strings for uint256; + + function __Base64NFT_init() internal onlyInitializing { + __ERC721_init_unchained("Base64NFT", "MTK"); + } + + function __Base64NFT_init_unchained() internal onlyInitializing {} + + // ... + + function tokenURI(uint256 tokenId) public pure override returns (string memory) { + // Equivalent to: + // { + // "name": "Base64NFT #1", + // // Replace with extra ERC-721 Metadata properties + // } + // prettier-ignore + string memory dataURI = string.concat("{\"name\": \"Base64NFT #", tokenId.toString(), "\"}"); + + return string.concat("data:application/json;base64,", Base64.encode(bytes(dataURI))); + } +} diff --git a/docs/examples/utilities/Multicall.sol b/docs/examples/utilities/Multicall.sol new file mode 100644 index 00000000..07dea950 --- /dev/null +++ b/docs/examples/utilities/Multicall.sol @@ -0,0 +1,15 @@ +// contracts/Box.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Multicall} from "@openzeppelin/contracts/utils/Multicall.sol"; + +contract Box is Multicall { + function foo() public { + // ... + } + + function bar() public { + // ... + } +} diff --git a/docs/examples/utilities/MulticallUpgradeable.sol b/docs/examples/utilities/MulticallUpgradeable.sol new file mode 100644 index 00000000..b71a4fe2 --- /dev/null +++ b/docs/examples/utilities/MulticallUpgradeable.sol @@ -0,0 +1,21 @@ +// contracts/Box.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {MulticallUpgradeable} from "@openzeppelin/contracts/utils/MulticallUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; + +contract BoxUpgradeable is Initializable, MulticallUpgradeable { + function __Box_init() internal onlyInitializing { + } + + function __Box_init_unchained() internal onlyInitializing { + } + function foo() public { + // ... + } + + function bar() public { + // ... + } +} diff --git a/docs/examples/utils/cryptography/ERC7739SignerECDSA.sol b/docs/examples/utils/cryptography/ERC7739SignerECDSA.sol new file mode 100644 index 00000000..ee11ce9e --- /dev/null +++ b/docs/examples/utils/cryptography/ERC7739SignerECDSA.sol @@ -0,0 +1,24 @@ +// contracts/ERC7739ECDSA.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +import {ERC7739} from "@openzeppelin/contracts/utils/cryptography/signers/draft-ERC7739.sol"; + +contract ERC7739ECDSA is ERC7739 { + address private immutable _signer; + + constructor(address signerAddr) EIP712("ERC7739ECDSA", "1") { + _signer = signerAddr; + } + + function _rawSignatureValidation( + bytes32 hash, + bytes calldata signature + ) internal view virtual override returns (bool) { + (address recovered, ECDSA.RecoverError err, ) = ECDSA.tryRecover(hash, signature); + return _signer == recovered && err == ECDSA.RecoverError.NoError; + } +} diff --git a/docs/examples/utils/cryptography/MyContractDomain.sol b/docs/examples/utils/cryptography/MyContractDomain.sol new file mode 100644 index 00000000..e3eeb5ee --- /dev/null +++ b/docs/examples/utils/cryptography/MyContractDomain.sol @@ -0,0 +1,20 @@ +// contracts/MyContractDomain.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; + +/// @dev Unsafe contract to demonstrate the use of EIP712 and ECDSA. +abstract contract MyContractDomain is EIP712 { + function validateSignature( + address mailTo, + string memory mailContents, + bytes memory signature + ) internal view returns (address) { + bytes32 digest = _hashTypedDataV4( + keccak256(abi.encode(keccak256("Mail(address to,string contents)"), mailTo, keccak256(bytes(mailContents)))) + ); + return ECDSA.recover(digest, signature); + } +} diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc deleted file mode 100644 index 6ae94fde..00000000 --- a/docs/modules/ROOT/nav.adoc +++ /dev/null @@ -1,26 +0,0 @@ -* xref:index.adoc[Overview] - -* Learn - -** xref:zkCircuits101.adoc[ZK Circuits 101] - -** xref:extensibility.adoc[Extensibility] - -* Modules - -** xref:access.adoc[Access] -*** xref:api/access.adoc[API Reference] - -** xref:security.adoc[Security] -*** xref:api/security.adoc[API Reference] - -** Tokens -*** xref:fungibleToken.adoc[FungibleToken] -**** xref:/api/fungibleToken.adoc[API Reference] -*** xref:nonFungibleToken.adoc[NonFungibleToken] -**** xref:/api/nonFungibleToken.adoc[API Reference] -*** xref:multitoken.adoc[MultiToken] -**** xref:api/multitoken.adoc[API Reference] - -** xref:utils.adoc[Utils] -*** xref:api/utils.adoc[API Reference] diff --git a/docs/modules/ROOT/pages/access.adoc b/docs/modules/ROOT/pages/access.adoc deleted file mode 100644 index 041fd5fd..00000000 --- a/docs/modules/ROOT/pages/access.adoc +++ /dev/null @@ -1,573 +0,0 @@ -:accessControl-guide: xref:accessControl.adoc[AccessControl guide] -:role-based-access: https://en.wikipedia.org/wiki/Role-based_access_control[Role-Based Access Control (RBAC)] - -= Access Control -:steals-system: https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7[steals your whole system] - -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 {steals-system}. - -== 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 for Compact provides an Ownable module for implementing ownership in your contracts. -The initial owner must be set by using the xref:api/ownable.adoc#Ownable-initialize[initialize] circuit during construction. -This can later be changed with xref:api/ownable.adoc#Ownable-transferOwnership[transferOwnership]. - -=== Usage - -Import the Ownable module into the implementing contract. -It's recommended to prefix the module with `Ownable_` to avoid circuit signature clashes. - -```ts -// MyOwnableContract.compact - -pragma language_version >= 0.18.0; - -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/Ownable" - prefix Ownable_; - -constructor( - initialOwner: Either -) { - Ownable_initialize(initialOwner); -} -``` - -To protect a circuit so that only the contract owner may call it, -insert the `assertOnlyOwner` circuit in the beginning of the circuit body like this: - -```ts -export circuit mySensitiveCircuit(): [] { - Ownable_assertOnlyOwner(); - - // Do something -} -``` - -Contracts may expose xref:api/ownable.adoc#Ownable-transferOwnership[transferOwnership] to allow the owner to transfer ownership. - -```ts -export circuit transferOwnership( - newOwner: Either -): [] { - Ownable_transferOwnership(newOwner); -} -``` - -Here's a complete contract showcasing how to integrate the Ownable module and protect sensitive circuits. - -```ts -// SimpleOwnable.compact - -pragma language_version >= 0.18.0; - -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/Ownable" - prefix Ownable_; - -/** - * Set `initialOwner` as the owner of the contract. -*/ -constructor(initialOwner: Either) { - Ownable_initialize(initialOwner); -} - -/** - * The current owner of the contact. - */ -export circuit owner(): Either { - return Ownable_owner(); -} - -/** - * Transfers ownership of the contract. - * Can only be called by the current owner. - */ -export circuit transferOwnership( - newOwner: Either -): [] { - Ownable_transferOwnership(newOwner); -} - -/** - * Leaves the contract without an owner. - * Can only be called by the current owner. - * Renouncing ownership means `mySensitiveCircuit` can never be called again. - */ -export circuit renounceOwnership(): [] { - Ownable_renounceOwnership(); -} - -/** - * This is the protected circuit that only the current owner can call. - */ -export circuit mySensitiveCircuit(): [] { - // Protects the circuit - Ownable_assertOnlyOwner(); - - // Do something -} -``` - -TIP: For more complex logic, contracts may transfer ownership to another user irrespective of the caller by leveraging xref:api/ownable.adoc#Ownable-_transferOwnership[_transferOwnership]. -This is generally more useful when contract addresses are the owner or when a contract has a unique deployment process. - -=== Ownership transfers - -Ownership can only be transferred to `ZswapCoinPublicKeys` through the main transfer circuits (xref:api/ownable.adoc#Ownable-transferOwnership[transferOwnership] and xref:api/ownable.adoc#Ownable-_transferOwnership[_transferOwnership]). -In other words, ownership transfers to contract addresses are disallowed through these circuits. -This is because Compact currently does not support contract-to-contract calls which means if a contract is granted ownership, the owner contract cannot directly call the protected circuit. - -=== Experimental features - -This module offers experimental circuits that allow ownership to be granted to contract addresses (xref:api/ownable.adoc#Ownable-_unsafeTransferOwnership[_unsafeTransferOwnership] and xref:api/ownable.adoc#Ownable-_unsafeUncheckedTransferOwnership[_unsafeUncheckedTransferOwnership]). -Note that the circuit names are very explicit ("unsafe") with these experimental circuits. -Until contract-to-contract calls are supported, -there is no direct way for a contract to call circuits of other contracts or transfer ownership back to a user. - -NOTE: The unsafe circuits are planned to become deprecated once contract-to-contract calls become available. - -== Shielded Ownership and `ZOwnablePK` - -Privacy-preserving access control is a fundamental building block for confidential smart contracts on Midnight. -While traditional ownership patterns expose the owner's identity on-chain, -many applications require administrative control without revealing who holds that authority. - -=== Privacy-First Ownership - -The most common approach to access control in traditional smart contracts is ownership: -there's an account that is the owner of a contract and can perform administrative tasks. -However, this approach reveals the owner's identity to all observers, creating privacy and security risks. -In privacy-sensitive applications—such as confidential voting systems, private treasuries, or anonymous governance—revealing the administrator's identity may compromise the entire system's confidentiality. -This library provides the `ZOwnablePK` module that implements shielded ownership—administrative control without identity disclosure. -The owner's public key is never revealed on-chain; instead, -the contract stores only a cryptographic commitment that proves ownership without exposing the underlying identity. - -=== Commitment Scheme - -The `ZOwnablePK` module employs a two-layer cryptographic commitment scheme designed to provide privacy, -unlinkability, and collision resistance across deployments and ownership transfers. - -==== Owner ID Computation - -The foundation of the system is the owner identifier, computed as: - -```ts -id = SHA256(pk, nonce) -``` - -Where `pk` is the owner's public key and `nonce` is a secret value that may be either randomly generated -for maximum privacy or deterministically derived for recoverability. -This identifier serves as a privacy-preserving alternative to exposing the raw public key, -ensuring the owner's identity remains confidential. - -==== Owner Commitment Computation - -The final ownership commitment stored on-chain is computed as: - -```ts -commitment = SHA256(id, instanceSalt, counter, pad(32, "ZOwnablePK:shield:")) -``` - -This multi-element hash provides several security properties: - -- `id`: The privacy-preserving owner identifier described above. -- `instanceSalt`: A unique per-deployment salt that prevents commitment collisions across different contract instances, even when the same owner and nonce are used. -- `counter`: Incremented with each ownership transfer to ensure unlinkability—each transfer produces a completely different commitment even with the same underlying owner. -- `pad(32, "ZOwnablePK:shield:")`: A domain separator padded to 32 bytes that prevents hash collisions with other commitment schemes and enables safe protocol extensions. - -==== Security Properties - -This commitment scheme ensures that: - -- Public keys are never revealed on-chain. -- Observers cannot correlate past and future ownership. -- Cross-contract collisions are prevented through instance-specific salting. - -=== Nonce Generation Strategies - -The choice of nonce generation strategy represents a fundamental trade-off between simplicity/security and recoverability. -Both approaches are valid, and the best choice depends on your specific threat model and operational requirements. - -==== Random Nonce - -Generating a cryptographically strong random nonce provides the strongest privacy guarantees: - -```typescript -const randomNonce = crypto.getRandomValues(new Uint8Array(32)); -const ownerId = ZOwnablePK._computeOwnerId(publicKey, randomNonce); -``` - -This approach is easy to generate and ensures maximum unlinkability—even with sophisticated analysis, -observers cannot correlate ownership across different contracts or time periods. -However, it requires secure backup of both the private key and the nonce. -*Loss of either component results in permanent, irrecoverable loss of ownership.* - -==== Deterministic Nonce - -:rfc6979: https://datatracker.ietf.org/doc/html/rfc6979[RFC 6979] -:ed25519: https://ed25519.cr.yp.to/[Ed25519] - -Deriving the nonce deterministically enables recovery through derivation schemes. -Some examples: - -- `H(passphrase + context)` - recoverable from passphrase only, but passphrase becomes critical single point of failure. -- `H(publicKey + userPassphrase + context)` - requires both public key and passphrase. -- `H(signature + context)` where `signature = sign(context)` - leverages wallet without exposing private key. - -WARNING: When using signature-based nonce derivation, -ensure the wallet/library uses deterministic signatures ({ed25519} or {rfc6979} for ECDSA). -Non-deterministic signatures will generate different nonces on each signing, making recovery impossible. -Test the implementation by signing the same message twice then verify that the signatures match. - -*Context-Dependent Derivations:* - -- Include contract address, deployment timestamp, user ID, etc. -- Trade-off: more context is more unique but harder to recreate. - -WARNING: Approaches that avoid private key exposure (public key + passphrase, signature-based) -are generally recommended for operational security. - -Deriving the nonce deterministically from an <> and user passphrase provides a balance of security and recoverability: - -```typescript -// Example: Scrypt-based derivation -import { scryptSync } from 'node:crypto'; - -const deterministicNonce = scryptSync( - userPassphrase - publicKey + ":ZOwnablePK:nonce:v1", - 32, - { N: 16384, r: 8, p: 1 } // Standard scrypt parameters -); -const recoverableOwnerId = ZOwnablePK._computeOwnerId(publicKey, deterministicNonce); -``` - -**Security Considerations** - -The `ZOwnablePK` module remains agnostic to nonce generation methods, placing the security/convenience decision entirely with the user. Key considerations include: - -- **Backup requirements**: Random nonces require additional secure storage. -- **Recovery scenarios**: Deterministic nonces enable recovery. -- **Cross-contract correlation**: Reusing nonce strategies may reduce privacy across deployments. -- **Rotation costs**: Changing nonces requires ownership transfer transactions with associated DUST costs. - -Users should carefully evaluate their threat model, operational requirements, -and privacy needs when selecting a nonce generation strategy, -as this choice cannot be easily changed without transferring ownership. - -=== Air-Gapped Public Key (AGPK) - -For maximum privacy guarantees, -users should employ an Air-Gapped Public Key (AGPK) exclusively for contract ownership and administrative circuits. -An AGPK is a public key that maintains complete isolation from all other on-chain activities, -similar to how air-gapped systems are isolated from networks to prevent data leakage. - -==== The Privacy Enhancement - -While `ZOwnablePK` provides cryptographic privacy through its commitment scheme, -operational security practices like using an AGPK provide an additional layer of protection against correlation attacks. Even with the strongest cryptographic commitments, -reusing a public key across different on-chain activities can potentially compromise privacy -through transaction pattern analysis. - -==== AGPK Principles - -An Air-Gapped Public Key must adhere to strict isolation principles: - -- *Never used before:* The private key material -(including any seed, parent key, or entropy source from which this key is derived) -has never generated any public key that appears in any on-chain transaction, across any blockchain network. -The key material must be cryptographically pure. -- *Never used elsewhere:* From the moment of AGPK generation until its destruction, -the private key material is used exclusively for this contract's administrative functions (i.e. `assertOnlyOwner`). -No other public keys may ever be derived from or generated with the same key material. -- *Never used again:* Users commit to destroying all copies of the private key material -upon ownership renunciation or transfer. -This relies entirely on user discipline and cannot be externally verified or enforced. - -==== Best Practices Recommendation - -While neither required nor enforced by the `ZOwnablePK` module, -an Air-Gapped Public Key provides strong operational privacy hygiene for shielded contract administration. -Users should evaluate their threat model and privacy requirements when deciding whether to implement AGPK practices. - -WARNING: The effectiveness of an AGPK depends entirely on abiding by the AGPK principles. -A single transaction using the key outside the administrative context compromises all privacy benefits. - -=== Usage - -Import the `ZOwnablePK` module into the implementing contract and expose the ownership-handling circuits. -It’s recommended to prefix the module with `ZOwnablePK_` to avoid circuit signature clashes. - -```typescript -// MyZOwnablePKContract.compact - -pragma language_version >= 0.18.0; - -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK" - prefix ZOwnablePK_; - -constructor( - initOwnerCommitment: Bytes<32>, - instanceSalt: Bytes<32>, -) { - ZOwnablePK_initialize(initOwnerCommitment, instanceSalt); -} - -export circuit owner(): Bytes<32> { - return ZOwnablePK_owner(); -} - -export circuit transferOwnership(newOwnerCommitment: Bytes<32>): [] { - return ZOwnablePK_transferOwnership(disclose(newOwnerCommitment)); -} - -export circuit renounceOwnership(): [] { - return ZOwnablePK_renounceOwnership(); -} -``` - -Similar to the Ownable module, -circuits can be protected so that only the contract owner may them by adding `assertOnlyOwner` -as the first line in the circuit body like this: - -```typescript -export circuit mySensitiveCircuit(): [] { - ZOwnablePK_assertOnlyOwner(); - - // Do something -} -``` - -This covers the basics for creating a contract, but before deploying the contract, -the owner's id must be derived for the commitment scheme because it's required to deploy the contract. - -First, the owner needs to generate a secret nonce that's stored in the owner's private state. -See <>. - -Once the owner has the secret nonce generated, they can insert their public key and nonce into the following: - -```typescript -import { - CompactTypeBytes, - CompactTypeVector, - persistentHash, -} from '@midnight-ntwrk/compact-runtime'; -import { getRandomValues } from 'node:crypto'; - -// Owner ID -const generateId = ( - pk: Uint8Array, - nonce: Uint8Array, -): Uint8Array => { - const rt_type = new CompactTypeVector(2, new CompactTypeBytes(32)); - return persistentHash(rt_type, [pk, nonce]); -}; - -// Instance salt for the constructor -const generateInstanceSalt = (): Uint8Array => { - return getRandomValues(new Uint8Array(32)); -} -``` - -TIP: Another way to get the user ID is to expose `_computeOwnerId` in the contract -and call this circuit off chain through a contract simulator. -Be on the lookout for future tooling that makes this process easier. - -== 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 for an account to have permission to ban users from a system, but not create new tokens. -{role-based-access} 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 `assertOnlyOwner`. -This check can be enforced through the `assertOnlyRole` circuit. -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 `AccessControl` - -The Compact contracts library provides `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` with xref:fungibleToken.adoc[FungibleToken] to define a 'minter' role, which allows accounts that have this role to create new tokens: - -```ts -// AccessControlMinter.compact - -pragma language_version >= 0.18.0; - -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" - prefix AccessControl_; -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" - prefix FungibleToken_; - -export sealed ledger MINTER_ROLE: Bytes<32>; - -/** - * Initialize FungibleToken and MINTER_ROLE - */ -constructor( - name: Opaque<"string">, - symbol: Opaque<"string">, - decimals: Uint<8>, - minter: Either -) { - FungibleToken_initialize(name, symbol, decimals); - MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE")); - AccessControl__grantRole(MINTER_ROLE, minter); -} - -export circuit mint( - recipient: Either, - value: Uint<128>, -): [] { - AccessControl_assertOnlyRole(MINTER_ROLE); - FungibleToken__mint(recipient, value); -} -``` - -NOTE: Make sure you fully understand how xref:api/accessControl.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 xref:ownable.adoc[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 FungibleToken example by also defining a 'burner' role, which lets accounts destroy tokens. - -```ts -// AccessControlMinter.compact - -pragma language_version >= 0.18.0; - -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" - prefix AccessControl_; -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" - prefix FungibleToken_; - -export sealed ledger MINTER_ROLE: Bytes<32>; -export sealed ledger BURNER_ROLE: Bytes<32>; - -/** - * Initialize FungibleToken and MINTER_ROLE - */ -constructor( - name: Opaque<"string">, - symbol: Opaque<"string">, - decimals: Uint<8>, - minter: Either, - burner: Either -) { - FungibleToken_initialize(name, symbol, decimals); - MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE")); - BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE")); - AccessControl__grantRole(MINTER_ROLE, minter); - AccessControl__grantRole(BURNER_ROLE, burner); -} - -export circuit mint( - recipient: Either, - value: Uint<128>, -): [] { - AccessControl_assertOnlyRole(MINTER_ROLE); - FungibleToken__mint(recipient, value); -} - -export circuit burn( - recipient: Either, - value: Uint<128>, -): [] { - AccessControl_assertOnlyRole(BURNER_ROLE); - FungibleToken__burn(recipient, value); -} -``` - -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 Roles - -The FungibleToken example above uses `_grantRole`, an internal circuit 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` circuits. 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. - -Let’s take a look at the FungibleToken example, this time taking advantage of the default admin role: - -```ts -// AccessControlMinter.compact - -pragma language_version >= 0.18.0; - -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" - prefix AccessControl_; -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken" - prefix FungibleToken_; - -export sealed ledger MINTER_ROLE: Bytes<32>; -export sealed ledger BURNER_ROLE: Bytes<32>; - -/** - * Initialize FungibleToken and MINTER_ROLE - */ -constructor( - name: Opaque<"string">, - symbol: Opaque<"string">, - decimals: Uint<8>, -) { - FungibleToken_initialize(name, symbol, decimals); - MINTER_ROLE = persistentHash>(pad(32, "MINTER_ROLE")); - BURNER_ROLE = persistentHash>(pad(32, "BURNER_ROLE")); - // Grant the contract deployer the default admin role: it will be able - // to grant and revoke any roles - AccessControl__grantRole( - AccessControl_DEFAULT_ADMIN_ROLE, - left(ownPublicKey()), - ); -} - -export circuit mint( - recipient: Either, - value: Uint<128>, - ): [] { - AccessControl_assertOnlyRole(MINTER_ROLE); - FungibleToken__mint(recipient, value); -} - -export circuit burn( - recipient: Either, - value: Uint<128>, - ): [] { - AccessControl_assertOnlyRole(BURNER_ROLE); - FungibleToken__burn(recipient, value); -} -``` - -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 `ownPublicKey()`, 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 KYC, where the list of role-bearers may not be known up-front, or may be prohibitively expensive to include in a single transaction. - -=== Experimental features - -This module offers an experimental circuit that allow access control permissions to be granted to contract addresses xref:api/accessControl.adoc#AccessControl-_unsafeGrantRole[_unsafeGrantRole]. -Note that the circuit name is very explicit ("unsafe") with this experimental circuit. -Until contract-to-contract calls are supported, there is no direct way for a contract to call permissioned circuits of other contracts or grant/revoke role permissions. - -NOTE: The unsafe circuits are planned to become deprecated once contract-to-contract calls become available. \ No newline at end of file diff --git a/docs/modules/ROOT/pages/api/access.adoc b/docs/modules/ROOT/pages/api/access.adoc deleted file mode 100644 index dd900b8a..00000000 --- a/docs/modules/ROOT/pages/api/access.adoc +++ /dev/null @@ -1,603 +0,0 @@ -:github-icon: pass:[] -:accessControl-guide: xref:access.adoc#role_based_access_control[AccessControl guide] -:ownable-guide: xref:access.adoc#ownership_and_ownable[Ownable guide] -:zownablepk-guide: xref:access.adoc#shielded_ownership_and_zownablepk[ZOwnablePK guide] -:agpk: xref:access.adoc#air_gapped_public_key_agpk[Air-Gapped Public Key] -:grantRole: <> -:revokeRole: <> - -= Access Control - -This directory provides ways to restrict who can access the circuits of a contract or when they can do it. - -- `<>` provides a per-contract role based access control mechanism. Multiple hierarchical roles can be created and assigned each to multiple accounts within the same instance. - -- `<>` is a simpler mechanism with a single owner "role" that can be assigned to a single account. This simpler mechanism can be useful for quick tests but projects with production concerns are likely to outgrow it. - -- `<>` provides a privacy-preserving single owner access control mechanism using cryptographic commitments. The owner's public key is never revealed on-chain, instead storing only a commitment that proves ownership without exposing identity, suitable for applications requiring administrative control with strong privacy guarantees. - -== Core - -[.contract] -[[AccessControl]] -=== `++AccessControl++` link:https://github.com/OpenZeppelin/compact-contracts/tree/main/contracts/accessControl/src/AccessControl.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/access/AccessControl"; -``` - -Roles are referred to by their `Bytes<32>` identifier. These should be exposed in the top-level contract and be unique. The best way to achieve this is by using `export sealed ledger` hash digests that are initialized in the top-level contract: - -```typescript -import CompactStandardLibrary; -import "./node_modules/@openzeppelin-compact/contracts/src/access/AccessControl" - prefix AccessControl_; - -export sealed ledger MY_ROLE: Bytes<32>; - -constructor() { - MY_ROLE = persistentHash>(pad(32, "MY_ROLE")); -} -``` - -To restrict access to a circuit, use <>: -```typescript -circuit foo(): [] { - assertOnlyRole(MY_ROLE); - ... -} -``` - -Roles can be granted and revoked dynamically via the {grantRole} and {revokeRole} functions. Each role has an associated admin role, and only accounts that have a role's admin role can call {grantRole} and {revokeRole}. - -By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means that only accounts with this role will be able to grant or revoke other roles. More complex role relationships can be created by using <>. To set a custom `DEFAULT_ADMIN_ROLE`, implement the `Initializable` module and set `DEFAULT_ADMIN_ROLE` in the `initialize()` function. - -WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to grant and revoke this role. Extra precautions should be taken to secure accounts that have been granted it. - -TIP: For an overview of the module, read the {accessControl-guide}. - -[.contract-index] -.Circuits --- - -[.sub-index#AccessControlModule] -* xref:#AccessControl-hasRole[`++hasRole(roleId, account)++`] -* xref:#AccessControl-assertOnlyRole[`++assertOnlyRole(roleId)++`] -* xref:#AccessControl-_checkRole[`++_checkRole(roleId, account)++`] -* xref:#AccessControl-getRoleAdmin[`++getRoleAdmin(roleId)++`] -* xref:#AccessControl-grantRole[`++grantRole(roleId, account)++`] -* xref:#AccessControl-revokeRole[`++revokeRole(roleId, account)++`] -* xref:#AccessControl-renounceRole[`++renounceRole(roleId, callerConfirmation)++`] -* xref:#AccessControl-_setRoleAdmin[`++_setRoleAdmin(roleId, adminRole)++`] -* xref:#AccessControl-_grantRole[`++_grantRole(roleId, account)++`] -* xref:#AccessControl-_unsafeGrantRole[`++_unsafeGrantRole(roleId, account)++`] -* xref:#AccessControl-_revokeRole[`++_revokeRole(roleId, account)++`] --- - -[.contract-item] -[[AccessControl-hasRole]] -==== `[.contract-item-name]#++hasRole++#++(roleId: Bytes<32>, account: Either) → Boolean++` [.item-kind]#circuit# - -Returns `true` if `account` has been granted `roleId`. - -Constraints: - -- k=10, rows=487 - -[.contract-item] -[[AccessControl-assertOnlyRole]] -==== `[.contract-item-name]#++assertOnlyRole++#++(roleId: Bytes<32>) → []++` [.item-kind]#circuit# - -Reverts if caller is missing `roleId`. - -Requirements: - -- The caller must have `roleId`. -- The caller must not be a `ContractAddress`. - -Constraints: - -- k=10, rows=345 - -[.contract-item] -[[AccessControl-_checkRole]] -==== `[.contract-item-name]#++_checkRole++#++(roleId: Bytes<32>, account: Either) → []++` [.item-kind]#circuit# - -Reverts if `account` is missing `roleId`. - -Requirements: - -- `account` must have `roleId`. - -Constraints: - -- k=10, rows=467 - -[.contract-item] -[[AccessControl-getRoleAdmin]] -==== `[.contract-item-name]#++getRoleAdmin++#++(roleId: Bytes<32>) → Bytes<32>++` [.item-kind]#circuit# - -Returns the admin role that controls `roleId` or a byte array with all zero bytes if `roleId` doesn't exist. See {grantRole} and {revokeRole}. - -To change a role's admin use <>. - -Constraints: - -- k=10, rows=207 - -[.contract-item] -[[AccessControl-grantRole]] -==== `[.contract-item-name]#++grantRole++#++(roleId: Bytes<32>, account: Either) → []++` [.item-kind]#circuit# - -Grants `roleId` to `account`. - -NOTE: Granting roles to contract addresses is currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents permanently disabling access to a circuit. - -Requirements: - -- `account` must not be a ContractAddress. -- The caller must have ``roleId``'s admin role. - -Constraints: - -- k=10, rows=994 - -[.contract-item] -[[AccessControl-revokeRole]] -==== `[.contract-item-name]#++revokeRole++#++(roleId: Bytes<32>, account: Either) → []++` [.item-kind]#circuit# - -Revokes `roleId` from `account`. - -Requirements: - -- The caller must have ``roleId``'s admin role. - -Constraints: - -- k=10, rows=827 - -[.contract-item] -[[AccessControl-renounceRole]] -==== `[.contract-item-name]#++renounceRole++#++(roleId: Bytes<32>, callerConfirmation: Either) → []++` [.item-kind]#circuit# - -Revokes `roleId` from the calling account. - -Roles are often managed via {grantRole} and {revokeRole}: this circuit's -purpose is to provide a mechanism for accounts to lose their privileges -if they are compromised (such as when a trusted device is misplaced). - -NOTE: We do not provide functionality for smart contracts to renounce roles because self-executing transactions are not supported on Midnight at this time. We may revisit this in future if this feature is made available in Compact. - -Requirements: - -- The caller must be `callerConfirmation`. -- The caller must not be a `ContractAddress`. - -Constraints: - -- k=10, rows=640 - -[.contract-item] -[[AccessControl-_setRoleAdmin]] -==== `[.contract-item-name]#++_setRoleAdmin++#++(roleId: Bytes<32>, adminRole: Bytes<32>) → []++` [.item-kind]#circuit# - -Sets `adminRole` as ``roleId``'s admin role. - -Constraints: - -- k=10, rows=209 - -[.contract-item] -[[AccessControl-_grantRole]] -==== `[.contract-item-name]#++_grantRole++#++(roleId: Bytes<32>, adminRole: Bytes<32>) → Boolean++` [.item-kind]#circuit# - -Attempts to grant `roleId` to `account` and returns a boolean indicating if `roleId` was granted. - -Internal circuit without access restriction. - -NOTE: Granting roles to contract addresses is currently disallowed in this circuit until contract-to-contract interactions are supported in Compact. -This restriction prevents permanently disabling access to a circuit. - -Requirements: - -- `account` must not be a ContractAddress. - -Constraints: - -- k=10, rows=734 - -[.contract-item] -[[AccessControl-_unsafeGrantRole]] -==== `[.contract-item-name]#++_unsafeGrantRole++#++(roleId: Bytes<32>, account: Either) → Boolean++` [.item-kind]#circuit# - -Unsafe variant of <>. - -WARNING: Granting roles to contract addresses is considered unsafe because contract-to-contract calls are not currently supported. -Granting a role to a smart contract may render a circuit permanently inaccessible. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Constraints: - -- k=10, rows=733 - -[.contract-item] -[[AccessControl-_revokeRole]] -==== `[.contract-item-name]#++_revokeRole++#++(roleId: Bytes<32>, account: Either) → Boolean++` [.item-kind]#circuit# - -Attempts to revoke `roleId` from `account` and returns a boolean indicating if `roleId` was revoked. - -Internal circuit without access restriction. - -Constraints: - -- k=10, rows=563 - -[.contract] -[[Ownable]] -=== `++Ownable++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/ownable/src/Ownable.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/access/Ownable"; -``` - -Ownable provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific circuits. - -This module includes <> to restrict a circuit to be used only by the owner. - -TIP: For an overview of the module, read the {ownable-guide}. - -[.contract-index] -.Circuits --- - -[.sub-index#OwnableModule] -* xref:#Ownable-initialize[`++initialize(initialOwner)++`] -* xref:#Ownable-owner[`++owner()++`] -* xref:#Ownable-transferOwnership[`++transferOwnership(newOwner)++`] -* xref:#Ownable-_unsafeTransferOwnership[`++_unsafeTransferOwnership(newOwner)++`] -* xref:#Ownable-renounceOwnership[`++renounceOwnership()++`] -* xref:#Ownable-assertOnlyOwner[`++assertOnlyOwner(operator, approved)++`] -* xref:#Ownable-_transferOwnership[`++_transferOwnership(newOwner)++`] -* xref:#Ownable-_unsafeUncheckedTransferOwnership[`++_unsafeUncheckedTransferOwnership(newOwner)++`] --- - -[.contract-item] -[[Ownable-initialize]] -==== `[.contract-item-name]#++initialize++#++(initialOwner: Either) → []++` [.item-kind]#circuit# - -Initializes the contract by setting the `initialOwner`. -This must be called in the contract's constructor. - -Requirements: - -- Contract is not already initialized. -- `initialOwner` is not a ContractAddress. -- `initialOwner` is not the zero address. - -Constraints: - -- k=10, rows=258 - -[.contract-item] -[[Ownable-owner]] -==== `[.contract-item-name]#++owner++#++() → Either++` [.item-kind]#circuit# - -Returns the current contract owner. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=84 - -[.contract-item] -[[Ownable-transferOwnership]] -==== `[.contract-item-name]#++transferOwnership++#++(newOwner: Either) → []++` [.item-kind]#circuit# - -Transfers ownership of the contract to `newOwner`. - -NOTE: Ownership transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents permanently disabling access to a circuit. - -Requirements: - -- Contract is initialized. -- The caller is the current contract owner. -- `newOwner` is not a ContractAddress. -- `newOwner` is not the zero address. - -Constraints: - -- k=10, rows=338 - -[.contract-item] -[[Ownable-_unsafeTransferOwnership]] -==== `[.contract-item-name]#++_unsafeTransferOwnership++#++(newOwner: Either) → []++` [.item-kind]#circuit# - -Unsafe variant of <>. - -WARNING: Ownership transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Ownership privileges sent to a contract address may become uncallable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- The caller is the current contract owner. -- `newOwner` is not the zero address. - -Constraints: - -- k=10, rows=335 - -[.contract-item] -[[Ownable-renounceOwnership]] -==== `[.contract-item-name]#++renounceOwnership++#++() → []++` [.item-kind]#circuit# - -Leaves the contract without an owner. -It will not be possible to call <> circuits anymore. -Can only be called by the current owner. - -Requirements: - -- Contract is initialized. -- The caller is the current contract owner. - -Constraints: - -- k=10, rows=124 - -[.contract-item] -[[Ownable-assertOnlyOwner]] -==== `[.contract-item-name]#++assertOnlyOwner++#++() → []++` [.item-kind]#circuit# - -Throws if called by any account other than the owner. -Use this to restrict access of specific circuits to the owner. - -Requirements: - -- Contract is initialized. -- The caller is the current contract owner. - -Constraints: - -- k=10, rows=115 - -[.contract-item] -[[Ownable-_transferOwnership]] -==== `[.contract-item-name]#++_transferOwnership++#++(newOwner: Either) → []++` [.item-kind]#circuit# - -Transfers ownership of the contract to a `newOwner` without enforcing permission checks on the caller. - -NOTE: Ownership transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents permanently disabling access to a circuit. - -Requirements: - -- Contract is initialized. -- `newOwner` is not a ContractAddress. - -Constraints: - -- k=10, rows=219 - -[.contract-item] -[[Ownable-_unsafeUncheckedTransferOwnership]] -==== `[.contract-item-name]#++_unsafeUncheckedTransferOwnership++#++(newOwner: Either) → []++` [.item-kind]#circuit# - -Unsafe variant of <>. - -WARNING: Ownership transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Ownership privileges sent to a contract address may become uncallable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=216 - -[.contract] -[[ZOwnablePK]] -=== `++ZOwnablePK++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/ownable/src/ZOwnablePK.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/access/ZOwnablePK"; -``` - -`ZOwnablePK` provides a privacy-preserving access control mechanism for contracts with a single administrative user. Unlike traditional `Ownable` implementations that store or expose the owner's public key on-chain, -this module stores only a commitment to a hashed identifier derived from the owner's public key and a secret nonce. -For the strongest security guarantees, use an {agpk}. - -Ownable provides a basic access control mechanism where an account (an owner) can be granted exclusive access to specific circuits. - -This module includes <> to restrict a circuit to be used only by the owner. - -TIP: For an overview of the module, read the {zownablepk-guide}. - -[.contract-index] -.Circuits --- - -[.sub-index#ZOwnablePKModule] -* xref:#ZOwnablePK-initialize[`++initialize(ownerId, instanceSalt)++`] -* xref:#ZOwnablePK-owner[`++owner()++`] -* xref:#ZOwnablePK-transferOwnership[`++transferOwnership(newOwnerId)++`] -* xref:#ZOwnablePK-renounceOwnership[`++renounceOwnership()++`] -* xref:#ZOwnablePK-assertOnlyOwner[`++assertOnlyOwner()++`] -* xref:#ZOwnablePK-_computeOwnerCommitment[`++_computeOwnerCommitment(id, counter)++`] -* xref:#ZOwnablePK-_computeOwnerId[`++_computeOwnerId(pk, nonce)++`] -* xref:#ZOwnablePK-_transferOwnership[`++_transferOwnership(newOwnerId)++`] --- - -[.contract-item] -[[ZOwnablePK-initialize]] -==== `[.contract-item-name]#++initialize++#++(initialOwner: Either) → []++` [.item-kind]#circuit# - -Initializes the contract by setting the initial owner via `ownerId` -and storing the `instanceSalt` that acts as a privacy additive -for preventing duplicate commitments among other contracts implementing `ZOwnablePK`. - -NOTE: The `ownerId` must be calculated prior to contract deployment. -See <> - -Requirements: - -- Contract is not already initialized. -- `ownerId` is not an empty array. - -Constraints: - -- k=14, rows=14933 - -[.contract-item] -[[ZOwnablePK-owner]] -==== `[.contract-item-name]#++owner++#++() → Bytes<32>++` [.item-kind]#circuit# - -Returns the current commitment representing the contract owner. -The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=57 - -[.contract-item] -[[ZOwnablePK-transferOwnership]] -==== `[.contract-item-name]#++transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# - -Transfers ownership of the contract to `newOwnerId`. -`newOwnerId` must be precalculated and given to the current owner off chain. - -Requirements: - -- Contract is initialized. -- Caller is the current contract owner. -- `newOwnerId` is not an empty array. - -Constraints: - -- k=16, rows=39240 - -[.contract-item] -[[ZOwnablePK-renounceOwnership]] -==== `[.contract-item-name]#++renounceOwnership++#++() → []++` [.item-kind]#circuit# - -Leaves the contract without an owner. -It will not be possible to call <> circuits anymore. -Can only be called by the current owner. - -Requirements: - -- Contract is initialized. -- Caller is the current owner. - -Constraints: - -- k=15, rows=24442 - -[.contract-item] -[[ZOwnablePK-assertOnlyOwner]] -==== `[.contract-item-name]#++assertOnlyOwner++#++() → []++` [.item-kind]#circuit# - -Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match the stored owner commitment. -Use this to only allow the owner to call specific circuits. - -Requirements: - -- Contract is initialized. -- Caller's id (`SHA256(pk, nonce)`) when used in <> equals the stored `_ownerCommitment`, -thus verifying themselves as the owner. - -Constraints: - -- k=15, rows=24437 - -[.contract-item] -[[ZOwnablePK-_computeOwnerCommitment]] -==== `[.contract-item-name]#++_computeOwnerCommitment++#++(id: Bytes<32>, counter: Uint<64>) → Bytes<32>++` [.item-kind]#circuit# - -Computes the owner commitment from the given `id` and `counter`. - -**Owner ID (`id`)** - -The `id` is expected to be computed off-chain as: `id = SHA256(pk, nonce)` - -- `pk`: The owner's public key. -- `nonce`: A secret nonce scoped to the instance, ideally rotated with each transfer. - -**Commitment Derivation** - -`commitment = SHA256(id, instanceSalt, counter, domain)` - -- `id`: See above. -- `instanceSalt`: A unique per-deployment salt, stored during initialization. -This prevents commitment collisions across deployments. -- `counter`: Incremented with each ownership transfer, ensuring uniqueness even with repeated `id` values. -Cast to `Field` then `Bytes<32>` for hashing. -- `domain`: Domain separator `"ZOwnablePK:shield:"` (padded to 32 bytes) to prevent hash collisions -when extending the module or using similar commitment schemes. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=14, rows=14853 - -[.contract-item] -[[ZOwnablePK-_computeOwnerId]] -==== `[.contract-item-name]#++_computeOwnerId++#++(pk: Either, nonce: Bytes<32>) → Bytes<32>++` [.item-kind]#circuit# - -Computes the unique identifier (`id`) of the owner from their public key and a secret nonce. - -**ID Derivation** -`id = SHA256(pk, nonce)` - -- `pk`: The public key of the caller. -This is passed explicitly to allow for off-chain derivation, testing, or scenarios -where the caller is different from the subject of the computation. -We recommend using an {agpk}. -- `nonce`: A secret nonce tied to the identity. -This value should be randomly generated and kept private. -It may be rotated periodically for enhanced unlinkability. - -The result is a 32-byte commitment that uniquely identifies the owner. -This value is later used in owner commitment hashing, -and acts as a privacy-preserving alternative to a raw public key. - -NOTE: This module allows ownership to be tied to an identity commitment derived from a public key and secret nonce. -While typically used with user public keys, -this mechanism may also support contract addresses as identifiers in future contract-to-contract interactions. -Both are treated as 32-byte values (`Bytes<32>`). - -Requirements: - -- Contract is initialized. -- `pk` is not a ContractAddress. - -[.contract-item] -[[ZOwnablePK-_transferOwnership]] -==== `[.contract-item-name]#++_transferOwnership++#++(newOwnerId: Bytes<32>) → []++` [.item-kind]#circuit# - -Transfers ownership to owner id `newOwnerId` without enforcing permission checks on the caller. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=14, rows=14823 diff --git a/docs/modules/ROOT/pages/api/fungibleToken.adoc b/docs/modules/ROOT/pages/api/fungibleToken.adoc deleted file mode 100644 index 8fea2149..00000000 --- a/docs/modules/ROOT/pages/api/fungibleToken.adoc +++ /dev/null @@ -1,389 +0,0 @@ -:github-icon: pass:[] -:fungible-guide: xref:fungibleToken.adoc[FungibleToken guide] - -= FungibleToken - -This module provides the full FungibleToken module API. - -TIP: For an overview of the module, read the {fungible-guide}. - -== Core - -[.contract] -[[FungibleToken]] -=== `++FungibleToken++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/fungibleToken/src/FungibleToken.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/token/FungibleToken"; -``` - -[.contract-index] -.Circuits --- -[.sub-index#FungibleTokenModule] -* xref:#FungibleTokenModule-initialize[`++initialize(name_, symbol_, decimals_)++`] -* xref:#FungibleTokenModule-name[`++name()++`] -* xref:#FungibleTokenModule-symbol[`++symbol()++`] -* xref:#FungibleTokenModule-decimals[`++decimals()++`] -* xref:#FungibleTokenModule-totalSupply[`++totalSupply()++`] -* xref:#FungibleTokenModule-balanceOf[`++balanceOf(account)++`] -* xref:#FungibleTokenModule-transfer[`++transfer(to, value)++`] -* xref:#FungibleTokenModule-_unsafeTransfer[`++_unsafeTransfer(to, value)++`] -* xref:#FungibleTokenModule-allowance[`++allowance(owner, spender)++`] -* xref:#FungibleTokenModule-approve[`++approve(spender, value)++`] -* xref:#FungibleTokenModule-transferFrom[`++transferFrom(from, to, value)++`] -* xref:#FungibleTokenModule-_unsafeTransferFrom[`++_unsafeTransferFrom(from, to, value)++`] -* xref:#FungibleTokenModule-_transfer[`++_transfer(from, to, value)++`] -* xref:#FungibleTokenModule-_unsafeUncheckedTransfer[`++_unsafeUncheckedTransfer(from, to, value)++`] -* xref:#FungibleTokenModule-_update[`++_update(from, to, value)++`] -* xref:#FungibleTokenModule-_mint[`++_mint(account, value)++`] -* xref:#FungibleTokenModule-_unsafeMint[`++_unsafeMint(account, value)++`] -* xref:#FungibleTokenModule-_burn[`++_burn(account, value)++`] -* xref:#FungibleTokenModule-_approve[`++_approve(owner, spender, value)++`] -* xref:#FungibleTokenModule-_spendAllowance[`++_spendAllowance(owner, spender, value)++`] --- - -[.contract-item] -[[FungibleTokenModule-initialize]] -==== `[.contract-item-name]#++initialize++#++(name_: Opaque<"string">, symbol_: Opaque<"string">, decimals_: Uint<8>) → []++` [.item-kind]#circuit# - -Initializes the contract by setting the name, symbol, and decimals. - -This MUST be called in the implementing contract's constructor. -Failure to do so can lead to an irreparable contract. - -Requirements: - -- Contract is not initialized. - -Constraints: - -- k=10, rows=71 - -[.contract-item] -[[FungibleTokenModule-name]] -==== `[.contract-item-name]#++name++#++() → Opaque<"string">++` [.item-kind]#circuit# - -Returns the token name. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=37 - -[.contract-item] -[[FungibleTokenModule-symbol]] -==== `[.contract-item-name]#++symbol++#++() → Opaque<"string">++` [.item-kind]#circuit# - -Returns the symbol of the token. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=37 - -[.contract-item] -[[FungibleTokenModule-decimals]] -==== `[.contract-item-name]#++decimals++#++() → Uint<8>++` [.item-kind]#circuit# - -Returns the number of decimals used to get its user representation. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=36 - -[.contract-item] -[[FungibleTokenModule-totalSupply]] -==== `[.contract-item-name]#++totalSupply++#++() → Uint<128>++` [.item-kind]#circuit# - -Returns the value of tokens in existence. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=36 - -[.contract-item] -[[FungibleTokenModule-balanceOf]] -==== `[.contract-item-name]#++balanceOf++#++(account: Either) → Uint<128>++` [.item-kind]#circuit# - -Returns the value of tokens owned by `account`. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=310 - -[.contract-item] -[[FungibleTokenModule-transfer]] -==== `[.contract-item-name]#++transfer++#++(to: Either, value: Uint<128>) → Boolean++` [.item-kind]#circuit# - -Moves a `value` amount of tokens from the caller's account to `to`. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- Contract is initialized. -- `to` is not a ContractAddress. -- `to` is not the zero address. -- The caller has a balance of at least `value`. - -Constraints: - -- k=11, rows=1173 - -[.contract-item] -[[FungibleTokenModule-_unsafeTransfer]] -==== `[.contract-item-name]#++_unsafeTransfer++#++(to: Either, value: Uint<128>) → Boolean++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `to` is not the zero address. -- The caller has a balance of at least `value`. - -Constraints: - -- k=11, rows=1170 - -[.contract-item] -[[FungibleTokenModule-allowance]] -==== `[.contract-item-name]#++allowance++#++(owner: Either, spender: Either) → Uint<128>++` [.item-kind]#circuit# - -Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through <>. -This value changes when <> or <> are called. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=624 - -[.contract-item] -[[FungibleTokenModule-approve]] -==== `[.contract-item-name]#++approve++#++(spender: Either, value: Uint<128>) → Boolean++` [.item-kind]#circuit# - -Sets a `value` amount of tokens as allowance of `spender` over the caller's tokens. - -Requirements: - -- Contract is initialized. -- `spender` is not the zero address. - -Constraints: - -- k=10, rows=452 - -[.contract-item] -[[FungibleTokenModule-transferFrom]] -==== `[.contract-item-name]#++transferFrom++#++(from: Either, to: Either, value: Uint<128>) → Boolean++` [.item-kind]#circuit# - -Moves `value` tokens from `from` to `to` using the allowance mechanism. -`value` is the deducted from the caller's allowance. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- Contract is initialized. -- `from` is not the zero address. -- `from` must have a balance of at least `value`. -- `to` is not the zero address. -- `to` is not a ContractAddress. -- The caller has an allowance of ``from``'s tokens of at least `value`. - -Constraints: - -- k=11, rows=1821 - -[.contract-item] -[[FungibleTokenModule-_unsafeTransferFrom]] -==== `[.contract-item-name]#++_unsafeTransferFrom++#++(from: Either, to: Either, value: Uint<128>) → Boolean++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `from` is not the zero address. -- `from` must have a balance of at least `value`. -- `to` is not the zero address. -- The caller has an allowance of ``from``'s tokens of at least `value`. - -Constraints: - -- k=11, rows=1818 - -[.contract-item] -[[FungibleTokenModule-_transfer]] -==== `[.contract-item-name]#++_transfer++#++(from: Either, to: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Moves a `value` amount of tokens from `from` to `to`. -This circuit is equivalent to <>, and can be used to e.g. -implement automatic token fees, slashing mechanisms, etc. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- Contract is initialized. -- `from` is not be the zero address. -- `from` must have at least a balance of `value`. -- `to` must not be the zero address. -- `to` must not be a ContractAddress. - -Constraints: - -- k=11, rows=1312 - -[.contract-item] -[[FungibleTokenModule-_unsafeUncheckedTransfer]] -==== `[.contract-item-name]#++_unsafeUncheckedTransfer++#++(from: Either, to: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `from` is not the zero address. -- `to` is not the zero address. - -Constraints: - -- k=11, rows=1309 - -[.contract-item] -[[FungibleTokenModule-_update]] -==== `[.contract-item-name]#++_update++#++(from: Either, to: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Transfers a `value` amount of tokens from `from` to `to`, -or alternatively mints (or burns) if `from` (or `to`) is the zero address. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=11, rows=1305 - -[.contract-item] -[[FungibleTokenModule-_mint]] -==== `[.contract-item-name]#++_mint++#++(account: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Creates a `value` amount of tokens and assigns them to `account`, by transferring it from the zero address. -Relies on the `update` mechanism. - -Requirements: - -- Contract is initialized. -- `to` is not a ContractAddress. -- `account` is not the zero address. - -Constraints: - -- k=10, rows=752 - -[.contract-item] -[[FungibleTokenModule-_unsafeMint]] -==== `[.contract-item-name]#++_unsafeMint++#++(account: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `account` is not the zero address. - -Constraints: - -- k=10, rows=749 - -[.contract-item] -[[FungibleTokenModule-_burn]] -==== `[.contract-item-name]#++_burn++#++(account: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Destroys a `value` amount of tokens from `account`, lowering the total supply. -Relies on the `_update` mechanism. - -Requirements: - -- Contract is initialized. -- `account` is not the zero address. -- `account` must have at least a balance of `value`. - -Constraints: - -- k=10, rows=773 - -[.contract-item] -[[FungibleTokenModule-_approve]] -==== `[.contract-item-name]#++_approve++#++(owner: Either, spender: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Sets `value` as the allowance of `spender` over the ``owner``'s tokens. -This circuit is equivalent to `approve`, and can be used to e.g. set automatic allowances for certain subsystems, etc. - -Requirements: - -- Contract is initialized. -- `owner` is not the zero address. -- `spender` is not the zero address. - -Constraints: - -- k=10, rows=583 - -[.contract-item] -[[FungibleTokenModule-_spendAllowance]] -==== `[.contract-item-name]#++_spendAllowance++#++(owner: Either, spender: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Updates ``owner``'s allowance for `spender` based on spent `value`. -Does not update the allowance value in case of infinite allowance. - -Requirements: - -- Contract is initialized. -- `spender` must have at least an allowance of `value` from `owner`. - -Constraints: - -- k=10, rows=931 diff --git a/docs/modules/ROOT/pages/api/multitoken.adoc b/docs/modules/ROOT/pages/api/multitoken.adoc deleted file mode 100644 index 05cd810c..00000000 --- a/docs/modules/ROOT/pages/api/multitoken.adoc +++ /dev/null @@ -1,313 +0,0 @@ -:github-icon: pass:[] -:multiToken-guide: xref:multitoken.adoc[MultiToken guide] -:erc1155-metadata: https://eips.ethereum.org/EIPS/eip-1155#metadata[ERC1155-Metadata] - -= MultiToken - -This module provides the full MultiToken module API. - -TIP: For an overview of the module, read the {multiToken-guide}. - -== Core - -[.contract] -[[MultiToken]] -=== `++MultiToken++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/multiToken/src/MultiToken.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/token/MultiToken"; -``` - -[.contract-index] -.Circuits --- - -[.sub-index#MultiTokenModule] -* xref:#MultiTokenModule-initialize[`++initialize(uri_)++`] -* xref:#MultiTokenModule-uri[`++uri(id)++`] -* xref:#MultiTokenModule-balanceOf[`++balanceOf(account, id)++`] -* xref:#MultiTokenModule-setApprovalForAll[`++setApprovalForAll(operator, approved)++`] -* xref:#MultiTokenModule-isApprovedForAll[`++isApprovedForAll(account, operator)++`] -* xref:#MultiTokenModule-transferFrom[`++transferFrom(from, to, id, value)++`] -* xref:#MultiTokenModule-_transfer[`++_transfer(from, to, id, value)++`] -* xref:#MultiTokenModule-_update[`++_update(from, to, id, value)++`] -* xref:#MultiTokenModule-_unsafeTransferFrom[`++_unsafeTransferFrom(from, to, id, value)++`] -* xref:#MultiTokenModule-_unsafeTransfer[`++_unsafeTransfer(from, to, id, value)++`] -* xref:#MultiTokenModule-_setURI[`++_setURI(newURI)++`] -* xref:#MultiTokenModule-_mint[`++_mint(to, id, value)++`] -* xref:#MultiTokenModule-_unsafeMint[`++_unsafeMint(to, id, value)++`] -* xref:#MultiTokenModule-_burn[`++_burn(from, id, value)++`] -* xref:#MultiTokenModule-_setApprovalForAll[`++_setApprovalForAll(owner, operator, approved)++`] --- - -[.contract-item] -[[MultiTokenModule-initialize]] -==== `[.contract-item-name]#++initialize++#++(uri_: Opaque<"string">) → []++` [.item-kind]#circuit# - -Initializes the contract by setting the base URI for all tokens. - -This MUST be called in the implementing contract's constructor. -Failure to do so can lead to an irreparable contract. - -Requirements: - -- Contract is not initialized. - -Constraints: - -- k=10, rows=45 - -[.contract-item] -[[MultiTokenModule-uri]] -==== `[.contract-item-name]#++uri++#++(id: Uint<128>) → Opaque<"string">++` [.item-kind]#circuit# - -This implementation returns the same URI for *all* token types. -It relies on the token type ID substitution mechanism defined in the EIP: {erc1155-metadata}. -Clients calling this function must replace the `\{id\}` substring with the actual token type ID. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=90 - -[.contract-item] -[[MultiTokenModule-balanceOf]] -==== `[.contract-item-name]#++balanceOf++#++(account: Either, id: Uint<128>) → Uint<128>++` [.item-kind]#circuit# - -Returns the amount of `id` tokens owned by `account`. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=439 - -[.contract-item] -[[MultiTokenModule-setApprovalForAll]] -==== `[.contract-item-name]#++setApprovalForAll++#++(operator: Either, approved: Boolean) → []++` [.item-kind]#circuit# - -Enables or disables approval for `operator` to manage all of the caller's assets. - -Requirements: - -- Contract is initialized. -- `operator` is not the zero address. - -Constraints: - -- k=10, rows=404 - -[.contract-item] -[[MultiTokenModule-isApprovedForAll]] -==== `[.contract-item-name]#++balanceOf++#++(account: Either, operator: Either) → Boolean++` [.item-kind]#circuit# - -Queries if `operator` is an authorized operator for `owner`. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=619 - -[.contract-item] -[[MultiTokenModule-transferFrom]] -==== `[.contract-item-name]#++transferFrom++#++(from: Either, to: Either, id: Uint<128>, value: Uint<128>) → []++` [.item-kind]#circuit# - -Transfers ownership of `value` amount of `id` tokens from `from` to `to`. -The caller must be `from` or approved to transfer on their behalf. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- Contract is initialized. -- `to` is not a ContractAddress. -- `to` is not the zero address. -- `from` is not the zero address. -- Caller must be `from` or approved via `setApprovalForAll`. -- `from` must have an `id` balance of at least `value`. - -Constraints: - -- k=11, rows=1882 - -[.contract-item] -[[MultiTokenModule-_transfer]] -==== `[.contract-item-name]#++_transfer++#++(from: Either, to: Either, id: Uint<128>, value: Uint<128>)++` [.item-kind]#circuit# - -Transfers ownership of `value` amount of `id` tokens from `from` to `to`. -Does not impose restrictions on the caller, making it suitable for composition in higher-level contract logic. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- Contract is initialized. -- `to` is not a ContractAddress. -- `to` is not the zero address. -- `from` is not the zero address. -- `from` must have an `id` balance of at least `value`. - -Constraints: - -- k=11, rows=1487 - -[.contract-item] -[[MultiTokenModule-_update]] -==== `[.contract-item-name]#++_update++#++(from: Either, to: Either, id: Uint<128>, value: Uint<128>)++` [.item-kind]#internal# - -Transfers a value amount of tokens of type id from from to to. -This circuit will mint (or burn) if `from` (or `to`) is the zero address. - -Requirements: - -- Contract is initialized. -- If `from` is not zero, the balance of `id` of `from` must be >= `value`. - -Constraints: - -- k=11, rows=1482 - -[.contract-item] -[[MultiTokenModule-_unsafeTransferFrom]] -==== `[.contract-item-name]#++_unsafeTransferFrom++#++(from: Either, to: Either, id: Uint<128>, value: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. -The caller must be `from` or approved to transfer on their behalf. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `to` is not the zero address. -- `from` is not the zero address. -- Caller must be `from` or approved via `setApprovalForAll`. -- `from` must have an `id` balance of at least `value`. - -Constraints: - -- k=11, rows=1881 - -[.contract-item] -[[MultiTokenModule-_unsafeTransfer]] -==== `[.contract-item-name]#++_unsafeTransfer++#++(from: Either, to: Either, id: Uint<128>, value: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. -Does not impose restrictions on the caller, making it suitable as a low-level building block for advanced contract logic. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `from` is not the zero address. -- `to` is not the zero address. -- `from` must have an `id` balance of at least `value`. - -Constraints: - -- k=11, rows=1486 - -[.contract-item] -[[MultiTokenModule-_setURI]] -==== `[.contract-item-name]#++_setURI++#++(newURI: Opaque<"string">) → []++` [.item-kind]#circuit# - -Sets a new URI for all token types, by relying on the token type ID substitution mechanism defined in the MultiToken standard. -See https://eips.ethereum.org/EIPS/eip-1155#metadata. - -By this mechanism, any occurrence of the `\{id\}` substring -in either the URI or any of the values in the JSON file at said URI will be replaced by clients with the token type ID. - -For example, the `https://token-cdn-domain/\{id\}.json` URI would be interpreted by clients as -`https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` for token type ID 0x4cce0. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=39 - -[.contract-item] -[[MultiTokenModule-_mint]] -==== `[.contract-item-name]#++_mint++#++(to: Either, id: Uint<128>, value: Uint<128>) → []++` [.item-kind]#circuit# - -Creates a `value` amount of tokens of type `token_id`, and assigns them to `to`. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- Contract is initialized. -- `to` is not the zero address. -- `to` is not a ContractAddress - -Constraints: - -- k=10, rows=912 - -[.contract-item] -[[MultiTokenModule-_unsafeMint]] -==== `[.contract-item-name]#++_unsafeMint++#++(to: Either, id: Uint<128>, value: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of `_mint` which allows transfers to contract addresses. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `to` is not the zero address. - -Constraints: - -- k=10, rows=911 - -[.contract-item] -[[MultiTokenModule-_burn]] -==== `[.contract-item-name]#++_burn++#++(from: Either, id: Uint<128>, value: Uint<128>) → []++` [.item-kind]#circuit# - -Destroys a `value` amount of tokens of type `token_id` from `from`. - -Requirements: - -- Contract is initialized. -- `from` is not the zero address. -- `from` must have an `id` balance of at least `value`. - -Constraints: - -- k=10, rows=688 - -[.contract-item] -[[MultiTokenModule-_setApprovalForAll]] -==== `[.contract-item-name]#++_setApprovalForAll++#++(owner: Either, operator: Either, approved: Boolean) → []++` [.item-kind]#circuit# - -Enables or disables approval for `operator` to manage all of the caller's assets. -This circuit does not check for access permissions but can be useful as a building block for more complex contract logic. - -Requirements: - -- Contract is initialized. -- `operator` is not the zero address. - -Constraints: - -- k=10, rows=518 diff --git a/docs/modules/ROOT/pages/api/nonFungibleToken.adoc b/docs/modules/ROOT/pages/api/nonFungibleToken.adoc deleted file mode 100644 index df015904..00000000 --- a/docs/modules/ROOT/pages/api/nonFungibleToken.adoc +++ /dev/null @@ -1,489 +0,0 @@ -:github-icon: pass:[] -:nonfungible-guide: xref:nonFungibleToken.adoc[NonFungibleToken guide] - -= NonFungibleToken - -This module provides the full NonFungibleToken module API. - -TIP: For an overview of the module, read the {nonfungible-guide}. - -== Core - -[.contract] -[[NonFungibleToken]] -=== `++NonFungibleToken++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/nonFungibleToken/src/NonFungibleToken.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/token/NonFungibleToken"; -``` - -[.contract-index] -.Circuits --- -[.sub-index#NonFungibleTokenModule] -* xref:#NonFungibleTokenModule-initialize[`++initialize(name_, symbol_)++`] -* xref:#NonFungibleTokenModule-balanceOf[`++balanceOf(owner)++`] -* xref:#NonFungibleTokenModule-ownerOf[`++ownerOf(tokenId)++`] -* xref:#NonFungibleTokenModule-name[`++name()++`] -* xref:#NonFungibleTokenModule-symbol[`++symbol()++`] -* xref:#NonFungibleTokenModule-tokenURI[`++tokenURI(tokenId)++`] -* xref:#NonFungibleTokenModule-_setTokenURI[`++_setTokenURI(tokenId, tokenURI)++`] -* xref:#NonFungibleTokenModule-approve[`++approve(to, tokenId)++`] -* xref:#NonFungibleTokenModule-getApproved[`++getApproved(tokenId)++`] -* xref:#NonFungibleTokenModule-setApprovalForAll[`++setApprovalForAll(operator, approved)++`] -* xref:#NonFungibleTokenModule-isApprovedForAll[`++isApprovedForAll(owner, operator)++`] -* xref:#NonFungibleTokenModule-transferFrom[`++transferFrom(from, to, tokenId)++`] -* xref:#NonFungibleTokenModule-_unsafeTransferFrom[`++_unsafeTransferFrom(from, to, tokenId)++`] -* xref:#NonFungibleTokenModule-_ownerOf[`++_ownerOf(tokenId)++`] -* xref:#NonFungibleTokenModule-_getApproved[`++_getApproved(tokenId)++`] -* xref:#NonFungibleTokenModule-_isAuthorized[`++_isAuthorized(owner, spender, tokenId)++`] -* xref:#NonFungibleTokenModule-_checkAuthorized[`++_checkAuthorized(owner, spender, tokenId)++`] -* xref:#NonFungibleTokenModule-_update[`++_update(to, tokenId, auth)++`] -* xref:#NonFungibleTokenModule-_mint[`++_mint(to, tokenId)++`] -* xref:#NonFungibleTokenModule-_unsafeMint[`++_unsafeMint(to, tokenId)++`] -* xref:#NonFungibleTokenModule-_burn[`++_burn(tokenId)++`] -* xref:#NonFungibleTokenModule-_transfer[`++_transfer(from, to, tokenId)++`] -* xref:#NonFungibleTokenModule-_unsafeTransfer[`++_unsafeTransfer(from, to, tokenId)++`] -* xref:#NonFungibleTokenModule-_approve[`++_approve(to, tokenId, auth)++`] -* xref:#NonFungibleTokenModule-_setApprovalForAll[`++_setApprovalForAll(owner, operator, approved)++`] -* xref:#NonFungibleTokenModule-_requireOwned[`++_requireOwned(tokenId)++`] --- - -[.contract-item] -[[NonFungibleTokenModule-initialize]] -==== `[.contract-item-name]#++initialize++#++(name_: Opaque<"string">, symbol_: Opaque<"string">) → []++` [.item-kind]#circuit# - -Initializes the contract by setting the name and symbol. - -This MUST be called in the implementing contract's constructor. -Failure to do so can lead to an irreparable contract. - -Requirements: - -- Contract is not initialized. - -Constraints: - -- k=10, rows=65 - -[.contract-item] -[[NonFungibleTokenModule-balanceOf]] -==== `[.contract-item-name]#++balanceOf++#++(owner: Either) → Uint<128>++` [.item-kind]#circuit# - -Returns the number of tokens in ``owner``'s account. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=309 - -[.contract-item] -[[NonFungibleTokenModule-ownerOf]] -==== `[.contract-item-name]#++ownerOf++#++(tokenId: Uint<128>) → Either++` [.item-kind]#circuit# - -Returns the owner of the `tokenId` token. - -Requirements: - -- The contract is initialized. -- The `tokenId` must exist. - -Constraints: - -- k=10, rows=290 - -[.contract-item] -[[NonFungibleTokenModule-name]] -==== `[.contract-item-name]#++name++#++() → Opaque<"string">++` [.item-kind]#circuit# - -Returns the token name. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=36 - -[.contract-item] -[[NonFungibleTokenModule-symbol]] -==== `[.contract-item-name]#++symbol++#++() → Opaque<"string">++` [.item-kind]#circuit# - -Returns the symbol of the token. - -Requirements: - -- Contract is initialized. - -Constraints: - -- k=10, rows=36 - -[.contract-item] -[[NonFungibleTokenModule-tokenURI]] -==== `[.contract-item-name]#++tokenURI++#++(tokenId: Uint<128>) → Opaque<"string">++` [.item-kind]#circuit# - -Returns the token URI for the given `tokenId`. -Returns an empty string if a tokenURI does not exist. - -Requirements: - -- The contract is initialized. -- The `tokenId` must exist. - -NOTE: Native strings and string operations aren't supported within the Compact language, e.g. concatenating a base URI + token ID is not possible like in other NFT implementations. -Therefore, we propose the URI storage approach; whereby, NFTs may or may not have unique "base" URIs. -It's up to the implementation to decide on how to handle this. - -Constraints: - -- k=10, rows=296 - -[.contract-item] -[[NonFungibleTokenModule-_setTokenURI]] -==== `[.contract-item-name]#++_setTokenURI++#++(tokenId: Uint<128>, tokenURI: Opaque<"string">) → []++` [.item-kind]#circuit# - -Sets the the URI as `tokenURI` for the given `tokenId`. - -Requirements: - -- The contract is initialized. -- The `tokenId` must exist. - -NOTE: The URI for a given NFT is usually set when the NFT is minted. - -Constraints: - -- k=10, rows=253 - -[.contract-item] -[[NonFungibleTokenModule-approve]] -==== `[.contract-item-name]#++approve++#++(to: Either, tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Gives permission to `to` to transfer `tokenId` token to another account. -The approval is cleared when the token is transferred. - -Only a single account can be approved at a time, so approving the zero address clears previous approvals. - - -Requirements: - -- The contract is initialized. -- The caller must either own the token or be an approved operator. -- `tokenId` must exist. - -Constraints: - -- k=10, rows=966 - -[.contract-item] -[[NonFungibleTokenModule-getApproved]] -==== `[.contract-item-name]#++getApproved++#++(tokenId: Uint<128>) → Either++` [.item-kind]#circuit# - -Returns the account approved for `tokenId` token. - -Requirements: - -- The contract is initialized. -- `tokenId` must exist. - -Constraints: - -- k=10, rows=409 - -[.contract-item] -[[NonFungibleTokenModule-setApprovalForAll]] -==== `[.contract-item-name]#++setApprovalForAll++#++(operator: Either, approved: Boolean) → []++` [.item-kind]#circuit# - -Approve or remove `operator` as an operator for the caller. -Operators can call <> for any token owned by the caller. - -Requirements: - -- The contract is initialized. -- The `operator` cannot be the zero address. - -Constraints: - -- k=10, rows=409 - -[.contract-item] -[[NonFungibleTokenModule-isApprovedForAll]] -==== `[.contract-item-name]#++isApprovedForAll++#++(owner: Either, operator: Either) → Boolean++` [.item-kind]#circuit# - -Returns if the `operator` is allowed to manage all of the assets of `owner`. - -Requirements: - -- The contract must have been initialized. - -Constraints: - -- k=10, rows=621 - -[.contract-item] -[[NonFungibleTokenModule-transferFrom]] -==== `[.contract-item-name]#++transferFrom++#++(from: Either, to: Either, tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Transfers `tokenId` token from `from` to `to`. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- The contract is initialized. -- `from` is not the zero address. -- `to` is not the zero address. -- `to` is not a ContractAddress. -- `tokenId` token must be owned by `from`. -- If the caller is not `from`, it must be approved to move this token by either <> or <>. - -Constraints: - -- k=11, rows=1966 - -[.contract-item] -[[NonFungibleTokenModule-_unsafeTransferFrom]] -==== `[.contract-item-name]#++_unsafeTransferFrom++#++(from: Either, to: Either, tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- The contract is initialized. -- `from` is not the zero address. -- `to` is not the zero address. -- `tokenId` token must be owned by `from`. -- If the caller is not `from`, it must be approved to move this token by either <> or <>. - -Constraints: - -- k=11, rows=1963 - -[.contract-item] -[[NonFungibleTokenModule-_ownerOf]] -==== `[.contract-item-name]#++_ownerOf++#++(tokenId: Uint<128>) → Either++` [.item-kind]#circuit# - -Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist - -Requirements: - -- The contract is initialized. - -Constraints: - -- k=10, rows=253 - -[.contract-item] -[[NonFungibleTokenModule-_getApproved]] -==== `[.contract-item-name]#++_getApproved++#++(tokenId: Uint<128>) → Either++` [.item-kind]#circuit# - -Returns the approved address for `tokenId`. Returns the zero address if `tokenId` is not minted. - -Requirements: - -- The contract is initialized. - -Constraints: - -- k=10, rows=253 - -[.contract-item] -[[NonFungibleTokenModule-_isAuthorized]] -==== `[.contract-item-name]#++_isAuthorized++#++(owner: Either, spender: Either, tokenId: Uint<128> ) → Boolean++` [.item-kind]#circuit# - -Returns whether `spender` is allowed to manage ``owner``'s tokens, or `tokenId` in particular (ignoring whether it is owned by `owner`). - -Requirements: - -- The contract is initialized. - -WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this assumption. - -Constraints: - -- k=11, rows=1098 - -[.contract-item] -[[NonFungibleTokenModule-_checkAuthorized]] -==== `[.contract-item-name]#++_checkAuthorized++#++(owner: Either, spender: Either, tokenId: Uint<128> ) → []++` [.item-kind]#circuit# - -Checks if `spender` can operate on `tokenId`, assuming the provided `owner` is the actual owner. - -Requirements: - -- The contract is initialized. -- `spender` has approval from `owner` for `tokenId` OR `spender` has approval to manage all of `owner`'s assets. - -WARNING: This function assumes that `owner` is the actual owner of `tokenId` and does not verify this assumption. - -Constraints: - -- k=11, rows=1121 - -[.contract-item] -[[NonFungibleTokenModule-_update]] -==== `[.contract-item-name]#++_update++#++(to: Either, tokenId: Uint<128>, auth: Either) → Either++` [.item-kind]#internal# - -Transfers `tokenId` from its current owner to `to`, or alternatively mints (or burns) if the current owner (or `to`) is the zero address. -Returns the owner of the `tokenId` before the update. - -Requirements: - -- The contract is initialized. -- If `auth` is non 0, then this function will check that `auth` is either the owner of the token, or approved to operate on the token (by the owner). - -Constraints: - -- k=12, rows=2049 - -[.contract-item] -[[NonFungibleTokenModule-_mint]] -==== `[.contract-item-name]#++_mint++#++(to: Either, tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Mints `tokenId` and transfers it to `to`. - -Requirements: - -- The contract is initialized. -- `tokenId` must not exist. -- `to` is not the zero address. -- `to` is not a ContractAddress. - -Constraints: - -- k=10, rows=1013 - -[.contract-item] -[[NonFungibleTokenModule-_unsafeMint]] -==== `[.contract-item-name]#++_unsafeMint++#++(account: Either, value: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -Requirements: - -- Contract is initialized. -- `tokenId` must not exist. -- `to` is not the zero address. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. -Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Constraints: - -- k=10, rows=1010 - -[.contract-item] -[[NonFungibleTokenModule-_burn]] -==== `[.contract-item-name]#++_burn++#++(tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Destroys `tokenId`. -The approval is cleared when the token is burned. -This circuit does not check if the sender is authorized to operate on the token. - -Requirements: - -- The contract is initialized. -- `tokenId` must exist. - -Constraints: - -- k=10, rows=479 - -[.contract-item] -[[NonFungibleTokenModule-_transfer]] -==== `[.contract-item-name]#++_transfer++#++(from: Either, to: Either, tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Transfers `tokenId` from `from` to `to`. As opposed to <>, this imposes no restrictions on `ownPublicKey()`. - -NOTE: Transfers to contract addresses are currently disallowed until contract-to-contract interactions are supported in Compact. -This restriction prevents assets from being inadvertently locked in contracts that cannot currently handle token receipt. - -Requirements: - -- The contract is initialized. -- `to` is not the zero address. -- `to` is not a ContractAddress. -- `tokenId` token must be owned by `from`. - -Constraints: - -- k=11, rows=1224 - -[.contract-item] -[[NonFungibleTokenModule-_unsafeTransfer]] -==== `[.contract-item-name]#++_unsafeTransfer++#++(from: Either, to: Either, tokenId: Uint<128>) → []++` [.item-kind]#circuit# - -Unsafe variant of <> which allows transfers to contract addresses. - -Transfers `tokenId` from `from` to `to`. As opposed to <>, this imposes no restrictions on `ownPublicKey()`. It does NOT check if the recipient is a `ContractAddress`. - -WARNING: Transfers to contract addresses are considered unsafe because contract-to-contract calls are not currently supported. Tokens sent to a contract address may become irretrievable. -Once contract-to-contract calls are supported, this circuit may be deprecated. - -Requirements: - -- Contract is initialized. -- `to` is not the zero address. -- `tokenId` token must be owned by `from`. - -Constraints: - -- k=11, rows=1221 - -[.contract-item] -[[NonFungibleTokenModule-_approve]] -==== `[.contract-item-name]#++_approve++#++(to: Either, tokenId: Uint<128>, auth: Either) → []++` [.item-kind]#circuit# - -Approve `to` to operate on `tokenId` - -Requirements: - -- The contract is initialized. -- If `auth` is non 0, then this function will check that `auth` is either the owner of the token, or approved to operate on the token (by the owner). - -Constraints: - -- k=11, rows=1109 - -[.contract-item] -[[NonFungibleTokenModule-_setApprovalForAll]] -==== `[.contract-item-name]#++_setApprovalForAll++#++(owner: Either, operator: Either, approved: Boolean) → []++` [.item-kind]#circuit# - -Approve `operator` to operate on all of `owner` tokens - -Requirements: - -- The contract is initialized. -- `operator` is not the zero address. - -Constraints: - -- k=10, rows=524 - -[.contract-item] -[[NonFungibleTokenModule-_requireOwned]] -==== `[.contract-item-name]#++_requireOwned++#++(tokenId: Uint<128>) → Either++` [.item-kind]#circuit# - -Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). -Returns the owner. - -Requirements: - -- The contract is initialized. -- `tokenId` must exist. - -Constraints: - -- k=10, rows=288 diff --git a/docs/modules/ROOT/pages/api/security.adoc b/docs/modules/ROOT/pages/api/security.adoc deleted file mode 100644 index 3d4e0e18..00000000 --- a/docs/modules/ROOT/pages/api/security.adoc +++ /dev/null @@ -1,161 +0,0 @@ -:github-icon: pass:[] -:security-guide: xref:security.adoc[Security guide] - -= Security - -This directory provides the API for all Security modules. - -TIP: For an overview of the module, read the {security-guide}. - -== Initializable - -[.contract] -[[Initializable]] -=== `++Initializable++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/utils/src/Initializable.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/security/Initializable"; -``` - -[.contract-index] -.Circuits --- - -[.sub-index#InitializableModule] -* xref:#InitializableModule-initialize[`++initialize()++`] -* xref:#InitializableModule-assertInitialized[`++assertInitialized()++`] -* xref:#InitializableModule-assertNotInitialized[`++assertNotInitialized()++`] --- - -[.contract-item] -[[InitializableModule-initialize]] -==== `[.contract-item-name]#++initialize++#++() → []++` [.item-kind]#circuit# - -Initializes the state thus ensuring the calling circuit can only be called once. - -Requirements: - -- Contract must not be initialized. - -Constraints: - -- k=10, rows=38 - -[.contract-item] -[[InitializableModule-assertInitialized]] -==== `[.contract-item-name]#++assertInitialized++#++() → []++` [.item-kind]#circuit# - -Asserts that the contract has been initialized, throwing an error if not. - -Requirements: - -- Contract must be initialized. - -Constraints: - -- k=10, rows=31 - -[.contract-item] -[[InitializableModule-assertNotInitialized]] -==== `[.contract-item-name]#++assertNotInitialized++#++() → []++` [.item-kind]#circuit# - -Asserts that the contract has not been initialized, throwing an error if it has. - -Requirements: - -- Contract must not be initialized. - -Constraints: - -- k=10, rows=35 - -== Pausable - -[.contract] -[[Pausable]] -=== `++Pausable++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/utils/src/Pausable.compact[{github-icon},role=heading-link] - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/security/Pausable"; - -``` - -[.contract-index] -.Circuits --- - -[.sub-index#PausableModule] -* xref:#PausableModule-isPaused[`++isPaused()++`] -* xref:#PausableModule-assertPaused[`++assertPaused()++`] -* xref:#PausableModule-assertNotPaused[`++assertNotPaused()++`] -* xref:#PausableModule-_pause[`++_pause()++`] -* xref:#PausableModule-_unpause[`++_unpause()++`] --- - -[.contract-item] -[[PausableModule-isPaused]] -==== `[.contract-item-name]#++isPaused++#++() → Boolean++` [.item-kind]#circuit# - -Returns true if the contract is paused, and false otherwise. - -Constraints: - -- k=10, rows=32 - -[.contract-item] -[[PausableModule-assertPaused]] -==== `[.contract-item-name]#++assertPaused++#++() → []++` [.item-kind]#circuit# - -Makes a circuit only callable when the contract is paused. - -Requirements: - -- Contract must be paused. - -Constraints: - -- k=10, rows=31 - -[.contract-item] -[[PausableModule-assertNotPaused]] -==== `[.contract-item-name]#++assertNotPaused++#++() → []++` [.item-kind]#circuit# - -Makes a circuit only callable when the contract is not paused. - -Requirements: - -- Contract must not be paused. - -Constraints: - -- k=10, rows=35 - -[.contract-item] -[[PausableModule-_pause]] -==== `[.contract-item-name]#++_pause++#++() → []++` [.item-kind]#circuit# - -Triggers a stopped state. - -Requirements: - -- Contract must not be paused. - -Constraints: - -- k=10, rows=38 - -[.contract-item] -[[PausableModule-_unpause]] -==== `[.contract-item-name]#++_unpause++#++() → []++` [.item-kind]#circuit# - -Lifts the pause on the contract. - -Requirements: - -- Contract must be paused. - -Constraints: - -- k=10, rows=34 diff --git a/docs/modules/ROOT/pages/api/utils.adoc b/docs/modules/ROOT/pages/api/utils.adoc deleted file mode 100644 index 5e123aee..00000000 --- a/docs/modules/ROOT/pages/api/utils.adoc +++ /dev/null @@ -1,67 +0,0 @@ -:github-icon: pass:[] -:utils-guide: xref:utils.adoc[Utils guide] - -= Utils - -This directory provides the API for all Utils modules. - -TIP: For an overview of the module, read the {utils-guide}. - -== Utils - -[.hljs-theme-dark] -```ts -import "./node_modules/@openzeppelin-compact/contracts/src/utils/Utils" prefix Utils_; -``` - -[.contract] -[[Utils]] -=== `++Utils++` link:https://github.com/OpenZeppelin/compact-contracts/blob/main/contracts/utils/src/Utils.compact[{github-icon},role=heading-link] - -NOTE: There's no easy way to get the constraints of pure circuits at this time so the constraints of the circuits listed below have been omitted. - -[.contract-index] -.Circuits --- - -[.sub-index#UtilsModule] -* xref:#UtilsModule-isKeyOrAddressZero[`++isKeyOrAddressZero(keyOrAddress)++`] -* xref:#UtilsModule-isKeyZero[`++isKeyZero(key)++`] -* xref:#UtilsModule-isKeyOrAddressEqual[`++isKeyOrAddressEqual(keyOrAddress, other)++`] -* xref:#UtilsModule-isContractAddress[`++isContractAddress(keyOrAddress)++`] -* xref:#UtilsModule-emptyString[`++emptyString()++`] --- - -[.contract-item] -[[UtilsModule-isKeyOrAddressZero]] -==== `[.contract-item-name]#++isKeyOrAddressZero++#++(keyOrAddress: Either) → Boolean++` [.item-kind]#circuit# - -Returns whether `keyOrAddress` is the zero address. - -NOTE: Midnight's burn address is represented as `left(default)` in Compact, -so we've chosen to represent the zero address as this structure as well - -[.contract-item] -[[UtilsModule-isKeyZero]] -==== `[.contract-item-name]#++isKeyZero++#++(key: ZswapCoinPublicKey) → Boolean++` [.item-kind]#circuit# - -Returns whether `key` is the zero address. - -[.contract-item] -[[UtilsModule-isKeyOrAddressEqual]] -==== `[.contract-item-name]#++isKeyOrAddressEqual++#++(keyOrAddress: Either, other: Either) → Boolean++` [.item-kind]#circuit# - -Returns whether `keyOrAddress` is equal to `other`. -Assumes that a `ZswapCoinPublicKey` and a `ContractAddress` can never be equal - -[.contract-item] -[[UtilsModule-isContractAddress]] -==== `[.contract-item-name]#++isContractAddress++#++(keyOrAddress: Either) → Boolean++` [.item-kind]#circuit# - -Returns whether `keyOrAddress` is a `ContractAddress` type. - -[.contract-item] -[[UtilsModule-emptyString]] -==== `[.contract-item-name]#++emptyString++#++() → Opaque<"string">++` [.item-kind]#circuit# - -A helper function that returns the empty string: "". diff --git a/docs/netlify.toml b/docs/netlify.toml new file mode 100644 index 00000000..92156156 --- /dev/null +++ b/docs/netlify.toml @@ -0,0 +1,298 @@ +[build] +command = "pnpm run build" +publish = ".next" + +[[plugins]] +package = "@netlify/plugin-nextjs" + +# OpenZeppelin Contracts + +[[redirects]] + from = "/contracts/3.x/*" + to = "/contracts/3.x/:splat" + status = 200 + +[[redirects]] + from = "/contracts/4.x/*" + to = "/contracts/4.x/:splat" + status = 200 + +[[redirects]] + from = "/contracts/5.x/*" + to = "/contracts/5.x/:splat" + status = 200 + +[[redirects]] + from = "/contracts/*" + to = "/contracts/5.x/:splat" + status = 301 + +# Community Contracts + +[[redirects]] +from = "/community-contracts/0.0.1/*" +to = "/community-contracts/:splat" +status = 301 + +# Stellar Contracts + +[[redirects]] +from = "/stellar-contracts/0.4.0/*" +to = "/stellar-contracts/:splat" +status = 301 + +[[redirects]] +from = "/stellar-contracts/0.3.0/*" +to = "/stellar-contracts/:splat" +status = 301 + +[[redirects]] +from = "/stellar-contracts/0.2.0/*" +to = "/stellar-contracts/:splat" +status = 301 + +[[redirects]] +from = "/stellar-contracts/0.1.0/*" +to = "/stellar-contracts/:splat" +status = 301 + +# Contracts Cairo + +[[redirects]] +from = "/contracts-cairo/3.0.0-alpha.1/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/3.0.0-alpha.1/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/2.0.0-alpha.1/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/2.0.0-alpha.1/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.20.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.20.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.19.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.19.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.18.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.18.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.17.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.17.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.16.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.16.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.15.1/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.15.1/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.15.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.15.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.14.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.14.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.13.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.13.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.12.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.12.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.11.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.11.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/0.10.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/0.10.0/:splat" +status = 301 + +[[redirects]] +from = "/contracts-cairo/1.0.0/*" +to = "https://old-docs.openzeppelin.com/contracts-cairo/1.0.0/:splat" +status = 301 + +# Midnight Compact + +[[redirects]] +from = "/contracts-compact/0.0.1/*" +to = "/contracts-compact/:splat" +status = 301 + +# Stylus + +[[redirects]] +from = "/contracts-stylus/0.3.0/*" +to = "/contracts-stylus/:splat" +status = 301 + +# Polkadot Substrate Runtimes + +[[redirects]] +from = "/substrate-runtimes/3.0.0-rc/*" +to = "/substrate-runtimes/:splat" +status = 301 + +[[redirects]] +from = "/substrate-runtimes/1.0.0/*" +to = "/substrate-runtimes/:splat" +status = 301 + + +# Uniswap Hooks + +[[redirects]] +from = "/uniswap-hooks/1.x/*" +to = "/uniswap-hooks/:splat" +status = 301 + +# Zama Confidential contracts + +[[redirects]] +from = "/confidential-contracts/0.2/*" +to = "/confidential-contracts/:splat" +status = 301 + +# Tools redirects + +[[redirects]] +from = "/open-source-tools" +to = "/#open-source-tools" +status = 301 + +[[redirects]] +from = "/subgraphs/0.1.x/*" +to = "/contracts/5.x/subgraphs/:splat" +status = 301 + +[[redirects]] +from = "/monitor/1.0.x/*" +to = "/monitor/:splat" +status = 301 + +[[redirects]] +from = "/monitor/0.2.x/*" +to = "/monitor/:splat" +status = 301 + +[[redirects]] +from = "/monitor/0.1.0/*" +to = "/monitor/:splat" +status = 301 + +[[redirects]] +from = "/relayer/1.1.x/*" +to = "/relayer/:splat" +status = 301 + +[[redirects]] +from = "/relayer/1.0.x/*" +to = "/relayer/:splat" +status = 301 + +[[redirects]] +from = "/relayer/0.2.x/*" +to = "/relayer/:splat" +status = 301 + +[[redirects]] +from = "/relayer/0.1.0/*" +to = "/relayer/:splat" +status = 301 + +[[redirects]] +from = "/relayer/0.1.x/*" +to = "/relayer/:splat" +status = 301 + +[[redirects]] +from = "/contracts-ui-builder/1.0.x/*" +to = "/ui-builder/:splat" +status = 301 + +[[redirects]] +from = "/contracts-ui-builder/*" +to = "/ui-builder/:splat" +status = 301 + +[[redirects]] +from = "/ui-builder/1.0.x/*" +to = "/ui-builder/:splat" +status = 301 + +[[redirects]] +from = "/learn/*" +to = "/contracts/5.x/learn/:splat" +status = 301 + +# Deprecated projects redirects +[[redirects]] +from = "/cli/*" +to = "/" +status = 301 + +[[redirects]] +from = "/network-js/*" +to = "/" +status = 301 + +[[redirects]] +from = "/nile/*" +to = "/" +status = 301 + +[[redirects]] +from = "/test-environment/*" +to = "/" +status = 301 + +[[redirects]] +from = "/test-helpers/*" +to = "/" +status = 301 + +[[redirects]] +from = "/gsn-helpers/*" +to = "/" +status = 301 + +[[redirects]] +from = "/gsn-provider/*" +to = "/" +status = 301 + +[[redirects]] +from = "/starter-kits/*" +to = "/" +status = 301 + +[[redirects]] +from = "/sdk/*" +to = "/" +status = 301 + +[[redirects]] +from = "/openzeppelin/*" +to = "/:splat" +status = 301 diff --git a/docs/next.config.mjs b/docs/next.config.mjs new file mode 100644 index 00000000..aa49369c --- /dev/null +++ b/docs/next.config.mjs @@ -0,0 +1,12 @@ +import { createMDX } from "fumadocs-mdx/next"; + +const withMDX = createMDX(); + +/** @type {import('next').NextConfig} */ +const config = { + reactStrictMode: true, + output: "export", + images: { unoptimized: true }, +}; + +export default withMDX(config); diff --git a/docs/package.json b/docs/package.json index 02410b35..344a8419 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,15 +1,73 @@ { - "name": "docs", - "version": "0.0.1", - "scripts": { - "docs": "oz-docs -c .", - "docs:watch": "npm run docs watch", - "prepare-docs": "" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@openzeppelin/docs-utils": "^0.1.5" - } + "name": "docs", + "version": "0.0.0", + "private": true, + "type": "module", + "scripts": { + "build": "next build --turbo", + "dev": "next dev --turbo", + "start": "next start", + "postinstall": "fumadocs-mdx", + "lint": "biome lint src/", + "lint:links": "tsx scripts/link-validation.ts", + "lint:links-save": "tsx scripts/link-validation.ts --output broken-links.md", + "lint:fix": "biome lint --write src/", + "format": "biome format src/", + "format:fix": "biome format --write src/", + "check": "biome check src/ && pnpm run lint:links", + "check:fix": "biome check --write src/" + }, + "dependencies": { + "@fumadocs/mdx-remote": "^1.4.0", + "@netlify/plugin-nextjs": "^5.13.3", + "@next/third-parties": "^15.5.4", + "@orama/orama": "^3.1.13", + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-presence": "^1.1.5", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-separator": "^1.1.7", + "@tanstack/react-query": "^5.89.0", + "algoliasearch": "^5.37.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "fumadocs-core": "15.7.11", + "fumadocs-mdx": "12.0.1", + "fumadocs-openapi": "^9.3.8", + "fumadocs-ui": "15.7.11", + "glob": "^11.0.3", + "katex": "^0.16.22", + "lucide-react": "^0.540.0", + "mermaid": "^11.11.0", + "micromatch": "^4.0.8", + "next": "^15.5.3", + "next-themes": "^0.4.6", + "next-validate-link": "^1.6.3", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "rehype-katex": "^7.0.1", + "remark": "^15.0.1", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "remark-mdx": "^3.1.1", + "shiki": "^3.12.2", + "tailwind-merge": "^3.3.1", + "unist-util-visit": "^5.0.0" + }, + "devDependencies": { + "@biomejs/biome": "^2.2.4", + "@tailwindcss/postcss": "^4.1.13", + "@tanstack/react-query-devtools": "^5.89.0", + "@types/mdx": "^2.0.13", + "@types/micromatch": "^4.0.9", + "@types/node": "24.1.0", + "@types/react": "^19.1.12", + "@types/react-dom": "^19.1.9", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.13", + "tsx": "^4.20.5", + "tw-animate-css": "^1.3.8", + "typescript": "^5.9.2" + }, + "packageManager": "pnpm@10.17.1" } diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml new file mode 100644 index 00000000..81726364 --- /dev/null +++ b/docs/pnpm-lock.yaml @@ -0,0 +1,6399 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@fumadocs/mdx-remote': + specifier: ^1.4.0 + version: 1.4.0(@types/react@19.1.12)(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + '@netlify/plugin-nextjs': + specifier: ^5.13.3 + version: 5.13.3 + '@next/third-parties': + specifier: ^15.5.4 + version: 15.5.4(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + '@orama/orama': + specifier: ^3.1.13 + version: 3.1.13 + '@radix-ui/react-collapsible': + specifier: ^1.1.12 + version: 1.1.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-popover': + specifier: ^1.1.15 + version: 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': + specifier: ^1.1.5 + version: 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-scroll-area': + specifier: ^1.2.10 + version: 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@tanstack/react-query': + specifier: ^5.89.0 + version: 5.89.0(react@19.1.1) + algoliasearch: + specifier: ^5.37.0 + version: 5.37.0 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + fumadocs-core: + specifier: 15.7.11 + version: 15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + fumadocs-mdx: + specifier: 12.0.1 + version: 12.0.1(@fumadocs/mdx-remote@1.4.0(@types/react@19.1.12)(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1))(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + fumadocs-openapi: + specifier: ^9.3.8 + version: 9.3.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13)(typescript@5.9.2) + fumadocs-ui: + specifier: 15.7.11 + version: 15.7.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13) + glob: + specifier: ^11.0.3 + version: 11.0.3 + katex: + specifier: ^0.16.22 + version: 0.16.22 + lucide-react: + specifier: ^0.540.0 + version: 0.540.0(react@19.1.1) + mermaid: + specifier: ^11.11.0 + version: 11.11.0 + micromatch: + specifier: ^4.0.8 + version: 4.0.8 + next: + specifier: ^15.5.3 + version: 15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next-validate-link: + specifier: ^1.6.3 + version: 1.6.3 + react: + specifier: ^19.1.1 + version: 19.1.1 + react-dom: + specifier: ^19.1.1 + version: 19.1.1(react@19.1.1) + rehype-katex: + specifier: ^7.0.1 + version: 7.0.1 + remark: + specifier: ^15.0.1 + version: 15.0.1 + remark-gfm: + specifier: ^4.0.1 + version: 4.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 + remark-mdx: + specifier: ^3.1.1 + version: 3.1.1 + shiki: + specifier: ^3.12.2 + version: 3.12.2 + tailwind-merge: + specifier: ^3.3.1 + version: 3.3.1 + unist-util-visit: + specifier: ^5.0.0 + version: 5.0.0 + devDependencies: + '@biomejs/biome': + specifier: ^2.2.4 + version: 2.2.4 + '@tailwindcss/postcss': + specifier: ^4.1.13 + version: 4.1.13 + '@tanstack/react-query-devtools': + specifier: ^5.89.0 + version: 5.89.0(@tanstack/react-query@5.89.0(react@19.1.1))(react@19.1.1) + '@types/mdx': + specifier: ^2.0.13 + version: 2.0.13 + '@types/micromatch': + specifier: ^4.0.9 + version: 4.0.9 + '@types/node': + specifier: 24.1.0 + version: 24.1.0 + '@types/react': + specifier: ^19.1.12 + version: 19.1.12 + '@types/react-dom': + specifier: ^19.1.9 + version: 19.1.9(@types/react@19.1.12) + postcss: + specifier: ^8.5.6 + version: 8.5.6 + tailwindcss: + specifier: ^4.1.13 + version: 4.1.13 + tsx: + specifier: ^4.20.5 + version: 4.20.5 + tw-animate-css: + specifier: ^1.3.8 + version: 1.3.8 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + +packages: + + '@algolia/abtesting@1.3.0': + resolution: {integrity: sha512-KqPVLdVNfoJzX5BKNGM9bsW8saHeyax8kmPFXul5gejrSPN3qss7PgsFH5mMem7oR8tvjvNkia97ljEYPYCN8Q==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-abtesting@5.37.0': + resolution: {integrity: sha512-Dp2Zq+x9qQFnuiQhVe91EeaaPxWBhzwQ6QnznZQnH9C1/ei3dvtmAFfFeaTxM6FzfJXDLvVnaQagTYFTQz3R5g==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-analytics@5.37.0': + resolution: {integrity: sha512-wyXODDOluKogTuZxRII6mtqhAq4+qUR3zIUJEKTiHLe8HMZFxfUEI4NO2qSu04noXZHbv/sRVdQQqzKh12SZuQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-common@5.37.0': + resolution: {integrity: sha512-GylIFlPvLy9OMgFG8JkonIagv3zF+Dx3H401Uo2KpmfMVBBJiGfAb9oYfXtplpRMZnZPxF5FnkWaI/NpVJMC+g==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-insights@5.37.0': + resolution: {integrity: sha512-T63afO2O69XHKw2+F7mfRoIbmXWGzgpZxgOFAdP3fR4laid7pWBt20P4eJ+Zn23wXS5kC9P2K7Bo3+rVjqnYiw==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-personalization@5.37.0': + resolution: {integrity: sha512-1zOIXM98O9zD8bYDCJiUJRC/qNUydGHK/zRK+WbLXrW1SqLFRXECsKZa5KoG166+o5q5upk96qguOtE8FTXDWQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-query-suggestions@5.37.0': + resolution: {integrity: sha512-31Nr2xOLBCYVal+OMZn1rp1H4lPs1914Tfr3a34wU/nsWJ+TB3vWjfkUUuuYhWoWBEArwuRzt3YNLn0F/KRVkg==} + engines: {node: '>= 14.0.0'} + + '@algolia/client-search@5.37.0': + resolution: {integrity: sha512-DAFVUvEg+u7jUs6BZiVz9zdaUebYULPiQ4LM2R4n8Nujzyj7BZzGr2DCd85ip4p/cx7nAZWKM8pLcGtkTRTdsg==} + engines: {node: '>= 14.0.0'} + + '@algolia/ingestion@1.37.0': + resolution: {integrity: sha512-pkCepBRRdcdd7dTLbFddnu886NyyxmhgqiRcHHaDunvX03Ij4WzvouWrQq7B7iYBjkMQrLS8wQqSP0REfA4W8g==} + engines: {node: '>= 14.0.0'} + + '@algolia/monitoring@1.37.0': + resolution: {integrity: sha512-fNw7pVdyZAAQQCJf1cc/ih4fwrRdQSgKwgor4gchsI/Q/ss9inmC6bl/69jvoRSzgZS9BX4elwHKdo0EfTli3w==} + engines: {node: '>= 14.0.0'} + + '@algolia/recommend@5.37.0': + resolution: {integrity: sha512-U+FL5gzN2ldx3TYfQO5OAta2TBuIdabEdFwD5UVfWPsZE5nvOKkc/6BBqP54Z/adW/34c5ZrvvZhlhNTZujJXQ==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-browser-xhr@5.37.0': + resolution: {integrity: sha512-Ao8GZo8WgWFABrU7iq+JAftXV0t+UcOtCDL4mzHHZ+rQeTTf1TZssr4d0vIuoqkVNnKt9iyZ7T4lQff4ydcTrw==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-fetch@5.37.0': + resolution: {integrity: sha512-H7OJOXrFg5dLcGJ22uxx8eiFId0aB9b0UBhoOi4SMSuDBe6vjJJ/LeZyY25zPaSvkXNBN3vAM+ad6M0h6ha3AA==} + engines: {node: '>= 14.0.0'} + + '@algolia/requester-node-http@5.37.0': + resolution: {integrity: sha512-npZ9aeag4SGTx677eqPL3rkSPlQrnzx/8wNrl1P7GpWq9w/eTmRbOq+wKrJ2r78idlY0MMgmY/mld2tq6dc44g==} + engines: {node: '>= 14.0.0'} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + + '@antfu/utils@9.2.0': + resolution: {integrity: sha512-Oq1d9BGZakE/FyoEtcNeSwM7MpDO2vUBi11RWBZXf75zPsbUVWmUs03EqkRFrcgbXyKTas0BdZWC1wcuSoqSAw==} + + '@apidevtools/json-schema-ref-parser@11.9.3': + resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} + engines: {node: '>= 16'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.2.4': + resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.2.4': + resolution: {integrity: sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.2.4': + resolution: {integrity: sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.2.4': + resolution: {integrity: sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.2.4': + resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.2.4': + resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.2.4': + resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.2.4': + resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.2.4': + resolution: {integrity: sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@braintree/sanitize-url@7.1.1': + resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + + '@emnapi/runtime@1.5.0': + resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + + '@esbuild/aix-ppc64@0.25.10': + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.10': + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.10': + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.10': + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.10': + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.10': + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.10': + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.10': + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.10': + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.10': + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.10': + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.10': + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.10': + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.10': + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.10': + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.10': + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.10': + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.10': + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.10': + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.10': + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.10': + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.10': + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.10': + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.10': + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.10': + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.10': + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@floating-ui/core@1.7.3': + resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} + + '@floating-ui/dom@1.7.4': + resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} + + '@floating-ui/react-dom@2.1.6': + resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@formatjs/intl-localematcher@0.6.1': + resolution: {integrity: sha512-ePEgLgVCqi2BBFnTMWPfIghu6FkbZnnBVhO2sSxvLfrdFw7wCHAHiDoM2h4NRgjbaY7+B7HgOLZGkK187pZTZg==} + + '@fumadocs/mdx-remote@1.4.0': + resolution: {integrity: sha512-0aECFvjlpCMeDopjXKBndP/7FbzchNOJu0m3qPsKFtZl+/1QvGsrKPYVVnIY5lwvHL7+E9wGhl6MUHrvcqvWCw==} + peerDependencies: + '@types/react': '*' + fumadocs-core: ^14.0.0 || ^15.0.0 + react: 18.x.x || 19.x.x + peerDependenciesMeta: + '@types/react': + optional: true + + '@fumari/json-schema-to-typescript@1.1.3': + resolution: {integrity: sha512-KnaZAo5W769nOaxhPqEMTdjHdngugxmPpNS+Yr2U90iVxgmNAWwhSr8Nx3l+CUehJKNFzJi2C7clQXOfuPJegA==} + engines: {node: '>=18.0.0'} + + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.0.1': + resolution: {integrity: sha512-A78CUEnFGX8I/WlILxJCuIJXloL0j/OJ9PSchPAfCargEIKmUBWvvEMmKWB5oONwiUqlNt+5eRufdkLxeHIWYw==} + + '@img/sharp-darwin-arm64@0.34.3': + resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.3': + resolution: {integrity: sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.0': + resolution: {integrity: sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.0': + resolution: {integrity: sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.0': + resolution: {integrity: sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.0': + resolution: {integrity: sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.0': + resolution: {integrity: sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.0': + resolution: {integrity: sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.0': + resolution: {integrity: sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + resolution: {integrity: sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + resolution: {integrity: sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.3': + resolution: {integrity: sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.3': + resolution: {integrity: sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.3': + resolution: {integrity: sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.3': + resolution: {integrity: sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.3': + resolution: {integrity: sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.3': + resolution: {integrity: sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.3': + resolution: {integrity: sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.3': + resolution: {integrity: sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.3': + resolution: {integrity: sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.3': + resolution: {integrity: sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.3': + resolution: {integrity: sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/balanced-match@4.0.1': + resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} + engines: {node: 20 || >=22} + + '@isaacs/brace-expansion@5.0.0': + resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} + engines: {node: 20 || >=22} + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jsdevtools/ono@7.1.3': + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@mermaid-js/parser@0.6.2': + resolution: {integrity: sha512-+PO02uGF6L6Cs0Bw8RpGhikVvMWEysfAyl27qTlroUB8jSWr1lL0Sf6zi78ZxlSnmgSY2AMMKVgghnN9jTtwkQ==} + + '@netlify/plugin-nextjs@5.13.3': + resolution: {integrity: sha512-QIEsiTLEySyXroAOhstY01xROokOFojTkhzqhewQAMddbqDrVaCUI6w649R/1MJ0XcD6cB3se/dn8KJXh40HLw==} + engines: {node: '>=18.0.0'} + + '@next/env@15.5.3': + resolution: {integrity: sha512-RSEDTRqyihYXygx/OJXwvVupfr9m04+0vH8vyy0HfZ7keRto6VX9BbEk0J2PUk0VGy6YhklJUSrgForov5F9pw==} + + '@next/swc-darwin-arm64@15.5.3': + resolution: {integrity: sha512-nzbHQo69+au9wJkGKTU9lP7PXv0d1J5ljFpvb+LnEomLtSbJkbZyEs6sbF3plQmiOB2l9OBtN2tNSvCH1nQ9Jg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@15.5.3': + resolution: {integrity: sha512-w83w4SkOOhekJOcA5HBvHyGzgV1W/XvOfpkrxIse4uPWhYTTRwtGEM4v/jiXwNSJvfRvah0H8/uTLBKRXlef8g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@15.5.3': + resolution: {integrity: sha512-+m7pfIs0/yvgVu26ieaKrifV8C8yiLe7jVp9SpcIzg7XmyyNE7toC1fy5IOQozmr6kWl/JONC51osih2RyoXRw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@15.5.3': + resolution: {integrity: sha512-u3PEIzuguSenoZviZJahNLgCexGFhso5mxWCrrIMdvpZn6lkME5vc/ADZG8UUk5K1uWRy4hqSFECrON6UKQBbQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@15.5.3': + resolution: {integrity: sha512-lDtOOScYDZxI2BENN9m0pfVPJDSuUkAD1YXSvlJF0DKwZt0WlA7T7o3wrcEr4Q+iHYGzEaVuZcsIbCps4K27sA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@15.5.3': + resolution: {integrity: sha512-9vWVUnsx9PrY2NwdVRJ4dUURAQ8Su0sLRPqcCCxtX5zIQUBES12eRVHq6b70bbfaVaxIDGJN2afHui0eDm+cLg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@15.5.3': + resolution: {integrity: sha512-1CU20FZzY9LFQigRi6jM45oJMU3KziA5/sSG+dXeVaTm661snQP6xu3ykGxxwU5sLG3sh14teO/IOEPVsQMRfA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@15.5.3': + resolution: {integrity: sha512-JMoLAq3n3y5tKXPQwCK5c+6tmwkuFDa2XAxz8Wm4+IVthdBZdZGh+lmiLUHg9f9IDwIQpUjp+ysd6OkYTyZRZw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@next/third-parties@15.5.4': + resolution: {integrity: sha512-l3T1M/EA32phPzZx+gkQAWOF3E5iAULL1nX4Ej0JZQOXaBwwJzb/rd2uefr5TAshJj/+HjjwmdFu7olXudvgVg==} + peerDependencies: + next: ^13.0.0 || ^14.0.0 || ^15.0.0 + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + + '@orama/orama@3.1.13': + resolution: {integrity: sha512-O0hdKt4K31i8fpq8Bw5RfdPVAqm0EdduBUcluPo2MRcfCOwUEf5JlnvRhf/J0ezOYOD8jQ/LumYZxOVi/XK/BA==} + engines: {node: '>= 20.0.0'} + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-accordion@1.2.12': + resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collapsible@1.1.12': + resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-navigation-menu@1.2.14': + resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-scroll-area@1.2.10': + resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@scalar/helpers@0.0.10': + resolution: {integrity: sha512-VmVgIAkSLBmE5fHgkPiQ0EUZqjsYQNSd/Wd5PwsInYgBvTAxojSeiM58ZVZzMsRRfrCwboCkGn9uNEkbZQX8fg==} + engines: {node: '>=20'} + + '@scalar/helpers@0.0.9': + resolution: {integrity: sha512-paQArJ1zVHkDpK+hXES0ZSg4Cj77zrQSQ3wOVI6xjQ8jTVSVVfbHwkxtM1Me4fbLY7EkkVj8tX21uAw2XzJ51w==} + engines: {node: '>=20'} + + '@scalar/json-magic@0.4.0': + resolution: {integrity: sha512-P4xfYSQB+rcJKDfxNC+T9cms4VNp9aMW0wrVcSKEUJLap/GDUFFUbMoVZMYLUcCehMta9KGM1SsN37f9FIBNow==} + engines: {node: '>=20'} + + '@scalar/json-magic@0.4.1': + resolution: {integrity: sha512-A0tDockhyLDw19TUPuC+8dF4+mdPalNYw5RsT3j9FkyY4ZOvcPEgFiIv3C3/nK5wbwZu+Cf2mrzSkBc0yaN+Wg==} + engines: {node: '>=20'} + + '@scalar/openapi-parser@0.20.3': + resolution: {integrity: sha512-vjnJ9EyUza4GnYR+YK9/GLrYVWbuyH3U4NZWa51kdOg/34gh5H9Jc2Cphmrz/Unxe8EmzwqaElbeM+7qKADgXQ==} + engines: {node: '>=20'} + + '@scalar/openapi-types@0.3.7': + resolution: {integrity: sha512-QHSvHBVDze3+dUwAhIGq6l1iOev4jdoqdBK7QpfeN1Q4h+6qpVEw3EEqBiH0AXUSh/iWwObBv4uMgfIx0aNZ5g==} + engines: {node: '>=20'} + + '@shikijs/core@3.12.2': + resolution: {integrity: sha512-L1Safnhra3tX/oJK5kYHaWmLEBJi1irASwewzY3taX5ibyXyMkkSDZlq01qigjryOBwrXSdFgTiZ3ryzSNeu7Q==} + + '@shikijs/engine-javascript@3.12.2': + resolution: {integrity: sha512-Nm3/azSsaVS7hk6EwtHEnTythjQfwvrO5tKqMlaH9TwG1P+PNaR8M0EAKZ+GaH2DFwvcr4iSfTveyxMIvXEHMw==} + + '@shikijs/engine-oniguruma@3.12.2': + resolution: {integrity: sha512-hozwnFHsLvujK4/CPVHNo3Bcg2EsnG8krI/ZQ2FlBlCRpPZW4XAEQmEwqegJsypsTAN9ehu2tEYe30lYKSZW/w==} + + '@shikijs/langs@3.12.2': + resolution: {integrity: sha512-bVx5PfuZHDSHoBal+KzJZGheFuyH4qwwcwG/n+MsWno5cTlKmaNtTsGzJpHYQ8YPbB5BdEdKU1rga5/6JGY8ww==} + + '@shikijs/rehype@3.12.2': + resolution: {integrity: sha512-9wg+FKv0ByaQScTonpZdrDhADOoJP/yCWLAuiYYG6GehwNV5rGwnLvWKj33UmtLedKMSHzWUdB+Un6rfDFo/FA==} + + '@shikijs/themes@3.12.2': + resolution: {integrity: sha512-fTR3QAgnwYpfGczpIbzPjlRnxyONJOerguQv1iwpyQZ9QXX4qy/XFQqXlf17XTsorxnHoJGbH/LXBvwtqDsF5A==} + + '@shikijs/transformers@3.12.2': + resolution: {integrity: sha512-+z1aMq4N5RoNGY8i7qnTYmG2MBYzFmwkm/yOd6cjEI7OVzcldVvzQCfxU1YbIVgsyB0xHVc2jFe1JhgoXyUoSQ==} + + '@shikijs/types@3.12.2': + resolution: {integrity: sha512-K5UIBzxCyv0YoxN3LMrKB9zuhp1bV+LgewxuVwHdl4Gz5oePoUFrr9EfgJlGlDeXCU1b/yhdnXeuRvAnz8HN8Q==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.13': + resolution: {integrity: sha512-eq3ouolC1oEFOAvOMOBAmfCIqZBJuvWvvYWh5h5iOYfe1HFC6+GZ6EIL0JdM3/niGRJmnrOc+8gl9/HGUaaptw==} + + '@tailwindcss/oxide-android-arm64@4.1.13': + resolution: {integrity: sha512-BrpTrVYyejbgGo57yc8ieE+D6VT9GOgnNdmh5Sac6+t0m+v+sKQevpFVpwX3pBrM2qKrQwJ0c5eDbtjouY/+ew==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.13': + resolution: {integrity: sha512-YP+Jksc4U0KHcu76UhRDHq9bx4qtBftp9ShK/7UGfq0wpaP96YVnnjFnj3ZFrUAjc5iECzODl/Ts0AN7ZPOANQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.13': + resolution: {integrity: sha512-aAJ3bbwrn/PQHDxCto9sxwQfT30PzyYJFG0u/BWZGeVXi5Hx6uuUOQEI2Fa43qvmUjTRQNZnGqe9t0Zntexeuw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.13': + resolution: {integrity: sha512-Wt8KvASHwSXhKE/dJLCCWcTSVmBj3xhVhp/aF3RpAhGeZ3sVo7+NTfgiN8Vey/Fi8prRClDs6/f0KXPDTZE6nQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + resolution: {integrity: sha512-mbVbcAsW3Gkm2MGwA93eLtWrwajz91aXZCNSkGTx/R5eb6KpKD5q8Ueckkh9YNboU8RH7jiv+ol/I7ZyQ9H7Bw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + resolution: {integrity: sha512-wdtfkmpXiwej/yoAkrCP2DNzRXCALq9NVLgLELgLim1QpSfhQM5+ZxQQF8fkOiEpuNoKLp4nKZ6RC4kmeFH0HQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + resolution: {integrity: sha512-hZQrmtLdhyqzXHB7mkXfq0IYbxegaqTmfa1p9MBj72WPoDD3oNOh1Lnxf6xZLY9C3OV6qiCYkO1i/LrzEdW2mg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + resolution: {integrity: sha512-uaZTYWxSXyMWDJZNY1Ul7XkJTCBRFZ5Fo6wtjrgBKzZLoJNrG+WderJwAjPzuNZOnmdrVg260DKwXCFtJ/hWRQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + resolution: {integrity: sha512-oXiPj5mi4Hdn50v5RdnuuIms0PVPI/EG4fxAfFiIKQh5TgQgX7oSuDWntHW7WNIi/yVLAiS+CRGW4RkoGSSgVQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + resolution: {integrity: sha512-+LC2nNtPovtrDwBc/nqnIKYh/W2+R69FA0hgoeOn64BdCX522u19ryLh3Vf3F8W49XBcMIxSe665kwy21FkhvA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + resolution: {integrity: sha512-dziTNeQXtoQ2KBXmrjCxsuPk3F3CQ/yb7ZNZNA+UkNTeiTGgfeh+gH5Pi7mRncVgcPD2xgHvkFCh/MhZWSgyQg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + resolution: {integrity: sha512-3+LKesjXydTkHk5zXX01b5KMzLV1xl2mcktBJkje7rhFUpUlYJy7IMOLqjIRQncLTa1WZZiFY/foAeB5nmaiTw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.13': + resolution: {integrity: sha512-CPgsM1IpGRa880sMbYmG1s4xhAy3xEt1QULgTJGQmZUeNgXFR7s1YxYygmJyBGtou4SyEosGAGEeYqY7R53bIA==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.13': + resolution: {integrity: sha512-HLgx6YSFKJT7rJqh9oJs/TkBFhxuMOfUKSBEPYwV+t78POOBsdQ7crhZLzwcH3T0UyUuOzU/GK5pk5eKr3wCiQ==} + + '@tanstack/query-core@5.89.0': + resolution: {integrity: sha512-joFV1MuPhSLsKfTzwjmPDrp8ENfZ9N23ymFu07nLfn3JCkSHy0CFgsyhHTJOmWaumC/WiNIKM0EJyduCF/Ih/Q==} + + '@tanstack/query-devtools@5.87.3': + resolution: {integrity: sha512-LkzxzSr2HS1ALHTgDmJH5eGAVsSQiuwz//VhFW5OqNk0OQ+Fsqba0Tsf+NzWRtXYvpgUqwQr4b2zdFZwxHcGvg==} + + '@tanstack/react-query-devtools@5.89.0': + resolution: {integrity: sha512-Syc4UjZeIJCkXCRGyQcWwlnv89JNb98MMg/DAkFCV3rwOcknj98+nG3Nm6xLXM6ne9sK6RZeDJMPLKZUh6NUGA==} + peerDependencies: + '@tanstack/react-query': ^5.89.0 + react: ^18 || ^19 + + '@tanstack/react-query@5.89.0': + resolution: {integrity: sha512-SXbtWSTSRXyBOe80mszPxpEbaN4XPRUp/i0EfQK1uyj3KCk/c8FuPJNIRwzOVe/OU3rzxrYtiNabsAmk1l714A==} + peerDependencies: + react: ^18 || ^19 + + '@types/braces@3.0.5': + resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} + + '@types/d3-array@3.2.1': + resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/micromatch@4.0.9': + resolution: {integrity: sha512-7V+8ncr22h4UoYRLnLXSpTxjQrNUXtWHGeMPRJt1nULXI57G9bIcpyrHlmrQ7QK24EyyuXvYcSSWAM8GA9nqCg==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@24.1.0': + resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + + '@types/react-dom@19.1.9': + resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.12': + resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vue/compiler-core@3.5.21': + resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==} + + '@vue/compiler-dom@3.5.21': + resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==} + + '@vue/compiler-sfc@3.5.21': + resolution: {integrity: sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==} + + '@vue/compiler-ssr@3.5.21': + resolution: {integrity: sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==} + + '@vue/reactivity@3.5.21': + resolution: {integrity: sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==} + + '@vue/runtime-core@3.5.21': + resolution: {integrity: sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==} + + '@vue/runtime-dom@3.5.21': + resolution: {integrity: sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==} + + '@vue/server-renderer@3.5.21': + resolution: {integrity: sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==} + peerDependencies: + vue: 3.5.21 + + '@vue/shared@3.5.21': + resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + algoliasearch@5.37.0: + resolution: {integrity: sha512-y7gau/ZOQDqoInTQp0IwTOjkrHc4Aq4R8JgpmCleFwiLl+PbN2DMWoDUWZnrK8AhNJwT++dn28Bt4NZYNLAmuA==} + engines: {node: '>= 14.0.0'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + caniuse-lite@1.0.30001741: + resolution: {integrity: sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.2: + resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.11: + resolution: {integrity: sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==} + + dayjs@1.11.18: + resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dompurify@3.2.6: + resolution: {integrity: sha512-/2GogDQlohXPZe6D6NOgQvXLPSYBqIWMnZ8zzOhn09REE4eyAzb+Hed3jhoM9OkuaJ8P6ZGTTVWQKAi8ieIzfQ==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-value-to-estree@3.4.0: + resolution: {integrity: sha512-Zlp+gxis+gCfK12d3Srl2PdX2ybsEA8ZYy6vQGVQTNNYLEGRQQ56XB64bjemN8kxIKXP1nC9ip4Z+ILy9LGzvQ==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + exsolve@1.0.7: + resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fast-xml-parser@4.5.3: + resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} + hasBin: true + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + foreach@2.0.6: + resolution: {integrity: sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fumadocs-core@15.7.11: + resolution: {integrity: sha512-G7NjwU1OhQRM2Ntfko0KxHmJodVg4yBSbo66DBPILWKyzn5GGZ2rBRjLpf9p/yMeVbzIdB1HY17Ns66Q/i2/ew==} + peerDependencies: + '@mixedbread/sdk': ^0.19.0 + '@oramacloud/client': 1.x.x || 2.x.x + '@tanstack/react-router': 1.x.x + '@types/react': '*' + algoliasearch: 5.x.x + next: 14.x.x || 15.x.x + react: 18.x.x || 19.x.x + react-dom: 18.x.x || 19.x.x + react-router: 7.x.x + waku: ^0.26.0 + peerDependenciesMeta: + '@mixedbread/sdk': + optional: true + '@oramacloud/client': + optional: true + '@tanstack/react-router': + optional: true + '@types/react': + optional: true + algoliasearch: + optional: true + next: + optional: true + react: + optional: true + react-dom: + optional: true + react-router: + optional: true + waku: + optional: true + + fumadocs-mdx@12.0.1: + resolution: {integrity: sha512-o0Qf5pY5RGjn0IoiWbJQ+Yx2MKCwg/RTe0MdtLY2ue8OeRnZHCoPedBJFHQ0ep1X2rzAhjcmfTT5hxc3SELJGg==} + hasBin: true + peerDependencies: + '@fumadocs/mdx-remote': ^1.4.0 + fumadocs-core: ^14.0.0 || ^15.0.0 + next: ^15.3.0 + react: '*' + vite: 6.x.x || 7.x.x + peerDependenciesMeta: + '@fumadocs/mdx-remote': + optional: true + next: + optional: true + react: + optional: true + vite: + optional: true + + fumadocs-openapi@9.3.8: + resolution: {integrity: sha512-cHB/KAPnV7Y1Zjmh/9JXc/+6ClMu3oybCKWfUMLfFzaeCbI6Maj9McPIZ9YtuNWVo7y4fGwxVRm+wB1OpEQyqA==} + peerDependencies: + '@scalar/api-client-react': '*' + '@types/react': '*' + react: 18.x.x || 19.x.x + react-dom: 18.x.x || 19.x.x + peerDependenciesMeta: + '@scalar/api-client-react': + optional: true + '@types/react': + optional: true + + fumadocs-ui@15.7.11: + resolution: {integrity: sha512-XViexribg1qKSDqjBCCScMdvFdT78Lxk9r6BUw4klG1bKQfllUTH3zg8UK04EAI5DXmVnfRPZ+LHUMPMH69taQ==} + peerDependencies: + '@types/react': '*' + next: 14.x.x || 15.x.x + react: 18.x.x || 19.x.x + react-dom: 18.x.x || 19.x.x + tailwindcss: ^3.4.14 || ^4.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + next: + optional: true + tailwindcss: + optional: true + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob@11.0.3: + resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} + engines: {node: 20 || >=22} + hasBin: true + + globals@15.15.0: + resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==} + engines: {node: '>=18'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + image-size@2.0.2: + resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} + engines: {node: '>=16.x'} + hasBin: true + + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jackspeak@4.1.1: + resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} + engines: {node: 20 || >=22} + + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + json-pointer@0.6.2: + resolution: {integrity: sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + jsonpointer@5.0.1: + resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} + engines: {node: '>=0.10.0'} + + katex@0.16.22: + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kolorist@1.8.0: + resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} + + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + + leven@4.1.0: + resolution: {integrity: sha512-KZ9W9nWDT7rF7Dazg8xyLHGLrmpgq2nVNFUckhqdW3szVP6YhCpp/RAnpmVExA9JvrMynjwSLVrEj3AepHR6ew==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + lodash-es@4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@11.2.1: + resolution: {integrity: sha512-r8LA6i4LP4EeWOhqBaZZjDWwehd1xUJPCJd9Sv300H0ZmcUER4+JPh7bqqZeqs1o5pgtgvXm+d9UGrB5zZGDiQ==} + engines: {node: 20 || >=22} + + lru-cache@11.2.2: + resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + engines: {node: 20 || >=22} + + lucide-react@0.540.0: + resolution: {integrity: sha512-armkCAqQvO62EIX4Hq7hqX/q11WSZu0Jd23cnnqx0/49yIxGXyL/zyZfBxNN9YDx0ensPTb4L+DjTh3yQXUxtQ==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.19: + resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + mermaid@11.11.0: + resolution: {integrity: sha512-9lb/VNkZqWTRjVgCV+l1N+t4kyi94y+l5xrmBmbbxZYkfRl5hEDaTPMOcaWKCl1McG8nBEaMlWwkcAEEgjhBgg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@10.0.3: + resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} + engines: {node: 20 || >=22} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + next-validate-link@1.6.3: + resolution: {integrity: sha512-batZxYlkQQSa8jl0gi1AQd5BlJDY3FG41yecpvBWVIM1t/FnBmbo3cMZZx8sSt4UdrsbLuRE2P3p5s+TsQ8m1A==} + + next@15.5.3: + resolution: {integrity: sha512-r/liNAx16SQj4D+XH/oI1dlpv9tdKJ6cONYPwwcCC46f2NjpaRWY+EKCzULfgQYV6YKXjHBchff2IZBSlZmJNw==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + npm-to-yarn@3.0.1: + resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.3: + resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==} + + openapi-sampler@1.6.1: + resolution: {integrity: sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@1.3.0: + resolution: {integrity: sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + + postcss-selector-parser@7.1.0: + resolution: {integrity: sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==} + engines: {node: '>=4'} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + react-dom@19.1.1: + resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} + peerDependencies: + react: ^19.1.1 + + react-hook-form@7.62.0: + resolution: {integrity: sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^16.8.0 || ^17 || ^18 || ^19 + + react-medium-image-zoom@5.3.0: + resolution: {integrity: sha512-RCIzVlsKqy3BYgGgYbolUfuvx0aSKC7YhX/IJGEp+WJxsqdIVYJHkBdj++FAj6VD7RiWj6VVmdCfa/9vJE9hZg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.1: + resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.0.1: + resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==} + + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + remark@15.0.1: + resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + sax@1.4.1: + resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.3: + resolution: {integrity: sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@3.12.2: + resolution: {integrity: sha512-uIrKI+f9IPz1zDT+GMz+0RjzKJiijVr6WDWm9Pe3NNY6QigKCfifCEv9v9R2mDASKKjzjQ2QpFLcxaR3iHSnMA==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strnum@1.1.2: + resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + + style-to-js@1.1.17: + resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} + + style-to-object@1.0.9: + resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + + tailwind-merge@3.3.1: + resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + + tailwindcss@4.1.13: + resolution: {integrity: sha512-i+zidfmTqtwquj4hMEwdjshYYgMbOrPzb9a0M3ZgNa0JMoZeFC6bxZvO8yr8ozS6ix2SDz0+mvryPeBs2TFE+w==} + + tapable@2.2.3: + resolution: {integrity: sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + third-party-capital@1.0.20: + resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==} + + tinyexec@1.0.1: + resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} + engines: {node: '>=18.0.0'} + hasBin: true + + tw-animate-css@1.3.8: + resolution: {integrity: sha512-Qrk3PZ7l7wUcGYhwZloqfkWCmaXZAoqjkdbIDvzfGshwGtexa/DAs9koXxIkrpEasyevandomzCBAV1Yyop5rw==} + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + + vue@3.5.21: + resolution: {integrity: sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + xml-js@1.6.11: + resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} + hasBin: true + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + yaml@2.8.0: + resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + engines: {node: '>= 14.6'} + hasBin: true + + zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} + + zod@4.1.11: + resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + + zod@4.1.8: + resolution: {integrity: sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@algolia/abtesting@1.3.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/client-abtesting@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/client-analytics@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/client-common@5.37.0': {} + + '@algolia/client-insights@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/client-personalization@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/client-query-suggestions@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/client-search@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/ingestion@1.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/monitoring@1.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/recommend@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + '@algolia/requester-browser-xhr@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + + '@algolia/requester-fetch@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + + '@algolia/requester-node-http@5.37.0': + dependencies: + '@algolia/client-common': 5.37.0 + + '@alloc/quick-lru@5.2.0': {} + + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.3.0 + tinyexec: 1.0.1 + + '@antfu/utils@9.2.0': {} + + '@apidevtools/json-schema-ref-parser@11.9.3': + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.15 + js-yaml: 4.1.0 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@biomejs/biome@2.2.4': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.2.4 + '@biomejs/cli-darwin-x64': 2.2.4 + '@biomejs/cli-linux-arm64': 2.2.4 + '@biomejs/cli-linux-arm64-musl': 2.2.4 + '@biomejs/cli-linux-x64': 2.2.4 + '@biomejs/cli-linux-x64-musl': 2.2.4 + '@biomejs/cli-win32-arm64': 2.2.4 + '@biomejs/cli-win32-x64': 2.2.4 + + '@biomejs/cli-darwin-arm64@2.2.4': + optional: true + + '@biomejs/cli-darwin-x64@2.2.4': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.2.4': + optional: true + + '@biomejs/cli-linux-arm64@2.2.4': + optional: true + + '@biomejs/cli-linux-x64-musl@2.2.4': + optional: true + + '@biomejs/cli-linux-x64@2.2.4': + optional: true + + '@biomejs/cli-win32-arm64@2.2.4': + optional: true + + '@biomejs/cli-win32-x64@2.2.4': + optional: true + + '@braintree/sanitize-url@7.1.1': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + + '@emnapi/runtime@1.5.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.10': + optional: true + + '@esbuild/android-arm64@0.25.10': + optional: true + + '@esbuild/android-arm@0.25.10': + optional: true + + '@esbuild/android-x64@0.25.10': + optional: true + + '@esbuild/darwin-arm64@0.25.10': + optional: true + + '@esbuild/darwin-x64@0.25.10': + optional: true + + '@esbuild/freebsd-arm64@0.25.10': + optional: true + + '@esbuild/freebsd-x64@0.25.10': + optional: true + + '@esbuild/linux-arm64@0.25.10': + optional: true + + '@esbuild/linux-arm@0.25.10': + optional: true + + '@esbuild/linux-ia32@0.25.10': + optional: true + + '@esbuild/linux-loong64@0.25.10': + optional: true + + '@esbuild/linux-mips64el@0.25.10': + optional: true + + '@esbuild/linux-ppc64@0.25.10': + optional: true + + '@esbuild/linux-riscv64@0.25.10': + optional: true + + '@esbuild/linux-s390x@0.25.10': + optional: true + + '@esbuild/linux-x64@0.25.10': + optional: true + + '@esbuild/netbsd-arm64@0.25.10': + optional: true + + '@esbuild/netbsd-x64@0.25.10': + optional: true + + '@esbuild/openbsd-arm64@0.25.10': + optional: true + + '@esbuild/openbsd-x64@0.25.10': + optional: true + + '@esbuild/openharmony-arm64@0.25.10': + optional: true + + '@esbuild/sunos-x64@0.25.10': + optional: true + + '@esbuild/win32-arm64@0.25.10': + optional: true + + '@esbuild/win32-ia32@0.25.10': + optional: true + + '@esbuild/win32-x64@0.25.10': + optional: true + + '@floating-ui/core@1.7.3': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.4': + dependencies: + '@floating-ui/core': 1.7.3 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/dom': 1.7.4 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + '@floating-ui/utils@0.2.10': {} + + '@formatjs/intl-localematcher@0.6.1': + dependencies: + tslib: 2.8.1 + + '@fumadocs/mdx-remote@1.4.0(@types/react@19.1.12)(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + dependencies: + '@mdx-js/mdx': 3.1.1 + fumadocs-core: 15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + gray-matter: 4.0.3 + react: 19.1.1 + zod: 4.1.8 + optionalDependencies: + '@types/react': 19.1.12 + transitivePeerDependencies: + - supports-color + + '@fumari/json-schema-to-typescript@1.1.3': + dependencies: + '@apidevtools/json-schema-ref-parser': 11.9.3 + js-yaml: 4.1.0 + prettier: 3.6.2 + + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.0.1': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@antfu/utils': 9.2.0 + '@iconify/types': 2.0.0 + debug: 4.4.1 + globals: 15.15.0 + kolorist: 1.8.0 + local-pkg: 1.1.2 + mlly: 1.8.0 + transitivePeerDependencies: + - supports-color + + '@img/sharp-darwin-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.0 + optional: true + + '@img/sharp-darwin-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.0 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.0': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.0': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.0': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.0': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.0': + optional: true + + '@img/sharp-linux-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.0 + optional: true + + '@img/sharp-linux-arm@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.0 + optional: true + + '@img/sharp-linux-ppc64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.0 + optional: true + + '@img/sharp-linux-s390x@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.0 + optional: true + + '@img/sharp-linux-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.3': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + optional: true + + '@img/sharp-wasm32@0.34.3': + dependencies: + '@emnapi/runtime': 1.5.0 + optional: true + + '@img/sharp-win32-arm64@0.34.3': + optional: true + + '@img/sharp-win32-ia32@0.34.3': + optional: true + + '@img/sharp-win32-x64@0.34.3': + optional: true + + '@isaacs/balanced-match@4.0.1': {} + + '@isaacs/brace-expansion@5.0.0': + dependencies: + '@isaacs/balanced-match': 4.0.1 + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jsdevtools/ono@7.1.3': {} + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.15.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@mermaid-js/parser@0.6.2': + dependencies: + langium: 3.3.1 + + '@netlify/plugin-nextjs@5.13.3': {} + + '@next/env@15.5.3': {} + + '@next/swc-darwin-arm64@15.5.3': + optional: true + + '@next/swc-darwin-x64@15.5.3': + optional: true + + '@next/swc-linux-arm64-gnu@15.5.3': + optional: true + + '@next/swc-linux-arm64-musl@15.5.3': + optional: true + + '@next/swc-linux-x64-gnu@15.5.3': + optional: true + + '@next/swc-linux-x64-musl@15.5.3': + optional: true + + '@next/swc-win32-arm64-msvc@15.5.3': + optional: true + + '@next/swc-win32-x64-msvc@15.5.3': + optional: true + + '@next/third-parties@15.5.4(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': + dependencies: + next: 15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + third-party-capital: 1.0.20 + + '@orama/orama@3.1.13': {} + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-context@1.1.2(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.12)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-direction@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-id@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.12)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/rect': 1.1.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-select@2.2.6(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + aria-hidden: 1.2.6 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-remove-scroll: 2.7.1(@types/react@19.1.12)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-id': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-previous@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-rect@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-use-size@1.1.1(@types/react@19.1.12)(react@19.1.1)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.12)(react@19.1.1) + react: 19.1.1 + optionalDependencies: + '@types/react': 19.1.12 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + '@types/react-dom': 19.1.9(@types/react@19.1.12) + + '@radix-ui/rect@1.1.1': {} + + '@scalar/helpers@0.0.10': {} + + '@scalar/helpers@0.0.9': {} + + '@scalar/json-magic@0.4.0(typescript@5.9.2)': + dependencies: + '@scalar/helpers': 0.0.9 + vue: 3.5.21(typescript@5.9.2) + yaml: 2.8.0 + transitivePeerDependencies: + - typescript + + '@scalar/json-magic@0.4.1(typescript@5.9.2)': + dependencies: + '@scalar/helpers': 0.0.10 + vue: 3.5.21(typescript@5.9.2) + yaml: 2.8.0 + transitivePeerDependencies: + - typescript + + '@scalar/openapi-parser@0.20.3(typescript@5.9.2)': + dependencies: + '@scalar/json-magic': 0.4.0(typescript@5.9.2) + '@scalar/openapi-types': 0.3.7 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + ajv-formats: 3.0.1(ajv@8.17.1) + jsonpointer: 5.0.1 + leven: 4.1.0 + yaml: 2.8.0 + transitivePeerDependencies: + - typescript + + '@scalar/openapi-types@0.3.7': + dependencies: + zod: 3.24.1 + + '@shikijs/core@3.12.2': + dependencies: + '@shikijs/types': 3.12.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.12.2': + dependencies: + '@shikijs/types': 3.12.2 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.3 + + '@shikijs/engine-oniguruma@3.12.2': + dependencies: + '@shikijs/types': 3.12.2 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.12.2': + dependencies: + '@shikijs/types': 3.12.2 + + '@shikijs/rehype@3.12.2': + dependencies: + '@shikijs/types': 3.12.2 + '@types/hast': 3.0.4 + hast-util-to-string: 3.0.1 + shiki: 3.12.2 + unified: 11.0.5 + unist-util-visit: 5.0.0 + + '@shikijs/themes@3.12.2': + dependencies: + '@shikijs/types': 3.12.2 + + '@shikijs/transformers@3.12.2': + dependencies: + '@shikijs/core': 3.12.2 + '@shikijs/types': 3.12.2 + + '@shikijs/types@3.12.2': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@standard-schema/spec@1.0.0': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.13': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.19 + source-map-js: 1.2.1 + tailwindcss: 4.1.13 + + '@tailwindcss/oxide-android-arm64@4.1.13': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.13': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.13': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.13': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.13': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.13': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.13': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.13': + optional: true + + '@tailwindcss/oxide@4.1.13': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-arm64': 4.1.13 + '@tailwindcss/oxide-darwin-x64': 4.1.13 + '@tailwindcss/oxide-freebsd-x64': 4.1.13 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.13 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.13 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.13 + '@tailwindcss/oxide-linux-x64-musl': 4.1.13 + '@tailwindcss/oxide-wasm32-wasi': 4.1.13 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.13 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.13 + + '@tailwindcss/postcss@4.1.13': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.13 + '@tailwindcss/oxide': 4.1.13 + postcss: 8.5.6 + tailwindcss: 4.1.13 + + '@tanstack/query-core@5.89.0': {} + + '@tanstack/query-devtools@5.87.3': {} + + '@tanstack/react-query-devtools@5.89.0(@tanstack/react-query@5.89.0(react@19.1.1))(react@19.1.1)': + dependencies: + '@tanstack/query-devtools': 5.87.3 + '@tanstack/react-query': 5.89.0(react@19.1.1) + react: 19.1.1 + + '@tanstack/react-query@5.89.0(react@19.1.1)': + dependencies: + '@tanstack/query-core': 5.89.0 + react: 19.1.1 + + '@types/braces@3.0.5': {} + + '@types/d3-array@3.2.1': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.1 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.7': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.1 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.7 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/geojson@7946.0.16': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/katex@0.16.7': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/micromatch@4.0.9': + dependencies: + '@types/braces': 3.0.5 + + '@types/ms@2.1.0': {} + + '@types/node@24.1.0': + dependencies: + undici-types: 7.8.0 + + '@types/react-dom@19.1.9(@types/react@19.1.12)': + dependencies: + '@types/react': 19.1.12 + + '@types/react@19.1.12': + dependencies: + csstype: 3.1.3 + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vue/compiler-core@3.5.21': + dependencies: + '@babel/parser': 7.28.4 + '@vue/shared': 3.5.21 + entities: 4.5.0 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.21': + dependencies: + '@vue/compiler-core': 3.5.21 + '@vue/shared': 3.5.21 + + '@vue/compiler-sfc@3.5.21': + dependencies: + '@babel/parser': 7.28.4 + '@vue/compiler-core': 3.5.21 + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + estree-walker: 2.0.2 + magic-string: 0.30.19 + postcss: 8.5.6 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.21': + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/shared': 3.5.21 + + '@vue/reactivity@3.5.21': + dependencies: + '@vue/shared': 3.5.21 + + '@vue/runtime-core@3.5.21': + dependencies: + '@vue/reactivity': 3.5.21 + '@vue/shared': 3.5.21 + + '@vue/runtime-dom@3.5.21': + dependencies: + '@vue/reactivity': 3.5.21 + '@vue/runtime-core': 3.5.21 + '@vue/shared': 3.5.21 + csstype: 3.1.3 + + '@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.9.2))': + dependencies: + '@vue/compiler-ssr': 3.5.21 + '@vue/shared': 3.5.21 + vue: 3.5.21(typescript@5.9.2) + + '@vue/shared@3.5.21': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv-formats@3.0.1(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + algoliasearch@5.37.0: + dependencies: + '@algolia/abtesting': 1.3.0 + '@algolia/client-abtesting': 5.37.0 + '@algolia/client-analytics': 5.37.0 + '@algolia/client-common': 5.37.0 + '@algolia/client-insights': 5.37.0 + '@algolia/client-personalization': 5.37.0 + '@algolia/client-query-suggestions': 5.37.0 + '@algolia/client-search': 5.37.0 + '@algolia/ingestion': 1.37.0 + '@algolia/monitoring': 1.37.0 + '@algolia/recommend': 5.37.0 + '@algolia/requester-browser-xhr': 5.37.0 + '@algolia/requester-fetch': 5.37.0 + '@algolia/requester-node-http': 5.37.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + astring@1.9.0: {} + + bail@2.0.2: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + caniuse-lite@1.0.30001741: {} + + ccount@2.0.1: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.21 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + chownr@3.0.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + optional: true + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + optional: true + + comma-separated-tokens@2.0.3: {} + + commander@7.2.0: {} + + commander@8.3.0: {} + + compute-scroll-into-view@3.1.1: {} + + confbox@0.1.8: {} + + confbox@0.2.2: {} + + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.11: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + + dayjs@1.11.18: {} + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + dequal@2.0.3: {} + + detect-libc@2.0.4: {} + + detect-node-es@1.1.0: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dompurify@3.2.6: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + eastasianwidth@0.2.0: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.3 + + entities@4.5.0: {} + + entities@6.0.1: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.10: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 + + escape-string-regexp@5.0.0: {} + + esprima@4.0.1: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-value-to-estree@3.4.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + exsolve@1.0.7: {} + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.0: {} + + fast-xml-parser@4.5.3: + dependencies: + strnum: 1.1.2 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + foreach@2.0.6: {} + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fsevents@2.3.3: + optional: true + + fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@formatjs/intl-localematcher': 0.6.1 + '@orama/orama': 3.1.13 + '@shikijs/rehype': 3.12.2 + '@shikijs/transformers': 3.12.2 + github-slugger: 2.0.0 + hast-util-to-estree: 3.1.3 + hast-util-to-jsx-runtime: 2.3.6 + image-size: 2.0.2 + negotiator: 1.0.0 + npm-to-yarn: 3.0.1 + react-remove-scroll: 2.7.1(@types/react@19.1.12)(react@19.1.1) + remark: 15.0.1 + remark-gfm: 4.0.1 + remark-rehype: 11.1.2 + scroll-into-view-if-needed: 3.1.0 + shiki: 3.12.2 + unist-util-visit: 5.0.0 + optionalDependencies: + '@types/react': 19.1.12 + algoliasearch: 5.37.0 + next: 15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + transitivePeerDependencies: + - supports-color + + fumadocs-mdx@12.0.1(@fumadocs/mdx-remote@1.4.0(@types/react@19.1.12)(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1))(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1): + dependencies: + '@mdx-js/mdx': 3.1.1 + '@standard-schema/spec': 1.0.0 + chokidar: 4.0.3 + esbuild: 0.25.10 + estree-util-value-to-estree: 3.4.0 + fumadocs-core: 15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + js-yaml: 4.1.0 + lru-cache: 11.2.2 + mdast-util-to-markdown: 2.1.2 + picocolors: 1.1.1 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + tinyexec: 1.0.1 + tinyglobby: 0.2.15 + unified: 11.0.5 + unist-util-visit: 5.0.0 + zod: 4.1.11 + optionalDependencies: + '@fumadocs/mdx-remote': 1.4.0(@types/react@19.1.12)(fumadocs-core@15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1) + next: 15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: 19.1.1 + transitivePeerDependencies: + - supports-color + + fumadocs-openapi@9.3.8(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13)(typescript@5.9.2): + dependencies: + '@fumari/json-schema-to-typescript': 1.1.3 + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-select': 2.2.6(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@scalar/json-magic': 0.4.1(typescript@5.9.2) + '@scalar/openapi-parser': 0.20.3(typescript@5.9.2) + ajv: 8.17.1 + class-variance-authority: 0.7.1 + fumadocs-core: 15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + fumadocs-ui: 15.7.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13) + github-slugger: 2.0.0 + gray-matter: 4.0.3 + hast-util-to-jsx-runtime: 2.3.6 + js-yaml: 4.1.0 + next-themes: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + openapi-sampler: 1.6.1 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-hook-form: 7.62.0(react@19.1.1) + remark: 15.0.1 + remark-rehype: 11.1.2 + tinyglobby: 0.2.15 + xml-js: 1.6.11 + optionalDependencies: + '@types/react': 19.1.12 + transitivePeerDependencies: + - '@mixedbread/sdk' + - '@oramacloud/client' + - '@tanstack/react-router' + - '@types/react-dom' + - algoliasearch + - next + - react-router + - supports-color + - tailwindcss + - typescript + - waku + + fumadocs-ui@15.7.11(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(tailwindcss@4.1.13): + dependencies: + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-direction': 1.1.1(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.12)(react@19.1.1) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.1.9(@types/react@19.1.12))(@types/react@19.1.12)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + class-variance-authority: 0.7.1 + fumadocs-core: 15.7.11(@types/react@19.1.12)(algoliasearch@5.37.0)(next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + lodash.merge: 4.6.2 + next-themes: 0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + postcss-selector-parser: 7.1.0 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + react-medium-image-zoom: 5.3.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + scroll-into-view-if-needed: 3.1.0 + tailwind-merge: 3.3.1 + optionalDependencies: + '@types/react': 19.1.12 + next: 15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + tailwindcss: 4.1.13 + transitivePeerDependencies: + - '@mixedbread/sdk' + - '@oramacloud/client' + - '@tanstack/react-router' + - '@types/react-dom' + - algoliasearch + - react-router + - supports-color + - waku + + get-nonce@1.0.1: {} + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-slugger@2.0.0: {} + + glob@11.0.3: + dependencies: + foreground-child: 3.3.1 + jackspeak: 4.1.1 + minimatch: 10.0.3 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 2.0.0 + + globals@15.15.0: {} + + graceful-fs@4.2.11: {} + + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.1 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + + hachure-fill@0.5.2: {} + + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.17 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.17 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-void-elements@3.0.0: {} + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + image-size@2.0.2: {} + + inline-style-parser@0.2.4: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arrayish@0.3.2: + optional: true + + is-decimal@2.0.1: {} + + is-extendable@0.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + isexe@2.0.0: {} + + jackspeak@4.1.1: + dependencies: + '@isaacs/cliui': 8.0.2 + + jiti@2.5.1: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + json-pointer@0.6.2: + dependencies: + foreach: 2.0.6 + + json-schema-traverse@1.0.0: {} + + jsonpointer@5.0.1: {} + + katex@0.16.22: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + + kind-of@6.0.3: {} + + kolorist@1.8.0: {} + + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + + leven@4.1.0: {} + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + lodash-es@4.17.21: {} + + lodash.merge@4.6.2: {} + + longest-streak@3.1.0: {} + + lru-cache@11.2.1: {} + + lru-cache@11.2.2: {} + + lucide-react@0.540.0(react@19.1.1): + dependencies: + react: 19.1.1 + + magic-string@0.30.19: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + marked@15.0.12: {} + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + mermaid@11.11.0: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 3.0.1 + '@mermaid-js/parser': 0.6.2 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.11 + dayjs: 1.11.18 + dompurify: 3.2.6 + katex: 0.16.22 + khroma: 2.1.0 + lodash-es: 4.17.21 + marked: 15.0.12 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + transitivePeerDependencies: + - supports-color + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.7 + devlop: 1.1.0 + katex: 0.16.22 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.1 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@10.0.3: + dependencies: + '@isaacs/brace-expansion': 5.0.0 + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + mlly@1.8.0: + dependencies: + acorn: 8.15.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.1 + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + negotiator@1.0.0: {} + + next-themes@0.4.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + next-validate-link@1.6.3: + dependencies: + gray-matter: 4.0.3 + picocolors: 1.1.1 + remark: 15.0.1 + remark-gfm: 4.0.1 + remark-mdx: 3.1.1 + tinyglobby: 0.2.15 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - supports-color + + next@15.5.3(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@next/env': 15.5.3 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001741 + postcss: 8.4.31 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + styled-jsx: 5.1.6(react@19.1.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.5.3 + '@next/swc-darwin-x64': 15.5.3 + '@next/swc-linux-arm64-gnu': 15.5.3 + '@next/swc-linux-arm64-musl': 15.5.3 + '@next/swc-linux-x64-gnu': 15.5.3 + '@next/swc-linux-x64-musl': 15.5.3 + '@next/swc-win32-arm64-msvc': 15.5.3 + '@next/swc-win32-x64-msvc': 15.5.3 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + npm-to-yarn@3.0.1: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.3: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.0.1 + regex-recursion: 6.0.2 + + openapi-sampler@1.6.1: + dependencies: + '@types/json-schema': 7.0.15 + fast-xml-parser: 4.5.3 + json-pointer: 0.6.2 + + package-json-from-dist@1.0.1: {} + + package-manager-detector@1.3.0: {} + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-data-parser@0.1.0: {} + + path-key@3.1.1: {} + + path-scurry@2.0.0: + dependencies: + lru-cache: 11.2.1 + minipass: 7.1.2 + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.2 + exsolve: 1.0.7 + pathe: 2.0.3 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + + postcss-selector-parser@7.1.0: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.6.2: {} + + property-information@7.1.0: {} + + quansync@0.2.11: {} + + react-dom@19.1.1(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.26.0 + + react-hook-form@7.62.0(react@19.1.1): + dependencies: + react: 19.1.1 + + react-medium-image-zoom@5.3.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + + react-remove-scroll-bar@2.3.8(@types/react@19.1.12)(react@19.1.1): + dependencies: + react: 19.1.1 + react-style-singleton: 2.2.3(@types/react@19.1.12)(react@19.1.1) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.12 + + react-remove-scroll@2.7.1(@types/react@19.1.12)(react@19.1.1): + dependencies: + react: 19.1.1 + react-remove-scroll-bar: 2.3.8(@types/react@19.1.12)(react@19.1.1) + react-style-singleton: 2.2.3(@types/react@19.1.12)(react@19.1.1) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.1.12)(react@19.1.1) + use-sidecar: 1.1.3(@types/react@19.1.12)(react@19.1.1) + optionalDependencies: + '@types/react': 19.1.12 + + react-style-singleton@2.2.3(@types/react@19.1.12)(react@19.1.1): + dependencies: + get-nonce: 1.0.1 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.12 + + react@19.1.1: {} + + readdirp@4.1.2: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.0.1: + dependencies: + regex-utilities: 2.3.0 + + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.22 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + remark@15.0.1: + dependencies: + '@types/mdast': 4.0.4 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + require-from-string@2.0.2: {} + + resolve-pkg-maps@1.0.0: {} + + robust-predicates@3.0.2: {} + + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + + sax@1.4.1: {} + + scheduler@0.26.0: {} + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + + semver@7.7.2: + optional: true + + sharp@0.34.3: + dependencies: + color: 4.2.3 + detect-libc: 2.0.4 + semver: 7.7.2 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.3 + '@img/sharp-darwin-x64': 0.34.3 + '@img/sharp-libvips-darwin-arm64': 1.2.0 + '@img/sharp-libvips-darwin-x64': 1.2.0 + '@img/sharp-libvips-linux-arm': 1.2.0 + '@img/sharp-libvips-linux-arm64': 1.2.0 + '@img/sharp-libvips-linux-ppc64': 1.2.0 + '@img/sharp-libvips-linux-s390x': 1.2.0 + '@img/sharp-libvips-linux-x64': 1.2.0 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.0 + '@img/sharp-libvips-linuxmusl-x64': 1.2.0 + '@img/sharp-linux-arm': 0.34.3 + '@img/sharp-linux-arm64': 0.34.3 + '@img/sharp-linux-ppc64': 0.34.3 + '@img/sharp-linux-s390x': 0.34.3 + '@img/sharp-linux-x64': 0.34.3 + '@img/sharp-linuxmusl-arm64': 0.34.3 + '@img/sharp-linuxmusl-x64': 0.34.3 + '@img/sharp-wasm32': 0.34.3 + '@img/sharp-win32-arm64': 0.34.3 + '@img/sharp-win32-ia32': 0.34.3 + '@img/sharp-win32-x64': 0.34.3 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@3.12.2: + dependencies: + '@shikijs/core': 3.12.2 + '@shikijs/engine-javascript': 3.12.2 + '@shikijs/engine-oniguruma': 3.12.2 + '@shikijs/langs': 3.12.2 + '@shikijs/themes': 3.12.2 + '@shikijs/types': 3.12.2 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + optional: true + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + sprintf-js@1.0.3: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom-string@1.0.0: {} + + strnum@1.1.2: {} + + style-to-js@1.1.17: + dependencies: + style-to-object: 1.0.9 + + style-to-object@1.0.9: + dependencies: + inline-style-parser: 0.2.4 + + styled-jsx@5.1.6(react@19.1.1): + dependencies: + client-only: 0.0.1 + react: 19.1.1 + + stylis@4.3.6: {} + + tailwind-merge@3.3.1: {} + + tailwindcss@4.1.13: {} + + tapable@2.2.3: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + third-party-capital@1.0.20: {} + + tinyexec@1.0.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-dedent@2.2.0: {} + + tslib@2.8.1: {} + + tsx@4.20.5: + dependencies: + esbuild: 0.25.10 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + tw-animate-css@1.3.8: {} + + typescript@5.9.2: {} + + ufo@1.6.1: {} + + undici-types@7.8.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-is@6.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.1: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + use-callback-ref@1.3.3(@types/react@19.1.12)(react@19.1.1): + dependencies: + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.12 + + use-sidecar@1.1.3(@types/react@19.1.12)(react@19.1.1): + dependencies: + detect-node-es: 1.1.0 + react: 19.1.1 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.1.12 + + util-deprecate@1.0.2: {} + + uuid@11.1.0: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + + vue@3.5.21(typescript@5.9.2): + dependencies: + '@vue/compiler-dom': 3.5.21 + '@vue/compiler-sfc': 3.5.21 + '@vue/runtime-dom': 3.5.21 + '@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.9.2)) + '@vue/shared': 3.5.21 + optionalDependencies: + typescript: 5.9.2 + + web-namespaces@2.0.1: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + xml-js@1.6.11: + dependencies: + sax: 1.4.1 + + yallist@5.0.0: {} + + yaml@2.8.0: {} + + zod@3.24.1: {} + + zod@4.1.11: {} + + zod@4.1.8: {} + + zwitch@2.0.4: {} diff --git a/docs/postcss.config.mjs b/docs/postcss.config.mjs new file mode 100644 index 00000000..a34a3d56 --- /dev/null +++ b/docs/postcss.config.mjs @@ -0,0 +1,5 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +}; diff --git a/docs/public/GSNDappTool.png b/docs/public/GSNDappTool.png new file mode 100644 index 00000000..d112877c Binary files /dev/null and b/docs/public/GSNDappTool.png differ diff --git a/docs/public/StarterKit.png b/docs/public/StarterKit.png new file mode 100644 index 00000000..559a28c8 Binary files /dev/null and b/docs/public/StarterKit.png differ diff --git a/docs/public/access-control-multiple copy.svg b/docs/public/access-control-multiple copy.svg new file mode 100644 index 00000000..0314e09e --- /dev/null +++ b/docs/public/access-control-multiple copy.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/access-control-multiple.svg b/docs/public/access-control-multiple.svg new file mode 100644 index 00000000..0314e09e --- /dev/null +++ b/docs/public/access-control-multiple.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/access-manager copy.svg b/docs/public/access-manager copy.svg new file mode 100644 index 00000000..12f91bae --- /dev/null +++ b/docs/public/access-manager copy.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/access-manager-functions copy.svg b/docs/public/access-manager-functions copy.svg new file mode 100644 index 00000000..dbbf0417 --- /dev/null +++ b/docs/public/access-manager-functions copy.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/access-manager-functions.svg b/docs/public/access-manager-functions.svg new file mode 100644 index 00000000..dbbf0417 --- /dev/null +++ b/docs/public/access-manager-functions.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/access-manager.svg b/docs/public/access-manager.svg new file mode 100644 index 00000000..12f91bae --- /dev/null +++ b/docs/public/access-manager.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/alice-direct-link.png b/docs/public/alice-direct-link.png new file mode 100644 index 00000000..17d22536 Binary files /dev/null and b/docs/public/alice-direct-link.png differ diff --git a/docs/public/android-chrome-192x192.png b/docs/public/android-chrome-192x192.png new file mode 100644 index 00000000..bc9db0ab Binary files /dev/null and b/docs/public/android-chrome-192x192.png differ diff --git a/docs/public/android-chrome-512x512.png b/docs/public/android-chrome-512x512.png new file mode 100644 index 00000000..e8c1f11a Binary files /dev/null and b/docs/public/android-chrome-512x512.png differ diff --git a/docs/public/apple-touch-icon.png b/docs/public/apple-touch-icon.png new file mode 100644 index 00000000..18e109a2 Binary files /dev/null and b/docs/public/apple-touch-icon.png differ diff --git a/docs/public/defender/access-control-contract.png b/docs/public/defender/access-control-contract.png new file mode 100644 index 00000000..b3a53318 Binary files /dev/null and b/docs/public/defender/access-control-contract.png differ diff --git a/docs/public/defender/access-control.png b/docs/public/defender/access-control.png new file mode 100644 index 00000000..efc28fd5 Binary files /dev/null and b/docs/public/defender/access-control.png differ diff --git a/docs/public/defender/action-migration-2.0.png b/docs/public/defender/action-migration-2.0.png new file mode 100644 index 00000000..62d30f1a Binary files /dev/null and b/docs/public/defender/action-migration-2.0.png differ diff --git a/docs/public/defender/actions-autotask-faq.png b/docs/public/defender/actions-autotask-faq.png new file mode 100644 index 00000000..b9be9a92 Binary files /dev/null and b/docs/public/defender/actions-autotask-faq.png differ diff --git a/docs/public/defender/actions-parallel-workflow.png b/docs/public/defender/actions-parallel-workflow.png new file mode 100644 index 00000000..e5e4d2af Binary files /dev/null and b/docs/public/defender/actions-parallel-workflow.png differ diff --git a/docs/public/defender/actions-start-workflow.png b/docs/public/defender/actions-start-workflow.png new file mode 100644 index 00000000..b9e0bf67 Binary files /dev/null and b/docs/public/defender/actions-start-workflow.png differ diff --git a/docs/public/defender/actions-workflow.png b/docs/public/defender/actions-workflow.png new file mode 100644 index 00000000..d8b81161 Binary files /dev/null and b/docs/public/defender/actions-workflow.png differ diff --git a/docs/public/defender/actions.webm b/docs/public/defender/actions.webm new file mode 100644 index 00000000..59b2de78 Binary files /dev/null and b/docs/public/defender/actions.webm differ diff --git a/docs/public/defender/address-book-faq.png b/docs/public/defender/address-book-faq.png new file mode 100644 index 00000000..b045cc56 Binary files /dev/null and b/docs/public/defender/address-book-faq.png differ diff --git a/docs/public/defender/address-book-migration-1.0.png b/docs/public/defender/address-book-migration-1.0.png new file mode 100644 index 00000000..09bf8823 Binary files /dev/null and b/docs/public/defender/address-book-migration-1.0.png differ diff --git a/docs/public/defender/address-book-migration-2.0.png b/docs/public/defender/address-book-migration-2.0.png new file mode 100644 index 00000000..bd4be94f Binary files /dev/null and b/docs/public/defender/address-book-migration-2.0.png differ diff --git a/docs/public/defender/address-book.webm b/docs/public/defender/address-book.webm new file mode 100644 index 00000000..115452db Binary files /dev/null and b/docs/public/defender/address-book.webm differ diff --git a/docs/public/defender/api-key-expiration-config.png b/docs/public/defender/api-key-expiration-config.png new file mode 100644 index 00000000..ee39ff39 Binary files /dev/null and b/docs/public/defender/api-key-expiration-config.png differ diff --git a/docs/public/defender/audit-filter.png b/docs/public/defender/audit-filter.png new file mode 100644 index 00000000..54e3115a Binary files /dev/null and b/docs/public/defender/audit-filter.png differ diff --git a/docs/public/defender/audit-new-issue.png b/docs/public/defender/audit-new-issue.png new file mode 100644 index 00000000..fbf752c5 Binary files /dev/null and b/docs/public/defender/audit-new-issue.png differ diff --git a/docs/public/defender/audit-reply-issue.png b/docs/public/defender/audit-reply-issue.png new file mode 100644 index 00000000..43529c3c Binary files /dev/null and b/docs/public/defender/audit-reply-issue.png differ diff --git a/docs/public/defender/audit-side.png b/docs/public/defender/audit-side.png new file mode 100644 index 00000000..13be5875 Binary files /dev/null and b/docs/public/defender/audit-side.png differ diff --git a/docs/public/defender/audit-status.png b/docs/public/defender/audit-status.png new file mode 100644 index 00000000..5b5de4d5 Binary files /dev/null and b/docs/public/defender/audit-status.png differ diff --git a/docs/public/defender/audit-trail.png b/docs/public/defender/audit-trail.png new file mode 100644 index 00000000..5520302a Binary files /dev/null and b/docs/public/defender/audit-trail.png differ diff --git a/docs/public/defender/auto-action-general-info.png b/docs/public/defender/auto-action-general-info.png new file mode 100644 index 00000000..ae7c5070 Binary files /dev/null and b/docs/public/defender/auto-action-general-info.png differ diff --git a/docs/public/defender/autotasks-migration-1.0.png b/docs/public/defender/autotasks-migration-1.0.png new file mode 100644 index 00000000..af56afa2 Binary files /dev/null and b/docs/public/defender/autotasks-migration-1.0.png differ diff --git a/docs/public/defender/code-assets.png b/docs/public/defender/code-assets.png new file mode 100644 index 00000000..8ff115eb Binary files /dev/null and b/docs/public/defender/code-assets.png differ diff --git a/docs/public/defender/code-report-summary.png b/docs/public/defender/code-report-summary.png new file mode 100644 index 00000000..a9c42ab8 Binary files /dev/null and b/docs/public/defender/code-report-summary.png differ diff --git a/docs/public/defender/code-settings-advanced.png b/docs/public/defender/code-settings-advanced.png new file mode 100644 index 00000000..67494491 Binary files /dev/null and b/docs/public/defender/code-settings-advanced.png differ diff --git a/docs/public/defender/code-settings-repositories.png b/docs/public/defender/code-settings-repositories.png new file mode 100644 index 00000000..e4a46f8e Binary files /dev/null and b/docs/public/defender/code-settings-repositories.png differ diff --git a/docs/public/defender/contract-inspector-detailed-report.png b/docs/public/defender/contract-inspector-detailed-report.png new file mode 100644 index 00000000..992db43d Binary files /dev/null and b/docs/public/defender/contract-inspector-detailed-report.png differ diff --git a/docs/public/defender/dependency-checker-detailed-report.png b/docs/public/defender/dependency-checker-detailed-report.png new file mode 100644 index 00000000..0142e16f Binary files /dev/null and b/docs/public/defender/dependency-checker-detailed-report.png differ diff --git a/docs/public/defender/deploy-metadata-1.0.png b/docs/public/defender/deploy-metadata-1.0.png new file mode 100644 index 00000000..73bfe7f4 Binary files /dev/null and b/docs/public/defender/deploy-metadata-1.0.png differ diff --git a/docs/public/defender/feedback-button.png b/docs/public/defender/feedback-button.png new file mode 100644 index 00000000..339b2fe1 Binary files /dev/null and b/docs/public/defender/feedback-button.png differ diff --git a/docs/public/defender/feedback-form.png b/docs/public/defender/feedback-form.png new file mode 100644 index 00000000..de9358e4 Binary files /dev/null and b/docs/public/defender/feedback-form.png differ diff --git a/docs/public/defender/guide-configure-private-network.png b/docs/public/defender/guide-configure-private-network.png new file mode 100644 index 00000000..5cfa3e02 Binary files /dev/null and b/docs/public/defender/guide-configure-private-network.png differ diff --git a/docs/public/defender/guide-edit-private-network.png b/docs/public/defender/guide-edit-private-network.png new file mode 100644 index 00000000..32b17957 Binary files /dev/null and b/docs/public/defender/guide-edit-private-network.png differ diff --git a/docs/public/defender/guide-factory-action-run-history.png b/docs/public/defender/guide-factory-action-run-history.png new file mode 100644 index 00000000..a5f2b870 Binary files /dev/null and b/docs/public/defender/guide-factory-action-run-history.png differ diff --git a/docs/public/defender/guide-factory-api.png b/docs/public/defender/guide-factory-api.png new file mode 100644 index 00000000..daf9ca32 Binary files /dev/null and b/docs/public/defender/guide-factory-api.png differ diff --git a/docs/public/defender/guide-factory-create-action.png b/docs/public/defender/guide-factory-create-action.png new file mode 100644 index 00000000..d46b5270 Binary files /dev/null and b/docs/public/defender/guide-factory-create-action.png differ diff --git a/docs/public/defender/guide-factory-create-clone.png b/docs/public/defender/guide-factory-create-clone.png new file mode 100644 index 00000000..ea5d3aa2 Binary files /dev/null and b/docs/public/defender/guide-factory-create-clone.png differ diff --git a/docs/public/defender/guide-factory-monitor-alerts.png b/docs/public/defender/guide-factory-monitor-alerts.png new file mode 100644 index 00000000..32a40968 Binary files /dev/null and b/docs/public/defender/guide-factory-monitor-alerts.png differ diff --git a/docs/public/defender/guide-factory-monitor-clones.png b/docs/public/defender/guide-factory-monitor-clones.png new file mode 100644 index 00000000..80d3040b Binary files /dev/null and b/docs/public/defender/guide-factory-monitor-clones.png differ diff --git a/docs/public/defender/guide-factory-monitor-events.png b/docs/public/defender/guide-factory-monitor-events.png new file mode 100644 index 00000000..df18aba7 Binary files /dev/null and b/docs/public/defender/guide-factory-monitor-events.png differ diff --git a/docs/public/defender/guide-factory-monitor-general-information.png b/docs/public/defender/guide-factory-monitor-general-information.png new file mode 100644 index 00000000..108cb6a4 Binary files /dev/null and b/docs/public/defender/guide-factory-monitor-general-information.png differ diff --git a/docs/public/defender/guide-factory-secrets.png b/docs/public/defender/guide-factory-secrets.png new file mode 100644 index 00000000..eb298af1 Binary files /dev/null and b/docs/public/defender/guide-factory-secrets.png differ diff --git a/docs/public/defender/guide-fireblock-paste-api-key.png b/docs/public/defender/guide-fireblock-paste-api-key.png new file mode 100644 index 00000000..3f86465c Binary files /dev/null and b/docs/public/defender/guide-fireblock-paste-api-key.png differ diff --git a/docs/public/defender/guide-fireblocks-add-user.png b/docs/public/defender/guide-fireblocks-add-user.png new file mode 100644 index 00000000..3900b833 Binary files /dev/null and b/docs/public/defender/guide-fireblocks-add-user.png differ diff --git a/docs/public/defender/guide-fireblocks-api-key.png b/docs/public/defender/guide-fireblocks-api-key.png new file mode 100644 index 00000000..02ba22af Binary files /dev/null and b/docs/public/defender/guide-fireblocks-api-key.png differ diff --git a/docs/public/defender/guide-fireblocks-approval-process-automatic.png b/docs/public/defender/guide-fireblocks-approval-process-automatic.png new file mode 100644 index 00000000..29507dc8 Binary files /dev/null and b/docs/public/defender/guide-fireblocks-approval-process-automatic.png differ diff --git a/docs/public/defender/guide-fireblocks-approval-process-manual.png b/docs/public/defender/guide-fireblocks-approval-process-manual.png new file mode 100644 index 00000000..5148c602 Binary files /dev/null and b/docs/public/defender/guide-fireblocks-approval-process-manual.png differ diff --git a/docs/public/defender/guide-fireblocks-asset-wallet-address.png b/docs/public/defender/guide-fireblocks-asset-wallet-address.png new file mode 100644 index 00000000..33a3b6f2 Binary files /dev/null and b/docs/public/defender/guide-fireblocks-asset-wallet-address.png differ diff --git a/docs/public/defender/guide-fireblocks-csr-modal.png b/docs/public/defender/guide-fireblocks-csr-modal.png new file mode 100644 index 00000000..afc85eef Binary files /dev/null and b/docs/public/defender/guide-fireblocks-csr-modal.png differ diff --git a/docs/public/defender/guide-fireblocks-edit-api-key.png b/docs/public/defender/guide-fireblocks-edit-api-key.png new file mode 100644 index 00000000..43c15f54 Binary files /dev/null and b/docs/public/defender/guide-fireblocks-edit-api-key.png differ diff --git a/docs/public/defender/guide-fireblocks-integration-tab.png b/docs/public/defender/guide-fireblocks-integration-tab.png new file mode 100644 index 00000000..a593a6d5 Binary files /dev/null and b/docs/public/defender/guide-fireblocks-integration-tab.png differ diff --git a/docs/public/defender/guide-fireblocks-vault-id.png b/docs/public/defender/guide-fireblocks-vault-id.png new file mode 100644 index 00000000..73de939a Binary files /dev/null and b/docs/public/defender/guide-fireblocks-vault-id.png differ diff --git a/docs/public/defender/guide-forta-diagram.png b/docs/public/defender/guide-forta-diagram.png new file mode 100644 index 00000000..6d342c5f Binary files /dev/null and b/docs/public/defender/guide-forta-diagram.png differ diff --git a/docs/public/defender/guide-fund-private-network-relayer.png b/docs/public/defender/guide-fund-private-network-relayer.png new file mode 100644 index 00000000..5f8cf711 Binary files /dev/null and b/docs/public/defender/guide-fund-private-network-relayer.png differ diff --git a/docs/public/defender/guide-meta-tx-copy-webhook.png b/docs/public/defender/guide-meta-tx-copy-webhook.png new file mode 100644 index 00000000..0ab30776 Binary files /dev/null and b/docs/public/defender/guide-meta-tx-copy-webhook.png differ diff --git a/docs/public/defender/guide-profile-disable-system-notifications.png b/docs/public/defender/guide-profile-disable-system-notifications.png new file mode 100644 index 00000000..cc76af3d Binary files /dev/null and b/docs/public/defender/guide-profile-disable-system-notifications.png differ diff --git a/docs/public/defender/guide-subgraph-private-network.png b/docs/public/defender/guide-subgraph-private-network.png new file mode 100644 index 00000000..a22b0a37 Binary files /dev/null and b/docs/public/defender/guide-subgraph-private-network.png differ diff --git a/docs/public/defender/guide-tenderly-private-network.png b/docs/public/defender/guide-tenderly-private-network.png new file mode 100644 index 00000000..0a347cf8 Binary files /dev/null and b/docs/public/defender/guide-tenderly-private-network.png differ diff --git a/docs/public/defender/guide-timelock-proposer.png b/docs/public/defender/guide-timelock-proposer.png new file mode 100644 index 00000000..4025337a Binary files /dev/null and b/docs/public/defender/guide-timelock-proposer.png differ diff --git a/docs/public/defender/guide-timelock-role-receiver.png b/docs/public/defender/guide-timelock-role-receiver.png new file mode 100644 index 00000000..ff24b503 Binary files /dev/null and b/docs/public/defender/guide-timelock-role-receiver.png differ diff --git a/docs/public/defender/guide-timelock-role-remover.png b/docs/public/defender/guide-timelock-role-remover.png new file mode 100644 index 00000000..c10197af Binary files /dev/null and b/docs/public/defender/guide-timelock-role-remover.png differ diff --git a/docs/public/defender/guide-timelock-role-revoked.png b/docs/public/defender/guide-timelock-role-revoked.png new file mode 100644 index 00000000..d1b218a2 Binary files /dev/null and b/docs/public/defender/guide-timelock-role-revoked.png differ diff --git a/docs/public/defender/guide-timelock-roles-add-contract.png b/docs/public/defender/guide-timelock-roles-add-contract.png new file mode 100644 index 00000000..4cd28dc0 Binary files /dev/null and b/docs/public/defender/guide-timelock-roles-add-contract.png differ diff --git a/docs/public/defender/guide-timelock-roles-general-information.png b/docs/public/defender/guide-timelock-roles-general-information.png new file mode 100644 index 00000000..b0357c7d Binary files /dev/null and b/docs/public/defender/guide-timelock-roles-general-information.png differ diff --git a/docs/public/defender/guide-timelock-roles-grant.png b/docs/public/defender/guide-timelock-roles-grant.png new file mode 100644 index 00000000..7e449d9c Binary files /dev/null and b/docs/public/defender/guide-timelock-roles-grant.png differ diff --git a/docs/public/defender/guide-timelock-roles-granted.png b/docs/public/defender/guide-timelock-roles-granted.png new file mode 100644 index 00000000..6a64d4d5 Binary files /dev/null and b/docs/public/defender/guide-timelock-roles-granted.png differ diff --git a/docs/public/defender/guide-timelock-roles-schedule.png b/docs/public/defender/guide-timelock-roles-schedule.png new file mode 100644 index 00000000..dd23714b Binary files /dev/null and b/docs/public/defender/guide-timelock-roles-schedule.png differ diff --git a/docs/public/defender/guide-timelock-roles.png b/docs/public/defender/guide-timelock-roles.png new file mode 100644 index 00000000..97502501 Binary files /dev/null and b/docs/public/defender/guide-timelock-roles.png differ diff --git a/docs/public/defender/guide-usage-notifications-all.png b/docs/public/defender/guide-usage-notifications-all.png new file mode 100644 index 00000000..c53506ce Binary files /dev/null and b/docs/public/defender/guide-usage-notifications-all.png differ diff --git a/docs/public/defender/guide-usage-notifications-create.png b/docs/public/defender/guide-usage-notifications-create.png new file mode 100644 index 00000000..7406b447 Binary files /dev/null and b/docs/public/defender/guide-usage-notifications-create.png differ diff --git a/docs/public/defender/guide-usage-notifications-edit-menu.png b/docs/public/defender/guide-usage-notifications-edit-menu.png new file mode 100644 index 00000000..a61715e6 Binary files /dev/null and b/docs/public/defender/guide-usage-notifications-edit-menu.png differ diff --git a/docs/public/defender/guide-usage-notifications-system-unmetered.png b/docs/public/defender/guide-usage-notifications-system-unmetered.png new file mode 100644 index 00000000..11f2d7fc Binary files /dev/null and b/docs/public/defender/guide-usage-notifications-system-unmetered.png differ diff --git a/docs/public/defender/guide-usage-notifications-system.png b/docs/public/defender/guide-usage-notifications-system.png new file mode 100644 index 00000000..3847240b Binary files /dev/null and b/docs/public/defender/guide-usage-notifications-system.png differ diff --git a/docs/public/defender/logs-detailed.png b/docs/public/defender/logs-detailed.png new file mode 100644 index 00000000..c3a53eb0 Binary files /dev/null and b/docs/public/defender/logs-detailed.png differ diff --git a/docs/public/defender/logs-migration-1.0.png b/docs/public/defender/logs-migration-1.0.png new file mode 100644 index 00000000..68068a85 Binary files /dev/null and b/docs/public/defender/logs-migration-1.0.png differ diff --git a/docs/public/defender/logs-migration-2.0.png b/docs/public/defender/logs-migration-2.0.png new file mode 100644 index 00000000..1f70f794 Binary files /dev/null and b/docs/public/defender/logs-migration-2.0.png differ diff --git a/docs/public/defender/logs.png b/docs/public/defender/logs.png new file mode 100644 index 00000000..ea8052cf Binary files /dev/null and b/docs/public/defender/logs.png differ diff --git a/docs/public/defender/manage-address-book.png b/docs/public/defender/manage-address-book.png new file mode 100644 index 00000000..4ac56432 Binary files /dev/null and b/docs/public/defender/manage-address-book.png differ diff --git a/docs/public/defender/manage-advanced-export-serverless.png b/docs/public/defender/manage-advanced-export-serverless.png new file mode 100644 index 00000000..1267d230 Binary files /dev/null and b/docs/public/defender/manage-advanced-export-serverless.png differ diff --git a/docs/public/defender/manage-api-key-v2.png b/docs/public/defender/manage-api-key-v2.png new file mode 100644 index 00000000..b5260a8c Binary files /dev/null and b/docs/public/defender/manage-api-key-v2.png differ diff --git a/docs/public/defender/manage-api-key.png b/docs/public/defender/manage-api-key.png new file mode 100644 index 00000000..ea4494ff Binary files /dev/null and b/docs/public/defender/manage-api-key.png differ diff --git a/docs/public/defender/manage-approvals.png b/docs/public/defender/manage-approvals.png new file mode 100644 index 00000000..0b13000c Binary files /dev/null and b/docs/public/defender/manage-approvals.png differ diff --git a/docs/public/defender/manage-forked-networks-create.png b/docs/public/defender/manage-forked-networks-create.png new file mode 100644 index 00000000..b847425a Binary files /dev/null and b/docs/public/defender/manage-forked-networks-create.png differ diff --git a/docs/public/defender/manage-forked-networks-selection.png b/docs/public/defender/manage-forked-networks-selection.png new file mode 100644 index 00000000..144526aa Binary files /dev/null and b/docs/public/defender/manage-forked-networks-selection.png differ diff --git a/docs/public/defender/manage-new-api-key-v2.png b/docs/public/defender/manage-new-api-key-v2.png new file mode 100644 index 00000000..be26bd93 Binary files /dev/null and b/docs/public/defender/manage-new-api-key-v2.png differ diff --git a/docs/public/defender/manage-new-api-key.png b/docs/public/defender/manage-new-api-key.png new file mode 100644 index 00000000..77a1f3b3 Binary files /dev/null and b/docs/public/defender/manage-new-api-key.png differ diff --git a/docs/public/defender/manage-notify-channels.png b/docs/public/defender/manage-notify-channels.png new file mode 100644 index 00000000..b27929f0 Binary files /dev/null and b/docs/public/defender/manage-notify-channels.png differ diff --git a/docs/public/defender/manage-notify-datadog.png b/docs/public/defender/manage-notify-datadog.png new file mode 100644 index 00000000..910c04f5 Binary files /dev/null and b/docs/public/defender/manage-notify-datadog.png differ diff --git a/docs/public/defender/manage-notify-discord.png b/docs/public/defender/manage-notify-discord.png new file mode 100644 index 00000000..8908a3d4 Binary files /dev/null and b/docs/public/defender/manage-notify-discord.png differ diff --git a/docs/public/defender/manage-notify-telegram.png b/docs/public/defender/manage-notify-telegram.png new file mode 100644 index 00000000..5312bcf4 Binary files /dev/null and b/docs/public/defender/manage-notify-telegram.png differ diff --git a/docs/public/defender/manage-notify-webhook.png b/docs/public/defender/manage-notify-webhook.png new file mode 100644 index 00000000..00c37c44 Binary files /dev/null and b/docs/public/defender/manage-notify-webhook.png differ diff --git a/docs/public/defender/manage-private-networks-create.png b/docs/public/defender/manage-private-networks-create.png new file mode 100644 index 00000000..09cec0bf Binary files /dev/null and b/docs/public/defender/manage-private-networks-create.png differ diff --git a/docs/public/defender/manage-private-networks-selection.png b/docs/public/defender/manage-private-networks-selection.png new file mode 100644 index 00000000..dc5e16e9 Binary files /dev/null and b/docs/public/defender/manage-private-networks-selection.png differ diff --git a/docs/public/defender/manage-relayer-api-key.png b/docs/public/defender/manage-relayer-api-key.png new file mode 100644 index 00000000..0745d3a8 Binary files /dev/null and b/docs/public/defender/manage-relayer-api-key.png differ diff --git a/docs/public/defender/manage-relayer-policies.png b/docs/public/defender/manage-relayer-policies.png new file mode 100644 index 00000000..81dec267 Binary files /dev/null and b/docs/public/defender/manage-relayer-policies.png differ diff --git a/docs/public/defender/manage-relayers-create-api-key.png b/docs/public/defender/manage-relayers-create-api-key.png new file mode 100644 index 00000000..42079dda Binary files /dev/null and b/docs/public/defender/manage-relayers-create-api-key.png differ diff --git a/docs/public/defender/manage-relayers-detail.png b/docs/public/defender/manage-relayers-detail.png new file mode 100644 index 00000000..6a7fd6ff Binary files /dev/null and b/docs/public/defender/manage-relayers-detail.png differ diff --git a/docs/public/defender/manage-relayers.png b/docs/public/defender/manage-relayers.png new file mode 100644 index 00000000..a0b0f16c Binary files /dev/null and b/docs/public/defender/manage-relayers.png differ diff --git a/docs/public/defender/manage-role-create.png b/docs/public/defender/manage-role-create.png new file mode 100644 index 00000000..738c667b Binary files /dev/null and b/docs/public/defender/manage-role-create.png differ diff --git a/docs/public/defender/manage-secrets.png b/docs/public/defender/manage-secrets.png new file mode 100644 index 00000000..b1224c7e Binary files /dev/null and b/docs/public/defender/manage-secrets.png differ diff --git a/docs/public/defender/manage-team-invite.png b/docs/public/defender/manage-team-invite.png new file mode 100644 index 00000000..2429b8d4 Binary files /dev/null and b/docs/public/defender/manage-team-invite.png differ diff --git a/docs/public/defender/monitor-alert-v2.png b/docs/public/defender/monitor-alert-v2.png new file mode 100644 index 00000000..5c18f3d9 Binary files /dev/null and b/docs/public/defender/monitor-alert-v2.png differ diff --git a/docs/public/defender/monitor-alert.png b/docs/public/defender/monitor-alert.png new file mode 100644 index 00000000..105bb495 Binary files /dev/null and b/docs/public/defender/monitor-alert.png differ diff --git a/docs/public/defender/monitor-migration-2.0.png b/docs/public/defender/monitor-migration-2.0.png new file mode 100644 index 00000000..d0542aa3 Binary files /dev/null and b/docs/public/defender/monitor-migration-2.0.png differ diff --git a/docs/public/defender/monitor-settings.png b/docs/public/defender/monitor-settings.png new file mode 100644 index 00000000..7b3087cc Binary files /dev/null and b/docs/public/defender/monitor-settings.png differ diff --git a/docs/public/defender/monitor-templates.png b/docs/public/defender/monitor-templates.png new file mode 100644 index 00000000..880c8a22 Binary files /dev/null and b/docs/public/defender/monitor-templates.png differ diff --git a/docs/public/defender/monitor.webm b/docs/public/defender/monitor.webm new file mode 100644 index 00000000..5af09550 Binary files /dev/null and b/docs/public/defender/monitor.webm differ diff --git a/docs/public/defender/monitors-sentinels-faq.png b/docs/public/defender/monitors-sentinels-faq.png new file mode 100644 index 00000000..f56eadf4 Binary files /dev/null and b/docs/public/defender/monitors-sentinels-faq.png differ diff --git a/docs/public/defender/notification-channel-setup-1.0.png b/docs/public/defender/notification-channel-setup-1.0.png new file mode 100644 index 00000000..5c4fc769 Binary files /dev/null and b/docs/public/defender/notification-channel-setup-1.0.png differ diff --git a/docs/public/defender/notification-channel-setup-2.0.png b/docs/public/defender/notification-channel-setup-2.0.png new file mode 100644 index 00000000..737e61ef Binary files /dev/null and b/docs/public/defender/notification-channel-setup-2.0.png differ diff --git a/docs/public/defender/proposal-migration-1.0.png b/docs/public/defender/proposal-migration-1.0.png new file mode 100644 index 00000000..56641a84 Binary files /dev/null and b/docs/public/defender/proposal-migration-1.0.png differ diff --git a/docs/public/defender/proposal-migration-2.0.png b/docs/public/defender/proposal-migration-2.0.png new file mode 100644 index 00000000..48d9dd6f Binary files /dev/null and b/docs/public/defender/proposal-migration-2.0.png differ diff --git a/docs/public/defender/proposal.webm b/docs/public/defender/proposal.webm new file mode 100644 index 00000000..aaf5461c Binary files /dev/null and b/docs/public/defender/proposal.webm differ diff --git a/docs/public/defender/relayer-mempool-visibility-check.png b/docs/public/defender/relayer-mempool-visibility-check.png new file mode 100644 index 00000000..118c6f5e Binary files /dev/null and b/docs/public/defender/relayer-mempool-visibility-check.png differ diff --git a/docs/public/defender/relayer-withdraw-screen.png b/docs/public/defender/relayer-withdraw-screen.png new file mode 100644 index 00000000..62a74a2b Binary files /dev/null and b/docs/public/defender/relayer-withdraw-screen.png differ diff --git a/docs/public/defender/relayer-withdraw.png b/docs/public/defender/relayer-withdraw.png new file mode 100644 index 00000000..de2d203e Binary files /dev/null and b/docs/public/defender/relayer-withdraw.png differ diff --git a/docs/public/defender/relayers-faq.png b/docs/public/defender/relayers-faq.png new file mode 100644 index 00000000..3ce0a62a Binary files /dev/null and b/docs/public/defender/relayers-faq.png differ diff --git a/docs/public/defender/relayers-migration-1.0.png b/docs/public/defender/relayers-migration-1.0.png new file mode 100644 index 00000000..43fa267e Binary files /dev/null and b/docs/public/defender/relayers-migration-1.0.png differ diff --git a/docs/public/defender/relayers-migration-2.0.png b/docs/public/defender/relayers-migration-2.0.png new file mode 100644 index 00000000..7f5119be Binary files /dev/null and b/docs/public/defender/relayers-migration-2.0.png differ diff --git a/docs/public/defender/relayers.webm b/docs/public/defender/relayers.webm new file mode 100644 index 00000000..529a9e15 Binary files /dev/null and b/docs/public/defender/relayers.webm differ diff --git a/docs/public/defender/remix-plugin-api-key.png b/docs/public/defender/remix-plugin-api-key.png new file mode 100644 index 00000000..76ab8a8a Binary files /dev/null and b/docs/public/defender/remix-plugin-api-key.png differ diff --git a/docs/public/defender/remix-plugin-approval-process.png b/docs/public/defender/remix-plugin-approval-process.png new file mode 100644 index 00000000..290fa5f3 Binary files /dev/null and b/docs/public/defender/remix-plugin-approval-process.png differ diff --git a/docs/public/defender/remix-plugin-deploy-completed.png b/docs/public/defender/remix-plugin-deploy-completed.png new file mode 100644 index 00000000..6eff5246 Binary files /dev/null and b/docs/public/defender/remix-plugin-deploy-completed.png differ diff --git a/docs/public/defender/remix-plugin-deploy-deterministic.png b/docs/public/defender/remix-plugin-deploy-deterministic.png new file mode 100644 index 00000000..22409470 Binary files /dev/null and b/docs/public/defender/remix-plugin-deploy-deterministic.png differ diff --git a/docs/public/defender/remix-plugin-deploy.png b/docs/public/defender/remix-plugin-deploy.png new file mode 100644 index 00000000..244af1dc Binary files /dev/null and b/docs/public/defender/remix-plugin-deploy.png differ diff --git a/docs/public/defender/remix-plugin-install.png b/docs/public/defender/remix-plugin-install.png new file mode 100644 index 00000000..09b832f3 Binary files /dev/null and b/docs/public/defender/remix-plugin-install.png differ diff --git a/docs/public/defender/remix-plugin-network.png b/docs/public/defender/remix-plugin-network.png new file mode 100644 index 00000000..c259712f Binary files /dev/null and b/docs/public/defender/remix-plugin-network.png differ diff --git a/docs/public/defender/remix-plugin-setup.png b/docs/public/defender/remix-plugin-setup.png new file mode 100644 index 00000000..684457ac Binary files /dev/null and b/docs/public/defender/remix-plugin-setup.png differ diff --git a/docs/public/defender/safe-migration-1.0.png b/docs/public/defender/safe-migration-1.0.png new file mode 100644 index 00000000..b9ebb409 Binary files /dev/null and b/docs/public/defender/safe-migration-1.0.png differ diff --git a/docs/public/defender/secrets-migration-1.0.png b/docs/public/defender/secrets-migration-1.0.png new file mode 100644 index 00000000..5565677a Binary files /dev/null and b/docs/public/defender/secrets-migration-1.0.png differ diff --git a/docs/public/defender/secrets-migration-2.0.png b/docs/public/defender/secrets-migration-2.0.png new file mode 100644 index 00000000..e1c0c8db Binary files /dev/null and b/docs/public/defender/secrets-migration-2.0.png differ diff --git a/docs/public/defender/sentinel-migration-1.0.png b/docs/public/defender/sentinel-migration-1.0.png new file mode 100644 index 00000000..e54581de Binary files /dev/null and b/docs/public/defender/sentinel-migration-1.0.png differ diff --git a/docs/public/defender/switch-back-faq.png b/docs/public/defender/switch-back-faq.png new file mode 100644 index 00000000..fcedf4cf Binary files /dev/null and b/docs/public/defender/switch-back-faq.png differ diff --git a/docs/public/defender/tenant-migration-1.0.png b/docs/public/defender/tenant-migration-1.0.png new file mode 100644 index 00000000..62d86ccb Binary files /dev/null and b/docs/public/defender/tenant-migration-1.0.png differ diff --git a/docs/public/defender/tenant-migration-2.0.png b/docs/public/defender/tenant-migration-2.0.png new file mode 100644 index 00000000..b76c5800 Binary files /dev/null and b/docs/public/defender/tenant-migration-2.0.png differ diff --git a/docs/public/defender/timelock-migration-1.0.png b/docs/public/defender/timelock-migration-1.0.png new file mode 100644 index 00000000..484bfe30 Binary files /dev/null and b/docs/public/defender/timelock-migration-1.0.png differ diff --git a/docs/public/defender/transaction-proposals-faq.png b/docs/public/defender/transaction-proposals-faq.png new file mode 100644 index 00000000..234363d9 Binary files /dev/null and b/docs/public/defender/transaction-proposals-faq.png differ diff --git a/docs/public/defender/tutorial-access-control-add.gif b/docs/public/defender/tutorial-access-control-add.gif new file mode 100644 index 00000000..002020b7 Binary files /dev/null and b/docs/public/defender/tutorial-access-control-add.gif differ diff --git a/docs/public/defender/tutorial-access-control-copy-address.png b/docs/public/defender/tutorial-access-control-copy-address.png new file mode 100644 index 00000000..6382d6a8 Binary files /dev/null and b/docs/public/defender/tutorial-access-control-copy-address.png differ diff --git a/docs/public/defender/tutorial-access-control-factory.png b/docs/public/defender/tutorial-access-control-factory.png new file mode 100644 index 00000000..98f7f5ed Binary files /dev/null and b/docs/public/defender/tutorial-access-control-factory.png differ diff --git a/docs/public/defender/tutorial-access-control-page.gif b/docs/public/defender/tutorial-access-control-page.gif new file mode 100644 index 00000000..ee62c005 Binary files /dev/null and b/docs/public/defender/tutorial-access-control-page.gif differ diff --git a/docs/public/defender/tutorial-access-control-submit-proposal.gif b/docs/public/defender/tutorial-access-control-submit-proposal.gif new file mode 100644 index 00000000..352e5b3d Binary files /dev/null and b/docs/public/defender/tutorial-access-control-submit-proposal.gif differ diff --git a/docs/public/defender/tutorial-access-control-submit-tx.gif b/docs/public/defender/tutorial-access-control-submit-tx.gif new file mode 100644 index 00000000..3577a2ad Binary files /dev/null and b/docs/public/defender/tutorial-access-control-submit-tx.gif differ diff --git a/docs/public/defender/tutorial-access-control-tx-general.png b/docs/public/defender/tutorial-access-control-tx-general.png new file mode 100644 index 00000000..40c9c04f Binary files /dev/null and b/docs/public/defender/tutorial-access-control-tx-general.png differ diff --git a/docs/public/defender/tutorial-actions-action.png b/docs/public/defender/tutorial-actions-action.png new file mode 100644 index 00000000..0427fde7 Binary files /dev/null and b/docs/public/defender/tutorial-actions-action.png differ diff --git a/docs/public/defender/tutorial-actions-alert.png b/docs/public/defender/tutorial-actions-alert.png new file mode 100644 index 00000000..de55d4d2 Binary files /dev/null and b/docs/public/defender/tutorial-actions-alert.png differ diff --git a/docs/public/defender/tutorial-deploy-block-explorer.png b/docs/public/defender/tutorial-deploy-block-explorer.png new file mode 100644 index 00000000..29041be4 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-block-explorer.png differ diff --git a/docs/public/defender/tutorial-deploy-contract.png b/docs/public/defender/tutorial-deploy-contract.png new file mode 100644 index 00000000..2b9c99a3 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-contract.png differ diff --git a/docs/public/defender/tutorial-deploy-copy-relayer.png b/docs/public/defender/tutorial-deploy-copy-relayer.png new file mode 100644 index 00000000..e47b88e0 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-copy-relayer.png differ diff --git a/docs/public/defender/tutorial-deploy-directory.png b/docs/public/defender/tutorial-deploy-directory.png new file mode 100644 index 00000000..9033fa6d Binary files /dev/null and b/docs/public/defender/tutorial-deploy-directory.png differ diff --git a/docs/public/defender/tutorial-deploy-end-wizard.png b/docs/public/defender/tutorial-deploy-end-wizard.png new file mode 100644 index 00000000..b9d818fa Binary files /dev/null and b/docs/public/defender/tutorial-deploy-end-wizard.png differ diff --git a/docs/public/defender/tutorial-deploy-environments.png b/docs/public/defender/tutorial-deploy-environments.png new file mode 100644 index 00000000..5bce0b38 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-environments.png differ diff --git a/docs/public/defender/tutorial-deploy-executed-upgrade.png b/docs/public/defender/tutorial-deploy-executed-upgrade.png new file mode 100644 index 00000000..19434017 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-executed-upgrade.png differ diff --git a/docs/public/defender/tutorial-deploy-networks.png b/docs/public/defender/tutorial-deploy-networks.png new file mode 100644 index 00000000..68fde15f Binary files /dev/null and b/docs/public/defender/tutorial-deploy-networks.png differ diff --git a/docs/public/defender/tutorial-deploy-relayer-wizard.png b/docs/public/defender/tutorial-deploy-relayer-wizard.png new file mode 100644 index 00000000..f41db288 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-relayer-wizard.png differ diff --git a/docs/public/defender/tutorial-deploy-safe.png b/docs/public/defender/tutorial-deploy-safe.png new file mode 100644 index 00000000..d1f99bb7 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-safe.png differ diff --git a/docs/public/defender/tutorial-deploy-step1-wizard.png b/docs/public/defender/tutorial-deploy-step1-wizard.png new file mode 100644 index 00000000..0354542f Binary files /dev/null and b/docs/public/defender/tutorial-deploy-step1-wizard.png differ diff --git a/docs/public/defender/tutorial-deploy-step2-wizard.png b/docs/public/defender/tutorial-deploy-step2-wizard.png new file mode 100644 index 00000000..cbaf0200 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-step2-wizard.png differ diff --git a/docs/public/defender/tutorial-deploy-step3-wizard.png b/docs/public/defender/tutorial-deploy-step3-wizard.png new file mode 100644 index 00000000..266717e1 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-step3-wizard.png differ diff --git a/docs/public/defender/tutorial-deploy-step4-wizard.png b/docs/public/defender/tutorial-deploy-step4-wizard.png new file mode 100644 index 00000000..e9499152 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-step4-wizard.png differ diff --git a/docs/public/defender/tutorial-deploy-upgrade-wizard.png b/docs/public/defender/tutorial-deploy-upgrade-wizard.png new file mode 100644 index 00000000..a316dcf1 Binary files /dev/null and b/docs/public/defender/tutorial-deploy-upgrade-wizard.png differ diff --git a/docs/public/defender/tutorial-forked-network-phalcon-create.png b/docs/public/defender/tutorial-forked-network-phalcon-create.png new file mode 100644 index 00000000..f0a52763 Binary files /dev/null and b/docs/public/defender/tutorial-forked-network-phalcon-create.png differ diff --git a/docs/public/defender/tutorial-forked-networks-create.png b/docs/public/defender/tutorial-forked-networks-create.png new file mode 100644 index 00000000..c97a9709 Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-create.png differ diff --git a/docs/public/defender/tutorial-forked-networks-deploy-intro.png b/docs/public/defender/tutorial-forked-networks-deploy-intro.png new file mode 100644 index 00000000..3578ddd0 Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-deploy-intro.png differ diff --git a/docs/public/defender/tutorial-forked-networks-deploy-wizard-step1.png b/docs/public/defender/tutorial-forked-networks-deploy-wizard-step1.png new file mode 100644 index 00000000..c881416e Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-deploy-wizard-step1.png differ diff --git a/docs/public/defender/tutorial-forked-networks-deploy-wizard-step2.png b/docs/public/defender/tutorial-forked-networks-deploy-wizard-step2.png new file mode 100644 index 00000000..2424d935 Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-deploy-wizard-step2.png differ diff --git a/docs/public/defender/tutorial-forked-networks-deploy-wizard-step3.png b/docs/public/defender/tutorial-forked-networks-deploy-wizard-step3.png new file mode 100644 index 00000000..3d4a5363 Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-deploy-wizard-step3.png differ diff --git a/docs/public/defender/tutorial-forked-networks-intro.png b/docs/public/defender/tutorial-forked-networks-intro.png new file mode 100644 index 00000000..f3696547 Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-intro.png differ diff --git a/docs/public/defender/tutorial-forked-networks-phalcon-dashboard.png b/docs/public/defender/tutorial-forked-networks-phalcon-dashboard.png new file mode 100644 index 00000000..df86c821 Binary files /dev/null and b/docs/public/defender/tutorial-forked-networks-phalcon-dashboard.png differ diff --git a/docs/public/defender/tutorial-ir-etherscan.png b/docs/public/defender/tutorial-ir-etherscan.png new file mode 100644 index 00000000..df4c7b21 Binary files /dev/null and b/docs/public/defender/tutorial-ir-etherscan.png differ diff --git a/docs/public/defender/tutorial-ir-first-monitor.png b/docs/public/defender/tutorial-ir-first-monitor.png new file mode 100644 index 00000000..bd24b479 Binary files /dev/null and b/docs/public/defender/tutorial-ir-first-monitor.png differ diff --git a/docs/public/defender/tutorial-ir-monitor.png b/docs/public/defender/tutorial-ir-monitor.png new file mode 100644 index 00000000..b633ffee Binary files /dev/null and b/docs/public/defender/tutorial-ir-monitor.png differ diff --git a/docs/public/defender/tutorial-ir-proposal-action.png b/docs/public/defender/tutorial-ir-proposal-action.png new file mode 100644 index 00000000..da17d06d Binary files /dev/null and b/docs/public/defender/tutorial-ir-proposal-action.png differ diff --git a/docs/public/defender/tutorial-monitor-alerts.png b/docs/public/defender/tutorial-monitor-alerts.png new file mode 100644 index 00000000..22a40134 Binary files /dev/null and b/docs/public/defender/tutorial-monitor-alerts.png differ diff --git a/docs/public/defender/tutorial-monitor-card.png b/docs/public/defender/tutorial-monitor-card.png new file mode 100644 index 00000000..0ce119aa Binary files /dev/null and b/docs/public/defender/tutorial-monitor-card.png differ diff --git a/docs/public/defender/tutorial-monitor-event-filter.png b/docs/public/defender/tutorial-monitor-event-filter.png new file mode 100644 index 00000000..a1c77ffc Binary files /dev/null and b/docs/public/defender/tutorial-monitor-event-filter.png differ diff --git a/docs/public/defender/tutorial-monitor-first.png b/docs/public/defender/tutorial-monitor-first.png new file mode 100644 index 00000000..fddae806 Binary files /dev/null and b/docs/public/defender/tutorial-monitor-first.png differ diff --git a/docs/public/defender/tutorial-monitor-landing.png b/docs/public/defender/tutorial-monitor-landing.png new file mode 100644 index 00000000..8ed5609b Binary files /dev/null and b/docs/public/defender/tutorial-monitor-landing.png differ diff --git a/docs/public/defender/tutorial-monitor-receive.png b/docs/public/defender/tutorial-monitor-receive.png new file mode 100644 index 00000000..0edda75c Binary files /dev/null and b/docs/public/defender/tutorial-monitor-receive.png differ diff --git a/docs/public/defender/tutorial-monitor-save-template.png b/docs/public/defender/tutorial-monitor-save-template.png new file mode 100644 index 00000000..db1b333a Binary files /dev/null and b/docs/public/defender/tutorial-monitor-save-template.png differ diff --git a/docs/public/defender/tutorial-monitor-transaction-filters.png b/docs/public/defender/tutorial-monitor-transaction-filters.png new file mode 100644 index 00000000..e206a97a Binary files /dev/null and b/docs/public/defender/tutorial-monitor-transaction-filters.png differ diff --git a/docs/public/defender/tutorial-relayer-step1.png b/docs/public/defender/tutorial-relayer-step1.png new file mode 100644 index 00000000..4ab83a28 Binary files /dev/null and b/docs/public/defender/tutorial-relayer-step1.png differ diff --git a/docs/public/defender/tutorial-relayer-step2.png b/docs/public/defender/tutorial-relayer-step2.png new file mode 100644 index 00000000..79d90791 Binary files /dev/null and b/docs/public/defender/tutorial-relayer-step2.png differ diff --git a/docs/public/defender/tutorial-relayer-step3-1.png b/docs/public/defender/tutorial-relayer-step3-1.png new file mode 100644 index 00000000..25dc2484 Binary files /dev/null and b/docs/public/defender/tutorial-relayer-step3-1.png differ diff --git a/docs/public/defender/tutorial-relayer-step3.png b/docs/public/defender/tutorial-relayer-step3.png new file mode 100644 index 00000000..6f40138f Binary files /dev/null and b/docs/public/defender/tutorial-relayer-step3.png differ diff --git a/docs/public/defender/tutorial-relayer-step4.png b/docs/public/defender/tutorial-relayer-step4.png new file mode 100644 index 00000000..6a2bfce4 Binary files /dev/null and b/docs/public/defender/tutorial-relayer-step4.png differ diff --git a/docs/public/defender/tutorial-workflow-active-scenario.png b/docs/public/defender/tutorial-workflow-active-scenario.png new file mode 100644 index 00000000..6a7c99d6 Binary files /dev/null and b/docs/public/defender/tutorial-workflow-active-scenario.png differ diff --git a/docs/public/defender/tutorial-workflow-first-action.png b/docs/public/defender/tutorial-workflow-first-action.png new file mode 100644 index 00000000..a0a3e152 Binary files /dev/null and b/docs/public/defender/tutorial-workflow-first-action.png differ diff --git a/docs/public/defender/tutorial-workflow-scenario.png b/docs/public/defender/tutorial-workflow-scenario.png new file mode 100644 index 00000000..482334c3 Binary files /dev/null and b/docs/public/defender/tutorial-workflow-scenario.png differ diff --git a/docs/public/defender/wizard-deploy-further-steps.png b/docs/public/defender/wizard-deploy-further-steps.png new file mode 100644 index 00000000..297c2d66 Binary files /dev/null and b/docs/public/defender/wizard-deploy-further-steps.png differ diff --git a/docs/public/defender/wizard-plugin-approval-process.png b/docs/public/defender/wizard-plugin-approval-process.png new file mode 100644 index 00000000..1088bb20 Binary files /dev/null and b/docs/public/defender/wizard-plugin-approval-process.png differ diff --git a/docs/public/defender/wizard-plugin-configure-2.png b/docs/public/defender/wizard-plugin-configure-2.png new file mode 100644 index 00000000..cda0b276 Binary files /dev/null and b/docs/public/defender/wizard-plugin-configure-2.png differ diff --git a/docs/public/defender/wizard-plugin-configure.png b/docs/public/defender/wizard-plugin-configure.png new file mode 100644 index 00000000..93af80dd Binary files /dev/null and b/docs/public/defender/wizard-plugin-configure.png differ diff --git a/docs/public/defender/wizard-plugin-deploy.png b/docs/public/defender/wizard-plugin-deploy.png new file mode 100644 index 00000000..229c5153 Binary files /dev/null and b/docs/public/defender/wizard-plugin-deploy.png differ diff --git a/docs/public/defender/wizard-plugin-deterministic.png b/docs/public/defender/wizard-plugin-deterministic.png new file mode 100644 index 00000000..00bf3388 Binary files /dev/null and b/docs/public/defender/wizard-plugin-deterministic.png differ diff --git a/docs/public/defender/wizard-plugin-network-2.png b/docs/public/defender/wizard-plugin-network-2.png new file mode 100644 index 00000000..6882cb49 Binary files /dev/null and b/docs/public/defender/wizard-plugin-network-2.png differ diff --git a/docs/public/defender/wizard-plugin-start.png b/docs/public/defender/wizard-plugin-start.png new file mode 100644 index 00000000..0a14a2ed Binary files /dev/null and b/docs/public/defender/wizard-plugin-start.png differ diff --git a/docs/public/erc4626-attack copy.png b/docs/public/erc4626-attack copy.png new file mode 100644 index 00000000..dc059b22 Binary files /dev/null and b/docs/public/erc4626-attack copy.png differ diff --git a/docs/public/erc4626-attack-3a copy.png b/docs/public/erc4626-attack-3a copy.png new file mode 100644 index 00000000..4cb52237 Binary files /dev/null and b/docs/public/erc4626-attack-3a copy.png differ diff --git a/docs/public/erc4626-attack-3a.png b/docs/public/erc4626-attack-3a.png new file mode 100644 index 00000000..4cb52237 Binary files /dev/null and b/docs/public/erc4626-attack-3a.png differ diff --git a/docs/public/erc4626-attack-3b copy.png b/docs/public/erc4626-attack-3b copy.png new file mode 100644 index 00000000..3dc5256b Binary files /dev/null and b/docs/public/erc4626-attack-3b copy.png differ diff --git a/docs/public/erc4626-attack-3b.png b/docs/public/erc4626-attack-3b.png new file mode 100644 index 00000000..3dc5256b Binary files /dev/null and b/docs/public/erc4626-attack-3b.png differ diff --git a/docs/public/erc4626-attack-6 copy.png b/docs/public/erc4626-attack-6 copy.png new file mode 100644 index 00000000..1587fb5c Binary files /dev/null and b/docs/public/erc4626-attack-6 copy.png differ diff --git a/docs/public/erc4626-attack-6.png b/docs/public/erc4626-attack-6.png new file mode 100644 index 00000000..1587fb5c Binary files /dev/null and b/docs/public/erc4626-attack-6.png differ diff --git a/docs/public/erc4626-attack.png b/docs/public/erc4626-attack.png new file mode 100644 index 00000000..dc059b22 Binary files /dev/null and b/docs/public/erc4626-attack.png differ diff --git a/docs/public/erc4626-deposit copy.png b/docs/public/erc4626-deposit copy.png new file mode 100644 index 00000000..b6c75e67 Binary files /dev/null and b/docs/public/erc4626-deposit copy.png differ diff --git a/docs/public/erc4626-deposit.png b/docs/public/erc4626-deposit.png new file mode 100644 index 00000000..b6c75e67 Binary files /dev/null and b/docs/public/erc4626-deposit.png differ diff --git a/docs/public/erc4626-mint copy.png b/docs/public/erc4626-mint copy.png new file mode 100644 index 00000000..f89ab900 Binary files /dev/null and b/docs/public/erc4626-mint copy.png differ diff --git a/docs/public/erc4626-mint.png b/docs/public/erc4626-mint.png new file mode 100644 index 00000000..f89ab900 Binary files /dev/null and b/docs/public/erc4626-mint.png differ diff --git a/docs/public/erc4626-rate-linear copy.png b/docs/public/erc4626-rate-linear copy.png new file mode 100644 index 00000000..09e8045e Binary files /dev/null and b/docs/public/erc4626-rate-linear copy.png differ diff --git a/docs/public/erc4626-rate-linear.png b/docs/public/erc4626-rate-linear.png new file mode 100644 index 00000000..09e8045e Binary files /dev/null and b/docs/public/erc4626-rate-linear.png differ diff --git a/docs/public/erc4626-rate-loglog copy.png b/docs/public/erc4626-rate-loglog copy.png new file mode 100644 index 00000000..4eb19efe Binary files /dev/null and b/docs/public/erc4626-rate-loglog copy.png differ diff --git a/docs/public/erc4626-rate-loglog.png b/docs/public/erc4626-rate-loglog.png new file mode 100644 index 00000000..4eb19efe Binary files /dev/null and b/docs/public/erc4626-rate-loglog.png differ diff --git a/docs/public/erc4626-rate-loglogext copy.png b/docs/public/erc4626-rate-loglogext copy.png new file mode 100644 index 00000000..127bc7f2 Binary files /dev/null and b/docs/public/erc4626-rate-loglogext copy.png differ diff --git a/docs/public/erc4626-rate-loglogext.png b/docs/public/erc4626-rate-loglogext.png new file mode 100644 index 00000000..127bc7f2 Binary files /dev/null and b/docs/public/erc4626-rate-loglogext.png differ diff --git a/docs/public/favicon-16x16.png b/docs/public/favicon-16x16.png new file mode 100644 index 00000000..c6e4bd81 Binary files /dev/null and b/docs/public/favicon-16x16.png differ diff --git a/docs/public/favicon-32x32.png b/docs/public/favicon-32x32.png new file mode 100644 index 00000000..d8b5a429 Binary files /dev/null and b/docs/public/favicon-32x32.png differ diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 00000000..787a06bb Binary files /dev/null and b/docs/public/favicon.ico differ diff --git a/docs/public/node-success.png b/docs/public/node-success.png new file mode 100644 index 00000000..fc29df1b Binary files /dev/null and b/docs/public/node-success.png differ diff --git a/docs/public/register-parachain.png b/docs/public/register-parachain.png new file mode 100644 index 00000000..eb031b7c Binary files /dev/null and b/docs/public/register-parachain.png differ diff --git a/docs/public/schemas/openzeppelin-relayer-openapi.json b/docs/public/schemas/openzeppelin-relayer-openapi.json new file mode 100644 index 00000000..3f2aac91 --- /dev/null +++ b/docs/public/schemas/openzeppelin-relayer-openapi.json @@ -0,0 +1,7416 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "OpenZeppelin Relayer API", + "description": "OpenZeppelin Relayer API", + "termsOfService": "https://www.openzeppelin.com/tos", + "contact": { + "name": "OpenZeppelin", + "url": "https://www.openzeppelin.com" + }, + "license": { + "name": "AGPL-3.0 license", + "url": "https://github.com/OpenZeppelin/openzeppelin-relayer/blob/main/LICENSE" + }, + "version": "1.0.0" + }, + "paths": { + "/api/v1/notifications": { + "get": { + "tags": [ + "Notifications" + ], + "summary": "Notification routes implementation", + "description": "Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file\n\nLists all notifications with pagination support.", + "operationId": "listNotifications", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for pagination (starts at 1)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "per_page", + "in": "query", + "description": "Number of items per page (default: 10)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Notification list retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_Vec_NotificationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "Notifications" + ], + "summary": "Creates a new notification.", + "operationId": "createNotification", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Notification created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NotificationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "409": { + "description": "Notification with this ID already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Notification with this ID already exists", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/notifications/{notification_id}": { + "get": { + "tags": [ + "Notifications" + ], + "summary": "Retrieves details of a specific notification by ID.", + "operationId": "getNotification", + "parameters": [ + { + "name": "notification_id", + "in": "path", + "description": "Notification ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Notification retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NotificationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Notification not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Notification not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "Notifications" + ], + "summary": "Deletes a notification by ID.", + "operationId": "deleteNotification", + "parameters": [ + { + "name": "notification_id", + "in": "path", + "description": "Notification ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Notification deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": "Notification deleted successfully", + "message": "Notification deleted successfully", + "success": true + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Notification not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Notification not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "Notifications" + ], + "summary": "Updates an existing notification.", + "operationId": "updateNotification", + "parameters": [ + { + "name": "notification_id", + "in": "path", + "description": "Notification ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NotificationUpdateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Notification updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_NotificationResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Notification not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Notification not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/plugins/{plugin_id}/call": { + "post": { + "tags": [ + "Plugins" + ], + "summary": "Calls a plugin method.", + "operationId": "callPlugin", + "parameters": [ + { + "name": "plugin_id", + "in": "path", + "description": "The unique identifier of the plugin", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PluginCallRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Plugin call successful", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_PluginCallResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Plugin with ID plugin_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Relayer routes implementation", + "description": "Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file\n\nLists all relayers with pagination support.", + "operationId": "listRelayers", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for pagination (starts at 1)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "per_page", + "in": "query", + "description": "Number of items per page (default: 10)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Relayer list retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_Vec_RelayerResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "Relayers" + ], + "summary": "Creates a new relayer.", + "operationId": "createRelayer", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRelayerRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Relayer created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RelayerResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "409": { + "description": "Relayer with this ID already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with this ID already exists", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Retrieves details of a specific relayer by ID.", + "operationId": "getRelayer", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer details retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RelayerResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "Relayers" + ], + "summary": "Deletes a relayer by ID.", + "operationId": "deleteRelayer", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + } + } + } + }, + "400": { + "description": "Bad Request - Cannot delete relayer with active transactions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Cannot delete relayer 'relayer_id' because it has N transaction(s). Please wait for all transactions to complete or cancel them before deleting the relayer.", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "Relayers" + ], + "summary": "Updates a relayer's information based on the provided update request.", + "operationId": "updateRelayer", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateRelayerRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Relayer updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RelayerResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/balance": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Retrieves the balance of a specific relayer.", + "operationId": "getRelayerBalance", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer balance retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_BalanceResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/rpc": { + "post": { + "tags": [ + "Relayers" + ], + "summary": "Performs a JSON-RPC call using the specified relayer.", + "operationId": "rpc", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "JSON-RPC request with method and parameters", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonRpcRequest_NetworkRpcRequest" + }, + "example": { + "id": 1, + "jsonrpc": "2.0", + "method": "feeEstimate", + "params": { + "fee_token": "SOL", + "network": "solana", + "transaction": "base64_encoded_transaction" + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "RPC method executed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/JsonRpcResponse_NetworkRpcResult" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/sign": { + "post": { + "tags": [ + "Relayers" + ], + "summary": "Signs data using the specified relayer.", + "operationId": "sign", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignDataRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Relayer signed data successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SignDataResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/sign-transaction": { + "post": { + "tags": [ + "Relayers" + ], + "summary": "Signs a transaction using the specified relayer (Stellar only).", + "operationId": "signTransaction", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignTransactionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Transaction signed successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SignTransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/sign-typed-data": { + "post": { + "tags": [ + "Relayers" + ], + "summary": "Signs typed data using the specified relayer.", + "operationId": "signTypedData", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignTypedDataRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Relayer signed typed data successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SignDataResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/status": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Fetches the current status of a specific relayer.", + "operationId": "getRelayerStatus", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer status retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_RelayerStatus" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/transactions": { + "post": { + "tags": [ + "Relayers" + ], + "summary": "Sends a transaction through the specified relayer.", + "operationId": "sendTransaction", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTransactionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Relayer transactions sent successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/transactions/": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Lists all transactions for a specific relayer with pagination.", + "operationId": "listTransactions", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Page number for pagination (starts at 1)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "per_page", + "in": "query", + "description": "Number of items per page (default: 10)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Relayer transactions retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_Vec_TransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/transactions/by-nonce/{nonce}": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Retrieves a transaction by its nonce value.", + "operationId": "getTransactionByNonce", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "nonce", + "in": "path", + "description": "The nonce of the transaction", + "required": true, + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Relayer transaction retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/transactions/pending": { + "delete": { + "tags": [ + "Relayers" + ], + "summary": "Deletes all pending transactions for a specific relayer.", + "operationId": "deletePendingTransactions", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer pending transactions successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_DeletePendingTransactionsResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Relayer with ID relayer_id not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/relayers/{relayer_id}/transactions/{transaction_id}": { + "get": { + "tags": [ + "Relayers" + ], + "summary": "Retrieves a specific transaction by its ID.", + "operationId": "getTransactionById", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "transaction_id", + "in": "path", + "description": "The unique identifier of the transaction", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer transaction retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Not Found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "put": { + "tags": [ + "Relayers" + ], + "summary": "Replaces a specific transaction with a new one.", + "operationId": "replaceTransaction", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "transaction_id", + "in": "path", + "description": "The unique identifier of the transaction", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/NetworkTransactionRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Relayer transaction replaced successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "Relayers" + ], + "summary": "Cancels a specific transaction by its ID.", + "operationId": "cancelTransaction", + "parameters": [ + { + "name": "relayer_id", + "in": "path", + "description": "The unique identifier of the relayer", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "transaction_id", + "in": "path", + "description": "The unique identifier of the transaction", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Relayer transaction canceled successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_TransactionResponse" + } + } + } + }, + "400": { + "description": "BadRequest", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Not found", + "success": false + } + } + } + }, + "429": { + "description": "Too Many Requests", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Too Many Requests", + "success": false + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/signers": { + "get": { + "tags": [ + "Signers" + ], + "summary": "Signer routes implementation", + "description": "Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file\n\nLists all signers with pagination support.", + "operationId": "listSigners", + "parameters": [ + { + "name": "page", + "in": "query", + "description": "Page number for pagination (starts at 1)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + }, + { + "name": "per_page", + "in": "query", + "description": "Number of items per page (default: 10)", + "required": false, + "schema": { + "type": "integer", + "minimum": 0 + } + } + ], + "responses": { + "200": { + "description": "Signer list retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_Vec_SignerResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "post": { + "tags": [ + "Signers" + ], + "summary": "Creates a new signer.", + "operationId": "createSigner", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignerCreateRequest" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Signer created successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SignerResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "409": { + "description": "Signer with this ID already exists", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Signer with this ID already exists", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/api/v1/signers/{signer_id}": { + "get": { + "tags": [ + "Signers" + ], + "summary": "Retrieves details of a specific signer by ID.", + "operationId": "getSigner", + "parameters": [ + { + "name": "signer_id", + "in": "path", + "description": "Signer ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Signer retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SignerResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Signer not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Signer not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "delete": { + "tags": [ + "Signers" + ], + "summary": "Deletes a signer by ID.", + "operationId": "deleteSigner", + "parameters": [ + { + "name": "signer_id", + "in": "path", + "description": "Signer ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Signer deleted successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": "Signer deleted successfully", + "message": "Signer deleted successfully", + "success": true + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Signer not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Signer not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + }, + "patch": { + "tags": [ + "Signers" + ], + "summary": "Updates an existing signer.", + "operationId": "updateSigner", + "parameters": [ + { + "name": "signer_id", + "in": "path", + "description": "Signer ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SignerUpdateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Signer updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_SignerResponse" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Bad Request", + "success": false + } + } + } + }, + "401": { + "description": "Unauthorized", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Unauthorized", + "success": false + } + } + } + }, + "404": { + "description": "Signer not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Signer not found", + "success": false + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse_String" + }, + "example": { + "data": null, + "message": "Internal Server Error", + "success": false + } + } + } + } + }, + "security": [ + { + "bearer_auth": [] + } + ] + } + }, + "/debug/metrics/scrape": { + "get": { + "tags": [ + "Metrics" + ], + "summary": "Triggers an update of system metrics and returns the result in plain text format.", + "description": "# Returns\n\nAn `HttpResponse` containing the updated metrics in plain text, or an error message if the\nupdate fails.", + "operationId": "scrape_metrics", + "responses": { + "200": { + "description": "Complete metrics in Prometheus exposition format", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/metrics": { + "get": { + "tags": [ + "Metrics" + ], + "summary": "Metrics routes implementation", + "description": "Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file\nReturns a list of all available metric names in JSON format.\n\n# Returns\n\nAn `HttpResponse` containing a JSON array of metric names.", + "operationId": "list_metrics", + "responses": { + "200": { + "description": "Metric names list", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "401": { + "description": "Unauthorized" + } + } + } + }, + "/metrics/{metric_name}": { + "get": { + "tags": [ + "Metrics" + ], + "summary": "Returns the details of a specific metric in plain text format.", + "description": "# Parameters\n\n- `path`: The name of the metric to retrieve details for.\n\n# Returns\n\nAn `HttpResponse` containing the metric details in plain text, or a 404 error if the metric is\nnot found.", + "operationId": "metric_detail", + "parameters": [ + { + "name": "metric_name", + "in": "path", + "description": "Name of the metric to retrieve, e.g. utopia_transactions_total", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Metric details in Prometheus text format", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized - missing or invalid API key" + }, + "403": { + "description": "Forbidden - insufficient permissions to access this metric" + }, + "404": { + "description": "Metric not found" + }, + "429": { + "description": "Too many requests - rate limit for metrics access exceeded" + } + }, + "security": [ + { + "bearer_auth": [ + "metrics:read" + ] + } + ] + } + }, + "/v1/health": { + "get": { + "tags": [ + "Health" + ], + "summary": "Health routes implementation", + "description": "Note: OpenAPI documentation for these endpoints can be found in the `openapi.rs` file\nHandles the `/health` endpoint.\n\nReturns an `HttpResponse` with a status of `200 OK` and a body of `\"OK\"`.", + "operationId": "health", + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + }, + "500": { + "description": "Internal server error", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "ApiResponse_BalanceResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "balance", + "unit" + ], + "properties": { + "balance": { + "type": "integer", + "minimum": 0 + }, + "unit": { + "type": "string", + "example": "wei" + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_DeletePendingTransactionsResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "object", + "description": "Response for delete pending transactions operation", + "required": [ + "queued_for_cancellation_transaction_ids", + "failed_to_queue_transaction_ids", + "total_processed" + ], + "properties": { + "failed_to_queue_transaction_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "queued_for_cancellation_transaction_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "total_processed": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_NotificationResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "object", + "description": "Response structure for notification API endpoints", + "required": [ + "id", + "type", + "url", + "has_signing_key" + ], + "properties": { + "has_signing_key": { + "type": "boolean", + "description": "Signing key is hidden in responses for security" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" + }, + "url": { + "type": "string" + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_PluginCallResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "success", + "return_value", + "message", + "logs", + "error", + "traces" + ], + "properties": { + "error": { + "type": "string" + }, + "logs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LogEntry" + } + }, + "message": { + "type": "string" + }, + "return_value": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "traces": { + "type": "array", + "items": {} + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_RelayerResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "object", + "description": "Relayer response model for API endpoints", + "required": [ + "id", + "name", + "network", + "network_type", + "paused", + "signer_id" + ], + "properties": { + "address": { + "type": "string" + }, + "custom_rpc_urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RpcConfig" + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "network": { + "type": "string" + }, + "network_type": { + "$ref": "#/components/schemas/RelayerNetworkType" + }, + "notification_id": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "policies": { + "$ref": "#/components/schemas/RelayerNetworkPolicyResponse", + "description": "Policies without redundant network_type tag - network type is available at top level\nOnly included if user explicitly provided policies (not shown for empty/default policies)" + }, + "signer_id": { + "type": "string" + }, + "system_disabled": { + "type": "boolean" + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_RelayerStatus": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "oneOf": [ + { + "type": "object", + "required": [ + "balance", + "pending_transactions_count", + "system_disabled", + "paused", + "nonce", + "network_type" + ], + "properties": { + "balance": { + "type": "string" + }, + "last_confirmed_transaction_timestamp": { + "type": [ + "string", + "null" + ] + }, + "network_type": { + "type": "string", + "enum": [ + "evm" + ] + }, + "nonce": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "pending_transactions_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "system_disabled": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "balance", + "pending_transactions_count", + "system_disabled", + "paused", + "sequence_number", + "network_type" + ], + "properties": { + "balance": { + "type": "string" + }, + "last_confirmed_transaction_timestamp": { + "type": [ + "string", + "null" + ] + }, + "network_type": { + "type": "string", + "enum": [ + "stellar" + ] + }, + "paused": { + "type": "boolean" + }, + "pending_transactions_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "sequence_number": { + "type": "string" + }, + "system_disabled": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "balance", + "pending_transactions_count", + "system_disabled", + "paused", + "network_type" + ], + "properties": { + "balance": { + "type": "string" + }, + "last_confirmed_transaction_timestamp": { + "type": [ + "string", + "null" + ] + }, + "network_type": { + "type": "string", + "enum": [ + "solana" + ] + }, + "paused": { + "type": "boolean" + }, + "pending_transactions_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "system_disabled": { + "type": "boolean" + } + } + } + ], + "description": "Relayer status with runtime information" + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_SignDataResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/SignDataResponseEvm" + }, + { + "$ref": "#/components/schemas/SignDataResponseSolana" + } + ] + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_SignTransactionResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/SignTransactionResponseStellar" + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + ] + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_SignerResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "object", + "required": [ + "id", + "type", + "config" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/SignerConfigResponse", + "description": "Non-secret configuration details" + }, + "id": { + "type": "string", + "description": "The unique identifier of the signer" + }, + "type": { + "$ref": "#/components/schemas/SignerType", + "description": "The type of signer (local, aws_kms, google_cloud_kms, vault, etc.)" + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_String": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "string" + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_TransactionResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "oneOf": [ + { + "$ref": "#/components/schemas/EvmTransactionResponse" + }, + { + "$ref": "#/components/schemas/SolanaTransactionResponse" + }, + { + "$ref": "#/components/schemas/StellarTransactionResponse" + } + ] + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_Vec_NotificationResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "description": "Response structure for notification API endpoints", + "required": [ + "id", + "type", + "url", + "has_signing_key" + ], + "properties": { + "has_signing_key": { + "type": "boolean", + "description": "Signing key is hidden in responses for security" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" + }, + "url": { + "type": "string" + } + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_Vec_RelayerResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "description": "Relayer response model for API endpoints", + "required": [ + "id", + "name", + "network", + "network_type", + "paused", + "signer_id" + ], + "properties": { + "address": { + "type": "string" + }, + "custom_rpc_urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RpcConfig" + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "network": { + "type": "string" + }, + "network_type": { + "$ref": "#/components/schemas/RelayerNetworkType" + }, + "notification_id": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "policies": { + "$ref": "#/components/schemas/RelayerNetworkPolicyResponse", + "description": "Policies without redundant network_type tag - network type is available at top level\nOnly included if user explicitly provided policies (not shown for empty/default policies)" + }, + "signer_id": { + "type": "string" + }, + "system_disabled": { + "type": "boolean" + } + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_Vec_SignerResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "required": [ + "id", + "type", + "config" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/SignerConfigResponse", + "description": "Non-secret configuration details" + }, + "id": { + "type": "string", + "description": "The unique identifier of the signer" + }, + "type": { + "$ref": "#/components/schemas/SignerType", + "description": "The type of signer (local, aws_kms, google_cloud_kms, vault, etc.)" + } + } + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "ApiResponse_Vec_TransactionResponse": { + "type": "object", + "required": [ + "success" + ], + "properties": { + "data": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/EvmTransactionResponse" + }, + { + "$ref": "#/components/schemas/SolanaTransactionResponse" + }, + { + "$ref": "#/components/schemas/StellarTransactionResponse" + } + ] + } + }, + "error": { + "type": "string" + }, + "pagination": { + "$ref": "#/components/schemas/PaginationMeta" + }, + "success": { + "type": "boolean" + } + } + }, + "AssetSpec": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "native" + ] + } + } + }, + { + "type": "object", + "required": [ + "code", + "issuer", + "type" + ], + "properties": { + "code": { + "type": "string" + }, + "issuer": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "credit4" + ] + } + } + }, + { + "type": "object", + "required": [ + "code", + "issuer", + "type" + ], + "properties": { + "code": { + "type": "string" + }, + "issuer": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "credit12" + ] + } + } + } + ] + }, + "AuthSpec": { + "oneOf": [ + { + "type": "object", + "description": "No authorization required", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ] + } + } + }, + { + "type": "object", + "description": "Use the transaction source account for authorization", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "source_account" + ] + } + } + }, + { + "type": "object", + "description": "Use specific addresses for authorization", + "required": [ + "signers", + "type" + ], + "properties": { + "signers": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "addresses" + ] + } + } + }, + { + "type": "object", + "description": "Advanced format - provide complete XDR auth entries as base64-encoded strings", + "required": [ + "entries", + "type" + ], + "properties": { + "entries": { + "type": "array", + "items": { + "type": "string" + } + }, + "type": { + "type": "string", + "enum": [ + "xdr" + ] + } + } + } + ], + "description": "Authorization specification for Soroban operations" + }, + "AwsKmsSignerRequestConfig": { + "type": "object", + "description": "AWS KMS signer configuration for API requests", + "required": [ + "region", + "key_id" + ], + "properties": { + "key_id": { + "type": "string" + }, + "region": { + "type": "string" + } + }, + "additionalProperties": false + }, + "BalanceResponse": { + "type": "object", + "required": [ + "balance", + "unit" + ], + "properties": { + "balance": { + "type": "integer", + "minimum": 0 + }, + "unit": { + "type": "string", + "example": "wei" + } + } + }, + "ContractSource": { + "oneOf": [ + { + "type": "object", + "required": [ + "address", + "from" + ], + "properties": { + "address": { + "type": "string" + }, + "from": { + "type": "string", + "enum": [ + "address" + ] + } + } + }, + { + "type": "object", + "required": [ + "contract", + "from" + ], + "properties": { + "contract": { + "type": "string" + }, + "from": { + "type": "string", + "enum": [ + "contract" + ] + } + } + } + ], + "description": "Represents the source for contract creation" + }, + "CreateRelayerPolicyRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "Evm" + ], + "properties": { + "Evm": { + "$ref": "#/components/schemas/RelayerEvmPolicy" + } + } + }, + { + "type": "object", + "required": [ + "Solana" + ], + "properties": { + "Solana": { + "$ref": "#/components/schemas/RelayerSolanaPolicy" + } + } + }, + { + "type": "object", + "required": [ + "Stellar" + ], + "properties": { + "Stellar": { + "$ref": "#/components/schemas/RelayerStellarPolicy" + } + } + } + ], + "description": "Policy types for create requests - deserialized based on network_type from parent request" + }, + "CreateRelayerRequest": { + "type": "object", + "description": "Request model for creating a new relayer", + "required": [ + "name", + "network", + "paused", + "network_type", + "signer_id" + ], + "properties": { + "custom_rpc_urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RpcConfig" + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "network": { + "type": "string" + }, + "network_type": { + "$ref": "#/components/schemas/RelayerNetworkType" + }, + "notification_id": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "policies": { + "$ref": "#/components/schemas/CreateRelayerPolicyRequest", + "description": "Policies - will be deserialized based on the network_type field" + }, + "signer_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "DeletePendingTransactionsResponse": { + "type": "object", + "description": "Response for delete pending transactions operation", + "required": [ + "queued_for_cancellation_transaction_ids", + "failed_to_queue_transaction_ids", + "total_processed" + ], + "properties": { + "failed_to_queue_transaction_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "queued_for_cancellation_transaction_ids": { + "type": "array", + "items": { + "type": "string" + } + }, + "total_processed": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "EncodedSerializedTransaction": { + "type": "string" + }, + "EvmPolicyResponse": { + "type": "object", + "description": "EVM policy response model for OpenAPI documentation", + "properties": { + "eip1559_pricing": { + "type": "boolean" + }, + "gas_limit_estimation": { + "type": "boolean" + }, + "gas_price_cap": { + "type": "integer", + "minimum": 0 + }, + "min_balance": { + "type": "integer", + "minimum": 0 + }, + "private_transactions": { + "type": "boolean" + }, + "whitelist_receivers": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "EvmRpcRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string" + }, + "params": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "method", + "params" + ], + "properties": { + "method": { + "type": "string" + }, + "params": {} + } + } + ] + }, + "EvmRpcResult": { + "oneOf": [ + { + "type": "string" + }, + {} + ] + }, + "EvmTransactionDataSignature": { + "type": "object", + "required": [ + "r", + "s", + "v", + "sig" + ], + "properties": { + "r": { + "type": "string" + }, + "s": { + "type": "string" + }, + "sig": { + "type": "string" + }, + "v": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "EvmTransactionRequest": { + "type": "object", + "required": [ + "value" + ], + "properties": { + "data": { + "type": "string" + }, + "gas_limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "gas_price": { + "type": "integer", + "minimum": 0 + }, + "max_fee_per_gas": { + "type": "integer", + "minimum": 0 + }, + "max_priority_fee_per_gas": { + "type": "integer", + "minimum": 0 + }, + "speed": { + "$ref": "#/components/schemas/Speed" + }, + "to": { + "type": "string" + }, + "valid_until": { + "type": "string" + }, + "value": { + "type": "integer", + "format": "u128", + "minimum": 0 + } + } + }, + "EvmTransactionResponse": { + "type": "object", + "required": [ + "id", + "status", + "created_at", + "value", + "from", + "relayer_id" + ], + "properties": { + "confirmed_at": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "data": { + "type": "string" + }, + "from": { + "type": "string" + }, + "gas_limit": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "gas_price": { + "type": "integer", + "minimum": 0 + }, + "hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "max_fee_per_gas": { + "type": "integer", + "minimum": 0 + }, + "max_priority_fee_per_gas": { + "type": "integer", + "minimum": 0 + }, + "nonce": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "relayer_id": { + "type": "string" + }, + "sent_at": { + "type": "string" + }, + "signature": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/EvmTransactionDataSignature" + } + ] + }, + "speed": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/Speed" + } + ] + }, + "status": { + "$ref": "#/components/schemas/TransactionStatus" + }, + "status_reason": { + "type": [ + "string", + "null" + ] + }, + "to": { + "type": "string" + }, + "value": { + "type": "string" + } + } + }, + "FeeEstimateRequestParams": { + "type": "object", + "required": [ + "transaction", + "fee_token" + ], + "properties": { + "fee_token": { + "type": "string" + }, + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + }, + "additionalProperties": false + }, + "FeeEstimateResult": { + "type": "object", + "required": [ + "estimated_fee", + "conversion_rate" + ], + "properties": { + "conversion_rate": { + "type": "string" + }, + "estimated_fee": { + "type": "string" + } + } + }, + "GetFeaturesEnabledRequestParams": { + "type": "object", + "additionalProperties": false + }, + "GetFeaturesEnabledResult": { + "type": "object", + "required": [ + "features" + ], + "properties": { + "features": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "GetSupportedTokensItem": { + "type": "object", + "required": [ + "mint", + "symbol", + "decimals" + ], + "properties": { + "conversion_slippage_percentage": { + "type": "number", + "format": "float" + }, + "decimals": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "max_allowed_fee": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "mint": { + "type": "string" + }, + "symbol": { + "type": "string" + } + } + }, + "GetSupportedTokensRequestParams": { + "type": "object", + "additionalProperties": false + }, + "GetSupportedTokensResult": { + "type": "object", + "required": [ + "tokens" + ], + "properties": { + "tokens": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GetSupportedTokensItem" + } + } + } + }, + "GoogleCloudKmsSignerKeyRequestConfig": { + "type": "object", + "description": "Google Cloud KMS key configuration for API requests", + "required": [ + "location", + "key_ring_id", + "key_id", + "key_version" + ], + "properties": { + "key_id": { + "type": "string" + }, + "key_ring_id": { + "type": "string" + }, + "key_version": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "location": { + "type": "string" + } + }, + "additionalProperties": false + }, + "GoogleCloudKmsSignerKeyResponseConfig": { + "type": "object", + "required": [ + "location", + "key_ring_id", + "key_id", + "key_version" + ], + "properties": { + "key_id": { + "type": "string" + }, + "key_ring_id": { + "type": "string" + }, + "key_version": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "location": { + "type": "string" + } + } + }, + "GoogleCloudKmsSignerRequestConfig": { + "type": "object", + "description": "Google Cloud KMS signer configuration for API requests", + "required": [ + "service_account", + "key" + ], + "properties": { + "key": { + "$ref": "#/components/schemas/GoogleCloudKmsSignerKeyRequestConfig" + }, + "service_account": { + "$ref": "#/components/schemas/GoogleCloudKmsSignerServiceAccountRequestConfig" + } + }, + "additionalProperties": false + }, + "GoogleCloudKmsSignerServiceAccountRequestConfig": { + "type": "object", + "description": "Google Cloud KMS service account configuration for API requests", + "required": [ + "private_key", + "private_key_id", + "project_id", + "client_email", + "client_id", + "auth_uri", + "token_uri", + "auth_provider_x509_cert_url", + "client_x509_cert_url", + "universe_domain" + ], + "properties": { + "auth_provider_x509_cert_url": { + "type": "string" + }, + "auth_uri": { + "type": "string" + }, + "client_email": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_x509_cert_url": { + "type": "string" + }, + "private_key": { + "type": "string" + }, + "private_key_id": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "token_uri": { + "type": "string" + }, + "universe_domain": { + "type": "string" + } + }, + "additionalProperties": false + }, + "GoogleCloudKmsSignerServiceAccountResponseConfig": { + "type": "object", + "required": [ + "project_id", + "client_id", + "auth_uri", + "token_uri", + "auth_provider_x509_cert_url", + "client_x509_cert_url", + "universe_domain" + ], + "properties": { + "auth_provider_x509_cert_url": { + "type": "string" + }, + "auth_uri": { + "type": "string" + }, + "client_id": { + "type": "string" + }, + "client_x509_cert_url": { + "type": "string" + }, + "project_id": { + "type": "string" + }, + "token_uri": { + "type": "string" + }, + "universe_domain": { + "type": "string" + } + } + }, + "JsonRpcError": { + "type": "object", + "description": "JSON-RPC 2.0 Error structure.\n\nRepresents an error in a JSON-RPC response.", + "required": [ + "code", + "message", + "description" + ], + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "description": { + "type": "string" + }, + "message": { + "type": "string" + } + } + }, + "JsonRpcId": { + "oneOf": [ + { + "type": "string", + "description": "String identifier" + }, + { + "type": "integer", + "format": "int64", + "description": "Numeric identifier (should not contain fractional parts per spec)" + } + ], + "description": "Represents a JSON-RPC 2.0 ID value.\nAccording to the spec, the ID can be a String or Number.\nWhen used in `Option`: Some(id) = actual ID, None = explicit null." + }, + "JsonRpcRequest_NetworkRpcRequest": { + "allOf": [ + { + "oneOf": [ + { + "$ref": "#/components/schemas/SolanaRpcRequest" + }, + { + "$ref": "#/components/schemas/StellarRpcRequest" + }, + { + "$ref": "#/components/schemas/EvmRpcRequest" + } + ] + }, + { + "type": "object", + "required": [ + "jsonrpc" + ], + "properties": { + "id": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/JsonRpcId" + } + ] + }, + "jsonrpc": { + "type": "string" + } + } + } + ], + "description": "JSON-RPC 2.0 Request structure.\n\nRepresents a JSON-RPC request with proper ID handling:\n- `Some(JsonRpcId)` = request with ID\n- `None` = explicit null ID or notification" + }, + "JsonRpcResponse_NetworkRpcResult": { + "type": "object", + "description": "JSON-RPC 2.0 Response structure.\n\nRepresents a JSON-RPC response that can contain either a result or an error.", + "required": [ + "jsonrpc" + ], + "properties": { + "error": { + "$ref": "#/components/schemas/JsonRpcError" + }, + "id": { + "$ref": "#/components/schemas/JsonRpcId" + }, + "jsonrpc": { + "type": "string" + }, + "result": { + "oneOf": [ + { + "$ref": "#/components/schemas/SolanaRpcResult" + }, + { + "$ref": "#/components/schemas/StellarRpcResult" + }, + { + "$ref": "#/components/schemas/EvmRpcResult" + } + ] + } + } + }, + "JupiterSwapOptions": { + "type": "object", + "description": "Jupiter swap options", + "properties": { + "dynamic_compute_unit_limit": { + "type": "boolean" + }, + "priority_fee_max_lamports": { + "type": "integer", + "format": "int64", + "description": "Maximum priority fee (in lamports) for a transaction. Optional.", + "minimum": 0 + }, + "priority_level": { + "type": "string", + "description": "Priority. Optional." + } + }, + "additionalProperties": false + }, + "LocalSignerRequestConfig": { + "type": "object", + "description": "Local signer configuration for API requests", + "required": [ + "key" + ], + "properties": { + "key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "LogEntry": { + "type": "object", + "required": [ + "level", + "message" + ], + "properties": { + "level": { + "$ref": "#/components/schemas/LogLevel" + }, + "message": { + "type": "string" + } + } + }, + "LogLevel": { + "type": "string", + "enum": [ + "log", + "info", + "error", + "warn", + "debug", + "result" + ] + }, + "MemoSpec": { + "oneOf": [ + { + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "none" + ] + } + } + }, + { + "type": "object", + "required": [ + "value", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "text" + ] + }, + "value": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "value", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "id" + ] + }, + "value": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + { + "type": "object", + "required": [ + "value", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "hash" + ] + }, + "value": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + }, + { + "type": "object", + "required": [ + "value", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "return" + ] + }, + "value": { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + } + ] + }, + "NetworkPolicyResponse": { + "allOf": [ + { + "$ref": "#/components/schemas/RelayerNetworkPolicy" + } + ], + "description": "Network policy response models for OpenAPI documentation" + }, + "NetworkRpcRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/SolanaRpcRequest" + }, + { + "$ref": "#/components/schemas/StellarRpcRequest" + }, + { + "$ref": "#/components/schemas/EvmRpcRequest" + } + ] + }, + "NetworkRpcResult": { + "oneOf": [ + { + "$ref": "#/components/schemas/SolanaRpcResult" + }, + { + "$ref": "#/components/schemas/StellarRpcResult" + }, + { + "$ref": "#/components/schemas/EvmRpcResult" + } + ] + }, + "NetworkTransactionRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/EvmTransactionRequest" + }, + { + "$ref": "#/components/schemas/SolanaTransactionRequest" + }, + { + "$ref": "#/components/schemas/StellarTransactionRequest" + } + ] + }, + "NotificationCreateRequest": { + "type": "object", + "description": "Request structure for creating a new notification", + "required": [ + "url" + ], + "properties": { + "id": { + "type": "string" + }, + "signing_key": { + "type": "string", + "description": "Optional signing key for securing webhook notifications" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "NotificationResponse": { + "type": "object", + "description": "Response structure for notification API endpoints", + "required": [ + "id", + "type", + "url", + "has_signing_key" + ], + "properties": { + "has_signing_key": { + "type": "boolean", + "description": "Signing key is hidden in responses for security" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" + }, + "url": { + "type": "string" + } + } + }, + "NotificationType": { + "type": "string", + "description": "Notification type enum used by both config file and API", + "enum": [ + "webhook" + ] + }, + "NotificationUpdateRequest": { + "type": "object", + "description": "Request structure for updating an existing notification", + "properties": { + "signing_key": { + "type": [ + "string", + "null" + ], + "description": "Optional signing key for securing webhook notifications.\n- None: don't change the existing signing key\n- Some(\"\"): remove the signing key\n- Some(\"key\"): set the signing key to the provided value" + }, + "type": { + "$ref": "#/components/schemas/NotificationType" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "OperationSpec": { + "oneOf": [ + { + "type": "object", + "required": [ + "destination", + "amount", + "asset", + "type" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64" + }, + "asset": { + "$ref": "#/components/schemas/AssetSpec" + }, + "destination": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "payment" + ] + } + } + }, + { + "type": "object", + "required": [ + "contract_address", + "function_name", + "args", + "type" + ], + "properties": { + "args": { + "type": "array", + "items": {} + }, + "auth": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AuthSpec" + } + ] + }, + "contract_address": { + "type": "string" + }, + "function_name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "invoke_contract" + ] + } + } + }, + { + "type": "object", + "required": [ + "source", + "wasm_hash", + "type" + ], + "properties": { + "auth": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AuthSpec" + } + ] + }, + "constructor_args": { + "type": [ + "array", + "null" + ], + "items": {} + }, + "salt": { + "type": [ + "string", + "null" + ] + }, + "source": { + "$ref": "#/components/schemas/ContractSource" + }, + "type": { + "type": "string", + "enum": [ + "create_contract" + ] + }, + "wasm_hash": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "wasm", + "type" + ], + "properties": { + "auth": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/AuthSpec" + } + ] + }, + "type": { + "type": "string", + "enum": [ + "upload_wasm" + ] + }, + "wasm": { + "$ref": "#/components/schemas/WasmSource" + } + } + } + ] + }, + "PaginationMeta": { + "type": "object", + "required": [ + "current_page", + "per_page", + "total_items" + ], + "properties": { + "current_page": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "per_page": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "total_items": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "PluginCallRequest": { + "type": "object", + "required": [ + "params" + ], + "properties": { + "params": { + "description": "Plugin parameters" + } + } + }, + "PluginCallResponse": { + "type": "object", + "required": [ + "success", + "return_value", + "message", + "logs", + "error", + "traces" + ], + "properties": { + "error": { + "type": "string" + }, + "logs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LogEntry" + } + }, + "message": { + "type": "string" + }, + "return_value": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "traces": { + "type": "array", + "items": {} + } + } + }, + "PrepareTransactionRequestParams": { + "type": "object", + "required": [ + "transaction", + "fee_token" + ], + "properties": { + "fee_token": { + "type": "string" + }, + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + }, + "additionalProperties": false + }, + "PrepareTransactionResult": { + "type": "object", + "required": [ + "transaction", + "fee_in_spl", + "fee_in_lamports", + "fee_token", + "valid_until_blockheight" + ], + "properties": { + "fee_in_lamports": { + "type": "string" + }, + "fee_in_spl": { + "type": "string" + }, + "fee_token": { + "type": "string" + }, + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + }, + "valid_until_blockheight": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "RelayerEvmPolicy": { + "type": "object", + "description": "EVM-specific relayer policy configuration", + "properties": { + "eip1559_pricing": { + "type": [ + "boolean", + "null" + ] + }, + "gas_limit_estimation": { + "type": [ + "boolean", + "null" + ] + }, + "gas_price_cap": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "min_balance": { + "type": [ + "integer", + "null" + ], + "minimum": 0 + }, + "private_transactions": { + "type": [ + "boolean", + "null" + ] + }, + "whitelist_receivers": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "RelayerNetworkPolicy": { + "oneOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/RelayerEvmPolicy" + }, + { + "type": "object", + "required": [ + "network_type" + ], + "properties": { + "network_type": { + "type": "string", + "enum": [ + "evm" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/RelayerSolanaPolicy" + }, + { + "type": "object", + "required": [ + "network_type" + ], + "properties": { + "network_type": { + "type": "string", + "enum": [ + "solana" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/RelayerStellarPolicy" + }, + { + "type": "object", + "required": [ + "network_type" + ], + "properties": { + "network_type": { + "type": "string", + "enum": [ + "stellar" + ] + } + } + } + ] + } + ], + "description": "Network-specific policy for relayers" + }, + "RelayerNetworkPolicyResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/EvmPolicyResponse" + }, + { + "$ref": "#/components/schemas/StellarPolicyResponse" + }, + { + "$ref": "#/components/schemas/SolanaPolicyResponse" + } + ], + "description": "Policy types for responses - these don't include network_type tags\nsince the network_type is already available at the top level of RelayerResponse" + }, + "RelayerNetworkType": { + "type": "string", + "description": "Network type enum for relayers", + "enum": [ + "evm", + "solana", + "stellar" + ] + }, + "RelayerResponse": { + "type": "object", + "description": "Relayer response model for API endpoints", + "required": [ + "id", + "name", + "network", + "network_type", + "paused", + "signer_id" + ], + "properties": { + "address": { + "type": "string" + }, + "custom_rpc_urls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RpcConfig" + } + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "network": { + "type": "string" + }, + "network_type": { + "$ref": "#/components/schemas/RelayerNetworkType" + }, + "notification_id": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "policies": { + "$ref": "#/components/schemas/RelayerNetworkPolicyResponse", + "description": "Policies without redundant network_type tag - network type is available at top level\nOnly included if user explicitly provided policies (not shown for empty/default policies)" + }, + "signer_id": { + "type": "string" + }, + "system_disabled": { + "type": "boolean" + } + } + }, + "RelayerSolanaPolicy": { + "type": "object", + "description": "Solana-specific relayer policy configuration", + "properties": { + "allowed_accounts": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "allowed_programs": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "allowed_tokens": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/SolanaAllowedTokensPolicy" + } + }, + "disallowed_accounts": { + "type": [ + "array", + "null" + ], + "items": { + "type": "string" + } + }, + "fee_margin_percentage": { + "type": [ + "number", + "null" + ], + "format": "float" + }, + "fee_payment_strategy": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/SolanaFeePaymentStrategy" + } + ] + }, + "max_allowed_fee_lamports": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "max_signatures": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "max_tx_data_size": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "min_balance": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "swap_config": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/RelayerSolanaSwapConfig" + } + ] + } + }, + "additionalProperties": false + }, + "RelayerSolanaSwapConfig": { + "type": "object", + "description": "Solana swap policy configuration", + "properties": { + "cron_schedule": { + "type": "string", + "description": "Cron schedule for executing token swap logic to keep relayer funded. Optional." + }, + "jupiter_swap_options": { + "$ref": "#/components/schemas/JupiterSwapOptions", + "description": "Swap options for JupiterSwap strategy. Optional." + }, + "min_balance_threshold": { + "type": "integer", + "format": "int64", + "description": "Min sol balance to execute token swap logic to keep relayer funded. Optional.", + "minimum": 0 + }, + "strategy": { + "$ref": "#/components/schemas/SolanaSwapStrategy", + "description": "DEX strategy to use for token swaps." + } + }, + "additionalProperties": false + }, + "RelayerStatus": { + "oneOf": [ + { + "type": "object", + "required": [ + "balance", + "pending_transactions_count", + "system_disabled", + "paused", + "nonce", + "network_type" + ], + "properties": { + "balance": { + "type": "string" + }, + "last_confirmed_transaction_timestamp": { + "type": [ + "string", + "null" + ] + }, + "network_type": { + "type": "string", + "enum": [ + "evm" + ] + }, + "nonce": { + "type": "string" + }, + "paused": { + "type": "boolean" + }, + "pending_transactions_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "system_disabled": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "balance", + "pending_transactions_count", + "system_disabled", + "paused", + "sequence_number", + "network_type" + ], + "properties": { + "balance": { + "type": "string" + }, + "last_confirmed_transaction_timestamp": { + "type": [ + "string", + "null" + ] + }, + "network_type": { + "type": "string", + "enum": [ + "stellar" + ] + }, + "paused": { + "type": "boolean" + }, + "pending_transactions_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "sequence_number": { + "type": "string" + }, + "system_disabled": { + "type": "boolean" + } + } + }, + { + "type": "object", + "required": [ + "balance", + "pending_transactions_count", + "system_disabled", + "paused", + "network_type" + ], + "properties": { + "balance": { + "type": "string" + }, + "last_confirmed_transaction_timestamp": { + "type": [ + "string", + "null" + ] + }, + "network_type": { + "type": "string", + "enum": [ + "solana" + ] + }, + "paused": { + "type": "boolean" + }, + "pending_transactions_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "system_disabled": { + "type": "boolean" + } + } + } + ], + "description": "Relayer status with runtime information" + }, + "RelayerStellarPolicy": { + "type": "object", + "description": "Stellar-specific relayer policy configuration", + "properties": { + "concurrent_transactions": { + "type": [ + "boolean", + "null" + ] + }, + "max_fee": { + "type": [ + "integer", + "null" + ], + "format": "int32", + "minimum": 0 + }, + "min_balance": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + }, + "timeout_seconds": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "RpcConfig": { + "type": "object", + "description": "Configuration for an RPC endpoint.", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string", + "description": "The RPC endpoint URL." + }, + "weight": { + "type": "integer", + "format": "int32", + "description": "The weight of this endpoint in the weighted round-robin selection.\nDefaults to DEFAULT_RPC_WEIGHT (100). Should be between 0 and 100.", + "minimum": 0 + } + } + }, + "SignAndSendTransactionRequestParams": { + "type": "object", + "required": [ + "transaction" + ], + "properties": { + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + }, + "additionalProperties": false + }, + "SignAndSendTransactionResult": { + "type": "object", + "required": [ + "transaction", + "signature", + "id" + ], + "properties": { + "id": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + } + }, + "SignDataRequest": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, + "SignDataResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/SignDataResponseEvm" + }, + { + "$ref": "#/components/schemas/SignDataResponseSolana" + } + ] + }, + "SignDataResponseEvm": { + "type": "object", + "required": [ + "r", + "s", + "v", + "sig" + ], + "properties": { + "r": { + "type": "string" + }, + "s": { + "type": "string" + }, + "sig": { + "type": "string" + }, + "v": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "SignDataResponseSolana": { + "type": "object", + "required": [ + "signature", + "public_key" + ], + "properties": { + "public_key": { + "type": "string" + }, + "signature": { + "type": "string" + } + } + }, + "SignTransactionRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/SignTransactionRequestStellar" + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + ] + }, + "SignTransactionRequestParams": { + "type": "object", + "required": [ + "transaction" + ], + "properties": { + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + }, + "additionalProperties": false + }, + "SignTransactionRequestStellar": { + "type": "object", + "required": [ + "unsigned_xdr" + ], + "properties": { + "unsigned_xdr": { + "type": "string" + } + } + }, + "SignTransactionResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/SignTransactionResponseStellar" + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + }, + { + "type": "array", + "items": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + ] + }, + "SignTransactionResponseStellar": { + "type": "object", + "required": [ + "signedXdr", + "signature" + ], + "properties": { + "signature": { + "type": "string" + }, + "signedXdr": { + "type": "string" + } + } + }, + "SignTransactionResult": { + "type": "object", + "required": [ + "transaction", + "signature" + ], + "properties": { + "signature": { + "type": "string" + }, + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + } + }, + "SignTypedDataRequest": { + "type": "object", + "required": [ + "domain_separator", + "hash_struct_message" + ], + "properties": { + "domain_separator": { + "type": "string" + }, + "hash_struct_message": { + "type": "string" + } + } + }, + "SignerConfigRequest": { + "oneOf": [ + { + "$ref": "#/components/schemas/LocalSignerRequestConfig" + }, + { + "$ref": "#/components/schemas/AwsKmsSignerRequestConfig" + }, + { + "$ref": "#/components/schemas/VaultSignerRequestConfig" + }, + { + "$ref": "#/components/schemas/VaultTransitSignerRequestConfig" + }, + { + "$ref": "#/components/schemas/TurnkeySignerRequestConfig" + }, + { + "$ref": "#/components/schemas/GoogleCloudKmsSignerRequestConfig" + } + ], + "description": "Signer configuration enum for API requests (without type discriminator)" + }, + "SignerConfigResponse": { + "oneOf": [ + { + "type": "object", + "required": [ + "address", + "key_name" + ], + "properties": { + "address": { + "type": "string" + }, + "key_name": { + "type": "string" + }, + "mount_point": { + "type": [ + "string", + "null" + ] + }, + "namespace": { + "type": [ + "string", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "key_name", + "address", + "pubkey" + ], + "properties": { + "address": { + "type": "string" + }, + "key_name": { + "type": "string" + }, + "mount_point": { + "type": [ + "string", + "null" + ] + }, + "namespace": { + "type": [ + "string", + "null" + ] + }, + "pubkey": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "key_id" + ], + "properties": { + "key_id": { + "type": "string" + }, + "region": { + "type": [ + "string", + "null" + ] + } + } + }, + { + "type": "object", + "required": [ + "api_public_key", + "organization_id", + "private_key_id", + "public_key" + ], + "properties": { + "api_public_key": { + "type": "string" + }, + "organization_id": { + "type": "string" + }, + "private_key_id": { + "type": "string" + }, + "public_key": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "service_account", + "key" + ], + "properties": { + "key": { + "$ref": "#/components/schemas/GoogleCloudKmsSignerKeyResponseConfig" + }, + "service_account": { + "$ref": "#/components/schemas/GoogleCloudKmsSignerServiceAccountResponseConfig" + } + } + }, + { + "type": "object" + } + ], + "description": "Signer configuration response\nDoes not include sensitive information like private keys" + }, + "SignerCreateRequest": { + "type": "object", + "description": "Request model for creating a new signer", + "required": [ + "type", + "config" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/SignerConfigRequest", + "description": "The signer configuration" + }, + "id": { + "type": "string", + "description": "Optional ID - if not provided, a UUID will be generated" + }, + "type": { + "$ref": "#/components/schemas/SignerTypeRequest", + "description": "The type of signer" + } + }, + "additionalProperties": false + }, + "SignerResponse": { + "type": "object", + "required": [ + "id", + "type", + "config" + ], + "properties": { + "config": { + "$ref": "#/components/schemas/SignerConfigResponse", + "description": "Non-secret configuration details" + }, + "id": { + "type": "string", + "description": "The unique identifier of the signer" + }, + "type": { + "$ref": "#/components/schemas/SignerType", + "description": "The type of signer (local, aws_kms, google_cloud_kms, vault, etc.)" + } + } + }, + "SignerType": { + "type": "string", + "description": "Signer type enum used for validation and API responses", + "enum": [ + "local", + "aws_kms", + "google_cloud_kms", + "vault", + "vault_transit", + "turnkey" + ] + }, + "SignerTypeRequest": { + "type": "string", + "description": "Signer type enum for API requests", + "enum": [ + "plain", + "aws_kms", + "vault", + "vault_transit", + "turnkey", + "google_cloud_kms" + ] + }, + "SignerUpdateRequest": { + "type": "object", + "description": "Request model for updating an existing signer\nAt the moment, we don't allow updating signers", + "additionalProperties": false + }, + "SolanaAllowedTokensPolicy": { + "type": "object", + "description": "Configuration for allowed token handling on Solana", + "required": [ + "mint" + ], + "properties": { + "decimals": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "max_allowed_fee": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "mint": { + "type": "string" + }, + "swap_config": { + "$ref": "#/components/schemas/SolanaAllowedTokensSwapConfig" + }, + "symbol": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SolanaAllowedTokensSwapConfig": { + "type": "object", + "description": "Solana token swap configuration", + "properties": { + "max_amount": { + "type": "integer", + "format": "int64", + "description": "Maximum amount of tokens to swap. Optional.", + "minimum": 0 + }, + "min_amount": { + "type": "integer", + "format": "int64", + "description": "Minimum amount of tokens to swap. Optional.", + "minimum": 0 + }, + "retain_min_amount": { + "type": "integer", + "format": "int64", + "description": "Minimum amount of tokens to retain after swap. Optional.", + "minimum": 0 + }, + "slippage_percentage": { + "type": "number", + "format": "float", + "description": "Conversion slippage percentage for token. Optional." + } + }, + "additionalProperties": false + }, + "SolanaFeePaymentStrategy": { + "type": "string", + "description": "Solana fee payment strategy", + "enum": [ + "user", + "relayer" + ] + }, + "SolanaPolicyResponse": { + "type": "object", + "description": "Solana policy response model for OpenAPI documentation", + "properties": { + "allowed_accounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_programs": { + "type": "array", + "items": { + "type": "string" + } + }, + "allowed_tokens": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SolanaAllowedTokensPolicy" + } + }, + "disallowed_accounts": { + "type": "array", + "items": { + "type": "string" + } + }, + "fee_margin_percentage": { + "type": "number", + "format": "float" + }, + "fee_payment_strategy": { + "$ref": "#/components/schemas/SolanaFeePaymentStrategy" + }, + "max_allowed_fee_lamports": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "max_signatures": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "max_tx_data_size": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "min_balance": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "swap_config": { + "$ref": "#/components/schemas/RelayerSolanaSwapConfig" + } + }, + "additionalProperties": false + }, + "SolanaRpcRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "feeEstimate" + ] + }, + "params": { + "$ref": "#/components/schemas/FeeEstimateRequestParams" + } + }, + "example": "feeEstimate" + }, + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "transferTransaction" + ] + }, + "params": { + "$ref": "#/components/schemas/TransferTransactionRequestParams" + } + }, + "example": "transferTransaction" + }, + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "prepareTransaction" + ] + }, + "params": { + "$ref": "#/components/schemas/PrepareTransactionRequestParams" + } + }, + "example": "prepareTransaction" + }, + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "signTransaction" + ] + }, + "params": { + "$ref": "#/components/schemas/SignTransactionRequestParams" + } + }, + "example": "signTransaction" + }, + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "signAndSendTransaction" + ] + }, + "params": { + "$ref": "#/components/schemas/SignAndSendTransactionRequestParams" + } + }, + "example": "signAndSendTransaction" + }, + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "getSupportedTokens" + ] + }, + "params": { + "$ref": "#/components/schemas/GetSupportedTokensRequestParams" + } + }, + "example": "getSupportedTokens" + }, + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "getFeaturesEnabled" + ] + }, + "params": { + "$ref": "#/components/schemas/GetFeaturesEnabledRequestParams" + } + }, + "example": "getFeaturesEnabled" + } + ] + }, + "SolanaRpcResult": { + "oneOf": [ + { + "allOf": [ + { + "$ref": "#/components/schemas/FeeEstimateResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "feeEstimate" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/TransferTransactionResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "transferTransaction" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/PrepareTransactionResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "prepareTransaction" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/SignTransactionResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "signTransaction" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/SignAndSendTransactionResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "signAndSendTransaction" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/GetSupportedTokensResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "getSupportedTokens" + ] + } + } + } + ] + }, + { + "allOf": [ + { + "$ref": "#/components/schemas/GetFeaturesEnabledResult" + }, + { + "type": "object", + "required": [ + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "getFeaturesEnabled" + ] + } + } + } + ] + } + ] + }, + "SolanaSwapStrategy": { + "type": "string", + "description": "Solana swap strategy", + "enum": [ + "jupiter-swap", + "jupiter-ultra", + "noop" + ] + }, + "SolanaTransactionRequest": { + "type": "object", + "required": [ + "transaction" + ], + "properties": { + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + } + } + }, + "SolanaTransactionResponse": { + "type": "object", + "required": [ + "id", + "status", + "created_at", + "transaction" + ], + "properties": { + "confirmed_at": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "string" + }, + "sent_at": { + "type": "string" + }, + "signature": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TransactionStatus" + }, + "status_reason": { + "type": [ + "string", + "null" + ] + }, + "transaction": { + "type": "string" + } + } + }, + "Speed": { + "type": "string", + "enum": [ + "fastest", + "fast", + "average", + "safeLow" + ] + }, + "StellarPolicyResponse": { + "type": "object", + "description": "Stellar policy response model for OpenAPI documentation", + "properties": { + "concurrent_transactions": { + "type": "boolean" + }, + "max_fee": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "min_balance": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "timeout_seconds": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "StellarRpcRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "params", + "method" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "GenericRpcRequest" + ] + }, + "params": { + "type": "string" + } + } + } + ] + }, + "StellarRpcResult": { + "oneOf": [ + { + "type": "string" + } + ] + }, + "StellarTransactionRequest": { + "type": "object", + "required": [ + "network" + ], + "properties": { + "fee_bump": { + "type": [ + "boolean", + "null" + ], + "description": "Explicitly request fee-bump wrapper\nOnly valid when transaction_xdr contains a signed transaction" + }, + "max_fee": { + "type": [ + "integer", + "null" + ], + "format": "int64", + "description": "Maximum fee in stroops (defaults to 0.1 XLM = 1,000,000 stroops)" + }, + "memo": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/MemoSpec" + } + ] + }, + "network": { + "type": "string" + }, + "operations": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/OperationSpec" + } + }, + "source_account": { + "type": [ + "string", + "null" + ] + }, + "transaction_xdr": { + "type": [ + "string", + "null" + ], + "description": "Pre-built transaction XDR (base64 encoded, signed or unsigned)\nMutually exclusive with operations field" + }, + "valid_until": { + "type": [ + "string", + "null" + ] + } + } + }, + "StellarTransactionResponse": { + "type": "object", + "required": [ + "id", + "status", + "created_at", + "source_account", + "fee", + "sequence_number" + ], + "properties": { + "confirmed_at": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "fee": { + "type": "integer", + "format": "int32", + "minimum": 0 + }, + "hash": { + "type": "string" + }, + "id": { + "type": "string" + }, + "sent_at": { + "type": "string" + }, + "sequence_number": { + "type": "integer", + "format": "int64" + }, + "source_account": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TransactionStatus" + }, + "status_reason": { + "type": [ + "string", + "null" + ] + } + } + }, + "TransactionResponse": { + "oneOf": [ + { + "$ref": "#/components/schemas/EvmTransactionResponse" + }, + { + "$ref": "#/components/schemas/SolanaTransactionResponse" + }, + { + "$ref": "#/components/schemas/StellarTransactionResponse" + } + ] + }, + "TransactionStatus": { + "type": "string", + "enum": [ + "canceled", + "pending", + "sent", + "submitted", + "mined", + "confirmed", + "failed", + "expired" + ] + }, + "TransferTransactionRequestParams": { + "type": "object", + "required": [ + "amount", + "token", + "source", + "destination" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "destination": { + "type": "string" + }, + "source": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "additionalProperties": false + }, + "TransferTransactionResult": { + "type": "object", + "required": [ + "transaction", + "fee_in_spl", + "fee_in_lamports", + "fee_token", + "valid_until_blockheight" + ], + "properties": { + "fee_in_lamports": { + "type": "string" + }, + "fee_in_spl": { + "type": "string" + }, + "fee_token": { + "type": "string" + }, + "transaction": { + "$ref": "#/components/schemas/EncodedSerializedTransaction" + }, + "valid_until_blockheight": { + "type": "integer", + "format": "int64", + "minimum": 0 + } + } + }, + "TurnkeySignerRequestConfig": { + "type": "object", + "description": "Turnkey signer configuration for API requests", + "required": [ + "api_public_key", + "api_private_key", + "organization_id", + "private_key_id", + "public_key" + ], + "properties": { + "api_private_key": { + "type": "string" + }, + "api_public_key": { + "type": "string" + }, + "organization_id": { + "type": "string" + }, + "private_key_id": { + "type": "string" + }, + "public_key": { + "type": "string" + } + }, + "additionalProperties": false + }, + "UpdateRelayerRequest": { + "type": "object", + "properties": { + "custom_rpc_urls": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/components/schemas/RpcConfig" + } + }, + "name": { + "type": [ + "string", + "null" + ] + }, + "notification_id": { + "type": [ + "string", + "null" + ] + }, + "paused": { + "type": "boolean" + }, + "policies": { + "oneOf": [ + { + "type": "null" + }, + { + "$ref": "#/components/schemas/CreateRelayerPolicyRequest", + "description": "Raw policy JSON - will be validated against relayer's network type during application" + } + ] + } + }, + "additionalProperties": false + }, + "VaultSignerRequestConfig": { + "type": "object", + "description": "Vault signer configuration for API requests", + "required": [ + "address", + "role_id", + "secret_id", + "key_name" + ], + "properties": { + "address": { + "type": "string" + }, + "key_name": { + "type": "string" + }, + "mount_point": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "role_id": { + "type": "string" + }, + "secret_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "VaultTransitSignerRequestConfig": { + "type": "object", + "description": "Vault Transit signer configuration for API requests", + "required": [ + "key_name", + "address", + "role_id", + "secret_id", + "pubkey" + ], + "properties": { + "address": { + "type": "string" + }, + "key_name": { + "type": "string" + }, + "mount_point": { + "type": "string" + }, + "namespace": { + "type": "string" + }, + "pubkey": { + "type": "string" + }, + "role_id": { + "type": "string" + }, + "secret_id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "WasmSource": { + "oneOf": [ + { + "type": "object", + "required": [ + "hex" + ], + "properties": { + "hex": { + "type": "string" + } + } + }, + { + "type": "object", + "required": [ + "base64" + ], + "properties": { + "base64": { + "type": "string" + } + } + } + ], + "description": "Represents different ways to provide WASM code" + } + }, + "securitySchemes": { + "bearer_auth": { + "type": "http", + "scheme": "bearer" + } + } + }, + "tags": [ + { + "name": "Relayers", + "description": "Relayers are the core components of the OpenZeppelin Relayer API. They are responsible for executing transactions on behalf of users and providing a secure and reliable way to interact with the blockchain." + }, + { + "name": "Plugins", + "description": "Plugins are TypeScript functions that can be used to extend the OpenZeppelin Relayer API functionality." + }, + { + "name": "Notifications", + "description": "Notifications are responsible for showing the notifications related to the relayers." + }, + { + "name": "Signers", + "description": "Signers are responsible for signing the transactions related to the relayers." + }, + { + "name": "Metrics", + "description": "Metrics are responsible for showing the metrics related to the relayers." + }, + { + "name": "Health", + "description": "Health is responsible for showing the health of the relayers." + } + ] +} diff --git a/docs/public/site.webmanifest b/docs/public/site.webmanifest new file mode 100644 index 00000000..45dc8a20 --- /dev/null +++ b/docs/public/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/docs/public/social.png b/docs/public/social.png new file mode 100644 index 00000000..2e9942d1 Binary files /dev/null and b/docs/public/social.png differ diff --git a/docs/public/tally-exec copy.png b/docs/public/tally-exec copy.png new file mode 100644 index 00000000..e24a145d Binary files /dev/null and b/docs/public/tally-exec copy.png differ diff --git a/docs/public/tally-exec.png b/docs/public/tally-exec.png new file mode 100644 index 00000000..e24a145d Binary files /dev/null and b/docs/public/tally-exec.png differ diff --git a/docs/public/tally-vote copy.png b/docs/public/tally-vote copy.png new file mode 100644 index 00000000..7d270fce Binary files /dev/null and b/docs/public/tally-vote copy.png differ diff --git a/docs/public/tally-vote.png b/docs/public/tally-vote.png new file mode 100644 index 00000000..7d270fce Binary files /dev/null and b/docs/public/tally-vote.png differ diff --git a/docs/public/ui-builder/contracts-abi-auto.png b/docs/public/ui-builder/contracts-abi-auto.png new file mode 100644 index 00000000..57f8054d Binary files /dev/null and b/docs/public/ui-builder/contracts-abi-auto.png differ diff --git a/docs/public/ui-builder/contracts-abi-manual.png b/docs/public/ui-builder/contracts-abi-manual.png new file mode 100644 index 00000000..eea1b1e2 Binary files /dev/null and b/docs/public/ui-builder/contracts-abi-manual.png differ diff --git a/docs/public/ui-builder/contracts-state.png b/docs/public/ui-builder/contracts-state.png new file mode 100644 index 00000000..26aadf7d Binary files /dev/null and b/docs/public/ui-builder/contracts-state.png differ diff --git a/docs/public/ui-builder/customization-eoa.png b/docs/public/ui-builder/customization-eoa.png new file mode 100644 index 00000000..6f9747aa Binary files /dev/null and b/docs/public/ui-builder/customization-eoa.png differ diff --git a/docs/public/ui-builder/customization-fields.png b/docs/public/ui-builder/customization-fields.png new file mode 100644 index 00000000..10c3d31e Binary files /dev/null and b/docs/public/ui-builder/customization-fields.png differ diff --git a/docs/public/ui-builder/customization-general.png b/docs/public/ui-builder/customization-general.png new file mode 100644 index 00000000..f5ed2d7c Binary files /dev/null and b/docs/public/ui-builder/customization-general.png differ diff --git a/docs/public/ui-builder/customization-multisig.png b/docs/public/ui-builder/customization-multisig.png new file mode 100644 index 00000000..47356f78 Binary files /dev/null and b/docs/public/ui-builder/customization-multisig.png differ diff --git a/docs/public/ui-builder/customization-preview-button.png b/docs/public/ui-builder/customization-preview-button.png new file mode 100644 index 00000000..ab936928 Binary files /dev/null and b/docs/public/ui-builder/customization-preview-button.png differ diff --git a/docs/public/ui-builder/customization-preview-tx.png b/docs/public/ui-builder/customization-preview-tx.png new file mode 100644 index 00000000..6ae96980 Binary files /dev/null and b/docs/public/ui-builder/customization-preview-tx.png differ diff --git a/docs/public/ui-builder/customization-preview-usage.png b/docs/public/ui-builder/customization-preview-usage.png new file mode 100644 index 00000000..82742e47 Binary files /dev/null and b/docs/public/ui-builder/customization-preview-usage.png differ diff --git a/docs/public/ui-builder/customization-rainbow.png b/docs/public/ui-builder/customization-rainbow.png new file mode 100644 index 00000000..3abc14ab Binary files /dev/null and b/docs/public/ui-builder/customization-rainbow.png differ diff --git a/docs/public/ui-builder/customization-relayer.png b/docs/public/ui-builder/customization-relayer.png new file mode 100644 index 00000000..b8d3b80e Binary files /dev/null and b/docs/public/ui-builder/customization-relayer.png differ diff --git a/docs/public/ui-builder/customization-wallet-ui.png b/docs/public/ui-builder/customization-wallet-ui.png new file mode 100644 index 00000000..18dccd62 Binary files /dev/null and b/docs/public/ui-builder/customization-wallet-ui.png differ diff --git a/docs/public/ui-builder/exporting-export-button.png b/docs/public/ui-builder/exporting-export-button.png new file mode 100644 index 00000000..07e1b1c5 Binary files /dev/null and b/docs/public/ui-builder/exporting-export-button.png differ diff --git a/docs/public/ui-builder/exporting-history-contracts.png b/docs/public/ui-builder/exporting-history-contracts.png new file mode 100644 index 00000000..c29c2bf3 Binary files /dev/null and b/docs/public/ui-builder/exporting-history-contracts.png differ diff --git a/docs/public/ui-builder/exporting-history-save-configs.png b/docs/public/ui-builder/exporting-history-save-configs.png new file mode 100644 index 00000000..982f6446 Binary files /dev/null and b/docs/public/ui-builder/exporting-history-save-configs.png differ diff --git a/docs/public/ui-builder/functions-select.png b/docs/public/ui-builder/functions-select.png new file mode 100644 index 00000000..2774eb54 Binary files /dev/null and b/docs/public/ui-builder/functions-select.png differ diff --git a/docs/public/ui-builder/networks-advance-settings.png b/docs/public/ui-builder/networks-advance-settings.png new file mode 100644 index 00000000..322f5a0a Binary files /dev/null and b/docs/public/ui-builder/networks-advance-settings.png differ diff --git a/docs/public/ui-builder/networks-config-button.png b/docs/public/ui-builder/networks-config-button.png new file mode 100644 index 00000000..3dd953dc Binary files /dev/null and b/docs/public/ui-builder/networks-config-button.png differ diff --git a/docs/public/ui-builder/networks-explorer-settings.png b/docs/public/ui-builder/networks-explorer-settings.png new file mode 100644 index 00000000..48e78c4a Binary files /dev/null and b/docs/public/ui-builder/networks-explorer-settings.png differ diff --git a/docs/public/ui-builder/networks-rpc-settings.png b/docs/public/ui-builder/networks-rpc-settings.png new file mode 100644 index 00000000..5d7e3c48 Binary files /dev/null and b/docs/public/ui-builder/networks-rpc-settings.png differ diff --git a/docs/public/ui-builder/quickstart-contract-address.png b/docs/public/ui-builder/quickstart-contract-address.png new file mode 100644 index 00000000..ad2c7515 Binary files /dev/null and b/docs/public/ui-builder/quickstart-contract-address.png differ diff --git a/docs/public/ui-builder/quickstart-customize-form.png b/docs/public/ui-builder/quickstart-customize-form.png new file mode 100644 index 00000000..e09e9feb Binary files /dev/null and b/docs/public/ui-builder/quickstart-customize-form.png differ diff --git a/docs/public/ui-builder/quickstart-export-form.png b/docs/public/ui-builder/quickstart-export-form.png new file mode 100644 index 00000000..51f66a24 Binary files /dev/null and b/docs/public/ui-builder/quickstart-export-form.png differ diff --git a/docs/public/ui-builder/quickstart-select-function.png b/docs/public/ui-builder/quickstart-select-function.png new file mode 100644 index 00000000..b8a431c8 Binary files /dev/null and b/docs/public/ui-builder/quickstart-select-function.png differ diff --git a/docs/public/ui-builder/quickstart-select-network.png b/docs/public/ui-builder/quickstart-select-network.png new file mode 100644 index 00000000..d654e345 Binary files /dev/null and b/docs/public/ui-builder/quickstart-select-network.png differ diff --git a/docs/public/webauthn-demo.png b/docs/public/webauthn-demo.png new file mode 100644 index 00000000..4fbe2a3c Binary files /dev/null and b/docs/public/webauthn-demo.png differ diff --git a/docs/public/zombie-chain-spec.png b/docs/public/zombie-chain-spec.png new file mode 100644 index 00000000..d24bb9e7 Binary files /dev/null and b/docs/public/zombie-chain-spec.png differ diff --git a/docs/scripts/cairo-convert.js b/docs/scripts/cairo-convert.js new file mode 100644 index 00000000..c9b240e0 --- /dev/null +++ b/docs/scripts/cairo-convert.js @@ -0,0 +1,35 @@ +const fs = require("fs"); +const path = require("path"); + +function crawlDirectory(dir) { + const items = fs.readdirSync(dir); + + for (const item of items) { + const fullPath = path.join(dir, item); + const stat = fs.statSync(fullPath); + + if (stat.isDirectory()) { + crawlDirectory(fullPath); + } else if (stat.isFile()) { + processFile(fullPath); + } + } +} + +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, "utf8"); + const updatedContent = content.replace(/```rust/g, "```rust"); + + if (content !== updatedContent) { + fs.writeFileSync(filePath, updatedContent, "utf8"); + console.log(`Updated: ${filePath}`); + } + } catch (error) { + console.error(`Error processing ${filePath}:`, error.message); + } +} + +// Start crawling from current directory or specify a path +const targetDirectory = process.argv[2] || "."; +crawlDirectory(targetDirectory); diff --git a/docs/scripts/convert-adoc.js b/docs/scripts/convert-adoc.js new file mode 100755 index 00000000..5b633689 --- /dev/null +++ b/docs/scripts/convert-adoc.js @@ -0,0 +1,265 @@ +#!/usr/bin/env node + +const fs = require("fs").promises; +const fsSync = require("fs"); +const path = require("path"); +const { execSync } = require("child_process"); +const { glob } = require("glob"); + +async function convertAdocFiles(directory, apiRoute = "contracts/5.x/api") { + if (!directory) { + console.error("Usage: node convert-adoc.js [apiRoute]"); + process.exit(1); + } + + const adocFiles = await glob(`${directory}/**/*.adoc`); + + for (const adocFile of adocFiles) { + console.log(`Processing: ${adocFile}`); + + const dir = path.dirname(adocFile); + const filename = path.basename(adocFile, ".adoc"); + const mdxFile = path.join(dir, `${filename}.mdx`); + + try { + // Read original file + let content = await fs.readFile(adocFile, "utf8"); + + // Replace code blocks with includes + content = content.replace( + /```solidity\s*\ninclude::api:example\$([^[\]]+)\[\]\s*\n```/g, + "./examples/$1", + ); + + content = content.replace( + /\[source,solidity\]\s*\n----\s*\ninclude::api:example\$([^[\]]+)\[\]\s*\n----/g, + "./examples/$1", + ); + + // Replace TIP: and NOTE: callouts with tags + content = content.replace( + /^(TIP|NOTE):\s*(.+)$/gm, + "\n$2\n", + ); + + content = content.replace( + /^(IMPORTANT|WARNING|CAUTION):\s*(.+)$/gm, + "\n$2\n", + ); + + // Handle multi-line callouts + content = content.replace( + /^(TIP|NOTE):\s*\n((?:.*\n?)*?)(?=\n\n|$)/gm, + "\n$2\n", + ); + + content = content.replace( + /^(IMPORTANT|WARNING|CAUTION):\s*\n((?:.*\n?)*?)(?=\n\n|$)/gm, + "\n$2\n", + ); + // Write preprocessed content + const tempFile = path.join(dir, `temp_${filename}.adoc`); + await fs.writeFile(tempFile, content, "utf8"); + + // Run downdoc + execSync(`pnpm dlx downdoc "${tempFile}"`, { stdio: "pipe" }); + + // Find the generated .md file + const tempMdFile = path.join(dir, `temp_${filename}.md`); + let mdContent = await fs.readFile(tempMdFile, "utf8"); + + // Fix HTML entities globally + mdContent = mdContent + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(///g, "/") + .replace(/&/g, "&"); // This should be last to avoid double-decoding + + // Convert api: links to dynamic api route and change .adoc to .mdx + mdContent = mdContent.replace( + /\(api:([^)]+)\.adoc([^)]*)\)/g, + `(/${apiRoute}/$1$2)`, + ); + + // Add forward slash to image paths + mdContent = mdContent.replace( + /!\[([^\]]*)\]\(([^/)][^)]*\.(png|jpg|jpeg|gif|svg|webp))\)/g, + "![$1](/$2)", + ); + + // Fix xref: links - remove xref: and convert .adoc to .mdx + mdContent = mdContent.replace( + /xref:\[([^\]]+)\]\(([^)]+)\)/g, + "[$1]($2)", + ); + + // Fix .adoc internal links to .mdx + mdContent = mdContent.replace(/\]\(([^)]+)\.adoc([^)]*)\)/g, "]($1$2)"); + + // Fix curly bracket file references {filename} -> filename, but preserve braces in code blocks + const parts = mdContent.split(/(```[\s\S]*?```)/g); + mdContent = parts + .map((part, index) => { + // Every odd index is a code block (```...```) + if (index % 2 === 1) { + return part; // Preserve code blocks as-is + } else { + // Remove curly brackets from non-code-block parts only + return part.replace(/\{([^}]+)\}/g, "$1"); + } + }) + .join(""); + + // Fix HTML-style callouts
📌 NOTE
...
+ // Handle multi-line callouts by using a more permissive pattern + mdContent = mdContent.replace( + /
[📌🔔ℹ️]\s*(NOTE|TIP|INFO)<\/strong><\/dt>
([\s\S]*?)<\/dd><\/dl>/gu, + "\n$2\n", + ); + + mdContent = mdContent.replace( + /
[⚠️🚨❗]\s*(WARNING|IMPORTANT|CAUTION|DANGER)<\/strong><\/dt>
([\s\S]*?)<\/dd><\/dl>/g, + "\n$2\n", + ); + + // Handle cases where
might be missing or malformed + mdContent = mdContent.replace( + /
[📌🔔ℹ️]\s*(NOTE|TIP|INFO)<\/strong><\/dt>
([\s\S]*?)(?=\n\n|
|$)/gu, + "\n$2\n", + ); + + mdContent = mdContent.replace( + /
[⚠️🚨❗]\s*(WARNING|IMPORTANT|CAUTION|DANGER)<\/strong><\/dt>
([\s\S]*?)(?=\n\n|
|$)/g, + "\n$2\n", + ); + + // Fix xref patterns with complex anchors like xref:#ISRC6-\\__execute__[...] + mdContent = mdContent.replace( + /xref:#([^[\]]+)\[([^\]]+)\]/g, + "[$2](#$1)", + ); + + // Fix simple xref patterns + mdContent = mdContent.replace(/xref:([^[\s]+)\[([^\]]+)\]/g, "[$2]($1)"); + + // Clean up orphaned HTML tags from malformed callouts + // Handle orphaned
EMOJI TYPE
without closing tags + mdContent = mdContent.replace( + /
[📌🔔ℹ️]\s*(NOTE|TIP|INFO)<\/strong><\/dt>
\s*\n([\s\S]*?)(?=\n\n|
|$)/gu, + "\n$2\n", + ); + + mdContent = mdContent.replace( + /
[⚠️🚨❗]\s*(WARNING|IMPORTANT|CAUTION|DANGER)<\/strong><\/dt>
\s*\n([\s\S]*?)(?=\n\n|
|$)/g, + "\n$2\n", + ); + + // Clean up any remaining orphaned HTML tags + mdContent = mdContent.replace( + /
.*?<\/strong><\/dt>
/g, + "", + ); + mdContent = mdContent.replace(/<\/dd><\/dl>/g, ""); + mdContent = mdContent.replace(/
/g, ""); + mdContent = mdContent.replace(/<\/dd>/g, ""); + mdContent = mdContent.replace(/
/g, ""); + mdContent = mdContent.replace(/<\/dl>/g, ""); + + // Fix AsciiDoc monospace formatting (++ to backticks) + // Handle ++text++ -> `text` + mdContent = mdContent.replace(/\+\+([^+]+)\+\+/g, "`$1`"); + + // Fix any remaining ++ that might be standalone + mdContent = mdContent.replace(/\+\+/g, ""); + // Extract title + const headerMatch = mdContent.match(/^#+\s+(.+)$/m); + const title = headerMatch ? headerMatch[1].trim() : filename; + + // Remove the first H1 from content + const contentWithoutFirstH1 = mdContent + .replace(/^#+\s+.+$/m, "") + .replace(/^\n+/, ""); + + // Create MDX with frontmatter + const mdxContent = `--- +title: ${title} +--- + +${contentWithoutFirstH1}`; + + await fs.writeFile(mdxFile, mdxContent, "utf8"); + + // Cleanup + await fs.unlink(tempMdFile); + await fs.unlink(tempFile); + await fs.unlink(adocFile); + + console.log(" ✓ Converted successfully"); + } catch (error) { + console.log(` ✗ Error: ${error.message}`); + } + } +} + +// Process files to remove curly brackets after conversion +function processFile(filePath) { + try { + // Only process .mdx files (skip .adoc files) + if (!filePath.endsWith(".mdx")) { + console.log(`Skipped: ${filePath}`); + return; + } + + const content = fsSync.readFileSync(filePath, "utf8"); + // Split content by code blocks and process non-code-block parts only + const parts = content.split(/(```[\s\S]*?```)/g); + const modifiedContent = parts + .map((part, index) => { + // Every odd index is a code block (```...```) + if (index % 2 === 1) { + return part; // Preserve code blocks as-is + } else { + // Remove curly brackets from non-code-block parts + return part.replace(/[{}]/g, ""); + } + }) + .join(""); + + fsSync.writeFileSync(filePath, modifiedContent, "utf8"); + console.log(`Processed: ${filePath}`); + } catch (error) { + console.error(`Error processing ${filePath}: ${error.message}`); + } +} + +function crawlDirectory(dirPath) { + try { + const items = fsSync.readdirSync(dirPath); + + for (const item of items) { + const itemPath = path.join(dirPath, item); + const stats = fsSync.statSync(itemPath); + + if (stats.isDirectory()) { + crawlDirectory(itemPath); + } else if (stats.isFile()) { + processFile(itemPath); + } + } + } catch (error) { + console.error(`Error crawling directory ${dirPath}: ${error.message}`); + } +} + +const directory = process.argv[2]; +const apiRoute = process.argv[3]; + +async function main() { + await convertAdocFiles(directory, apiRoute); + // Run bracket processing after conversion + crawlDirectory(directory); +} + +main().catch(console.error); diff --git a/docs/scripts/generate-api-docs.js b/docs/scripts/generate-api-docs.js new file mode 100755 index 00000000..4a148aad --- /dev/null +++ b/docs/scripts/generate-api-docs.js @@ -0,0 +1,222 @@ +#!/usr/bin/env node + +import { promises as fs } from "node:fs"; +import path from "node:path"; +import { execSync } from "node:child_process"; + +// Parse command line arguments +function parseArgs() { + const args = process.argv.slice(2); + const options = { + contractsRepo: + "https://github.com/stevedylandev/openzeppelin-contracts.git", + contractsBranch: "master", + tempDir: "temp-contracts", + apiOutputDir: "content/contracts/5.x/api", + examplesOutputDir: "examples", + }; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + switch (arg) { + case "--help": + case "-h": + showHelp(); + process.exit(0); + break; + case "--repo": + case "-r": + options.contractsRepo = args[++i]; + break; + case "--branch": + case "-b": + options.contractsBranch = args[++i]; + break; + case "--temp-dir": + case "-t": + options.tempDir = args[++i]; + break; + case "--api-output": + case "-a": + options.apiOutputDir = args[++i]; + break; + case "--examples-output": + case "-e": + options.examplesOutputDir = args[++i]; + break; + default: + console.error(`Unknown option: ${arg}`); + showHelp(); + process.exit(1); + } + } + + return options; +} + +function showHelp() { + console.log(` +Generate OpenZeppelin Contracts API documentation + +Usage: node generate-api-docs.js [options] + +Options: + -r, --repo Contracts repository URL (default: https://github.com/OpenZeppelin/openzeppelin-contracts.git) + -b, --branch Contracts repository branch (default: master) + -t, --temp-dir Temporary directory for cloning (default: temp-contracts) + -a, --api-output API documentation output directory (default: content/contracts/v5.x/api) + -e, --examples-output Examples output directory (default: examples) + -h, --help Show this help message + +Examples: + node generate-api-docs.js + node generate-api-docs.js --repo https://github.com/myorg/contracts.git --branch v4.0 + node generate-api-docs.js --api-output content/contracts/v4.x/api --examples-output examples-v4 +`); +} + +async function generateApiDocs(options) { + const { + contractsRepo, + contractsBranch, + tempDir, + apiOutputDir, + examplesOutputDir, + } = options; + + console.log("🔄 Generating OpenZeppelin Contracts API documentation..."); + console.log(`📦 Repository: ${contractsRepo}`); + console.log(`🌿 Branch: ${contractsBranch}`); + console.log(`📂 API Output: ${apiOutputDir}`); + console.log(`📂 Examples Output: ${examplesOutputDir}`); + + try { + // Clean up previous runs + console.log("🧹 Cleaning up previous runs..."); + await fs.rm(tempDir, { recursive: true, force: true }); + await fs.rm(apiOutputDir, { recursive: true, force: true }); + + // Create output directory + await fs.mkdir(apiOutputDir, { recursive: true }); + + // Clone the contracts repository + console.log("📦 Cloning contracts repository..."); + execSync( + `git clone --depth 1 --branch "${contractsBranch}" --recurse-submodules "${contractsRepo}" "${tempDir}"`, + { + stdio: "inherit", + }, + ); + + // Navigate to contracts directory and install dependencies + console.log("📚 Installing dependencies..."); + const originalDir = process.cwd(); + process.chdir(tempDir); + + try { + execSync("npm install --silent", { stdio: "inherit" }); + + // Generate markdown documentation + console.log("🏗️ Generating clean markdown documentation..."); + execSync("npm run prepare-docs", { stdio: "inherit" }); + + // Copy generated markdown files + console.log("📋 Copying generated documentation..."); + const docsPath = path.join("docs", "modules", "api", "pages"); + + try { + await fs.access(docsPath); + // Copy API docs + const apiSource = path.join(process.cwd(), docsPath); + const apiDest = path.join(originalDir, apiOutputDir); + await copyDirRecursive(apiSource, apiDest); + console.log(`✅ API documentation copied to ${apiOutputDir}`); + } catch (error) { + console.log( + "❌ Error: Markdown documentation not found at expected location", + ); + process.exit(1); + } + + // Copy examples if they exist + const examplesPath = path.join("docs", "modules", "api", "examples"); + if ( + await fs + .access(examplesPath) + .then(() => true) + .catch(() => false) + ) { + const examplesDest = path.join(originalDir, examplesOutputDir); + await fs.mkdir(examplesDest, { recursive: true }); + await copyDirRecursive( + path.join(process.cwd(), examplesPath), + examplesDest, + ); + console.log(`✅ Examples copied to ${examplesOutputDir}`); + } + + // Get version for index file + let version = "latest"; + try { + const packageJson = JSON.parse( + await fs.readFile("package.json", "utf8"), + ); + version = packageJson.version || version; + } catch (error) { + console.log("⚠️ Could not read package.json for version info"); + } + + // Generate index file + // console.log("📝 Generating API index..."); + // const indexContent = `--- + // title: API Reference + // --- + + // # API Reference + // `; + + // await fs.writeFile( + // path.join(originalDir, apiOutputDir, "index.mdx"), + // indexContent, + // "utf8", + // ); + } finally { + // Go back to original directory + process.chdir(originalDir); + } + + // Clean up temporary directory + console.log("🧹 Cleaning up..."); + await fs.rm(tempDir, { recursive: true, force: true }); + + console.log("🎉 API documentation generation complete!"); + console.log(`📂 Documentation available in: ${apiOutputDir}`); + console.log(""); + console.log("Next steps:"); + console.log(` - Review generated markdown files in ${apiOutputDir}`); + console.log(" - Update the api/index.mdx file with your TOC"); + } catch (error) { + console.error("❌ Error generating API documentation:", error.message); + process.exit(1); + } +} + +async function copyDirRecursive(src, dest) { + const entries = await fs.readdir(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + if (entry.isDirectory()) { + await fs.mkdir(destPath, { recursive: true }); + await copyDirRecursive(srcPath, destPath); + } else { + await fs.copyFile(srcPath, destPath); + } + } +} + +// Main execution +const options = parseArgs(); +generateApiDocs(options); diff --git a/docs/scripts/generate-changelog.js b/docs/scripts/generate-changelog.js new file mode 100755 index 00000000..8f232575 --- /dev/null +++ b/docs/scripts/generate-changelog.js @@ -0,0 +1,71 @@ +#!/usr/bin/env node + +const fs = require("node:fs").promises; +const path = require("node:path"); +const { execSync } = require("node:child_process"); + +// Check if two arguments are provided +if (process.argv.length !== 4) { + console.error("Usage: node generate-changelog.js "); + process.exit(1); +} + +// Assign arguments to variables +const url = process.argv[2]; +const filePath = process.argv[3]; + +// Check if changelog-from-release is installed +function checkChangelogFromRelease() { + try { + // Try to run the command to see if it exists + execSync("which changelog-from-release", { stdio: "pipe" }); + } catch (error) { + console.error("Error: changelog-from-release is not installed."); + console.error( + "Please install it from: https://github.com/rhysd/changelog-from-release", + ); + process.exit(1); + } +} + +async function generateChangelog() { + try { + // Create the changelog.mdx file with the specified content + const frontmatter = `--- +title: Changelog +--- + +`; + + // Run changelog-from-release and get output + const changelogOutput = execSync(`changelog-from-release -r "${url}"`, { + encoding: "utf8", + stdio: "pipe", + }); + + // Remove the generated tag at the end + const cleanOutput = changelogOutput + .replace(/\s*$/, "") + .trim(); + + // Write the complete content + const fullContent = frontmatter + cleanOutput + "\n"; + await fs.writeFile( + path.join(filePath, "changelog.mdx"), + fullContent, + "utf8", + ); + + console.log( + `Changelog generated successfully at ${path.join(filePath, "changelog.mdx")}`, + ); + } catch (error) { + console.error("Error generating changelog:", error.message); + process.exit(1); + } +} + +// Check if required tool is installed +checkChangelogFromRelease(); + +generateChangelog(); diff --git a/docs/scripts/generate-openapi-docs.js b/docs/scripts/generate-openapi-docs.js new file mode 100755 index 00000000..fa595444 --- /dev/null +++ b/docs/scripts/generate-openapi-docs.js @@ -0,0 +1,10 @@ +import { generateFiles } from "fumadocs-openapi"; +import { openapi } from "@/lib/openapi"; + +void generateFiles({ + input: openapi, + output: "./content/relayer/api", + // we recommend to enable it + // make sure your endpoint description doesn't break MDX syntax. + includeDescription: true, +}); diff --git a/docs/scripts/link-validation.ts b/docs/scripts/link-validation.ts new file mode 100644 index 00000000..a7ab9ffc --- /dev/null +++ b/docs/scripts/link-validation.ts @@ -0,0 +1,265 @@ +import { + type FileObject, + printErrors, + scanURLs, + validateFiles, +} from "next-validate-link"; +import type { InferPageType } from "fumadocs-core/source"; +import { source } from "@/lib/source"; +import { writeFileSync } from "fs"; +import { + arbitrumStylusTree, + ethereumEvmTree, + impactTree, + midnightTree, + polkadotTree, + starknetTree, + stellarTree, + uniswapTree, + zamaTree, + type NavigationNode, + type NavigationPage, + type NavigationFolder, +} from "@/navigation"; + +async function checkLinks() { + // Parse command line arguments + const outputFile = process.argv.includes("--output") + ? process.argv[process.argv.indexOf("--output") + 1] + : null; + + const scope = process.argv.includes("--scope") + ? process.argv[process.argv.indexOf("--scope") + 1] + : null; + + const ignoreFragments = !process.argv.includes("--no-ignore-fragments"); + + const pages = await Promise.all( + source.getPages().map(async (page) => { + return { + value: { + slug: page.slugs, + }, + hashes: await getHeadings(page), + }; + }), + ); + + const scanned = await scanURLs({ + // pick a preset for your React framework + preset: "next", + populate: { + "(docs)/[...slug]": pages, + }, + }); + + const validationResults = await validateFiles(await getFiles(scope), { + scanned, + // check `href` attributes in different MDX components + markdown: { + components: { + Card: { attributes: ["href"] }, + }, + }, + ignoreFragment: ignoreFragments, + // check relative paths + checkRelativePaths: "as-url", + }); + + // Validate navigation URLs if requested + let navigationErrors: Array<{ tree: string; url: string; reason: string }> = + []; + navigationErrors = await validateNavigationUrls(scanned); + + if (outputFile) { + // Generate custom output format for file + let output = ""; + let totalErrors = 0; + let totalFiles = 0; + + for (const result of validationResults) { + totalFiles++; + if (result.errors.length > 0) { + output += `Invalid URLs in ${result.file}:\n`; + for (const error of result.errors) { + totalErrors++; + output += `${error.url}: ${error.reason} at line ${error.line} column ${error.column}\n`; + } + output += "------\n"; + } + } + + // Add navigation errors to output + if (navigationErrors.length > 0) { + output += `\nInvalid URLs in Navigation Trees:\n`; + for (const error of navigationErrors) { + totalErrors++; + output += `${error.tree}: ${error.url} - ${error.reason}\n`; + } + output += "------\n"; + } + + output += `\nSummary: ${totalErrors} errors found in ${validationResults.filter((r) => r.errors.length > 0).length} files out of ${totalFiles} total files`; + if (navigationErrors.length > 0) { + output += ` + ${navigationErrors.length} navigation errors`; + } + output += `\n`; + + writeFileSync(outputFile, output); + console.log(`Results saved to ${outputFile}`); + console.log( + `${totalErrors} errors found in ${validationResults.filter((r) => r.errors.length > 0).length} files`, + ); + if (navigationErrors.length > 0) { + console.log(`${navigationErrors.length} navigation errors found`); + } + } else { + // Use default printErrors for console output + printErrors(validationResults, true); + + // Print navigation errors + if (navigationErrors.length > 0) { + console.log("\n❌ Navigation URL Errors:"); + for (const error of navigationErrors) { + console.log(` ${error.tree}: ${error.url} - ${error.reason}`); + } + } + } +} + +async function getHeadings({ + data, +}: InferPageType): Promise { + const pageData = await data.load(); + const tocHeadings = pageData.toc.map((item) => item.url.slice(1)); + + // Also extract actual anchor IDs from the content for API reference pages + const content = await data.getText("raw"); + const anchorRegex = /<\/a>/g; + const anchorIds: string[] = []; + let match: any; + + while ((match = anchorRegex.exec(content)) !== null) { + anchorIds.push(match[1]); + } + + // Combine TOC headings and actual anchor IDs, removing duplicates + const allHeadings = [...new Set([...tocHeadings, ...anchorIds])]; + + return allHeadings; +} + +function getFiles(scope?: string | null) { + let pages = source.getPages(); + + // Filter pages based on scope if provided + if (scope) { + pages = pages.filter((page) => { + // Convert scope pattern to regex + // /contracts/* becomes ^/contracts/.* + // contracts/* becomes ^.*contracts/.* + const pattern = scope + .replace(/\*/g, ".*") + .replace(/^\//, "^/") + .replace(/^(?!\^)/, "^.*"); + + const regex = new RegExp(pattern); + return regex.test(page.url) || regex.test(page.absolutePath); + }); + } + + const promises = pages.map( + async (page): Promise => ({ + path: page.absolutePath, + content: await page.data.getText("raw"), + url: page.url, + data: page.data, + }), + ); + + return Promise.all(promises); +} + +function extractUrlsFromNavigation( + nodes: NavigationNode[], + urls: string[] = [], +): string[] { + for (const node of nodes) { + if (node.type === "page") { + const page = node as NavigationPage; + // Only validate internal URLs (not external links) + if (!page.external && page.url) { + urls.push(page.url); + } + } else if (node.type === "folder") { + const folder = node as NavigationFolder; + if (folder.index && !folder.index.external) { + urls.push(folder.index.url); + } + if (folder.children) { + extractUrlsFromNavigation(folder.children, urls); + } + } + } + return urls; +} + +async function validateNavigationUrls( + scanned: Awaited>, +): Promise> { + const navigationTrees = { + "Ethereum & EVM": ethereumEvmTree, + "Arbitrum Stylus": arbitrumStylusTree, + Stellar: stellarTree, + Midnight: midnightTree, + Starknet: starknetTree, + "Zama FHEVM": zamaTree, + "Uniswap Hooks": uniswapTree, + Polkadot: polkadotTree, + "OpenZeppelin Impact": impactTree, + }; + + const errors: Array<{ tree: string; url: string; reason: string }> = []; + + for (const [treeName, tree] of Object.entries(navigationTrees)) { + const urls = extractUrlsFromNavigation(tree.children); + + for (const url of urls) { + // Split URL into path and fragment + const [urlPath, fragment] = url.split("#"); + + // Check if the URL path exists in the scanned pages + // scanned.urls is a Map + const found = scanned.urls.has(urlPath); + + if (!found) { + // Check fallback URLs (dynamic routes) + const foundInFallback = scanned.fallbackUrls.some((fallback) => + fallback.url.test(urlPath), + ); + + if (!foundInFallback) { + errors.push({ + tree: treeName, + url, + reason: "URL not found in site pages", + }); + } + } else if (fragment) { + // If URL has a fragment, validate that the fragment exists on the page + const urlMeta = scanned.urls.get(urlPath); + if (urlMeta?.hashes && !urlMeta.hashes.includes(fragment)) { + errors.push({ + tree: treeName, + url, + reason: `Fragment '#${fragment}' not found on page`, + }); + } + } + } + } + + return errors; +} + +void checkLinks(); diff --git a/docs/scripts/process-mdx.js b/docs/scripts/process-mdx.js new file mode 100644 index 00000000..69e75e4d --- /dev/null +++ b/docs/scripts/process-mdx.js @@ -0,0 +1,148 @@ +#!/usr/bin/env node + +const fs = require("fs").promises; +const path = require("path"); +const { glob } = require("glob"); + +async function processMdxFiles(directory) { + if (!directory) { + console.error("Usage: node process-mdx.js "); + process.exit(1); + } + + const mdxFiles = await glob(`${directory}/**/*.mdx`); + + for (const mdxFile of mdxFiles) { + console.log(`Processing: ${mdxFile}`); + + try { + // Read original MDX file + let content = await fs.readFile(mdxFile, "utf8"); + + // Replace code blocks with includes + content = content.replace( + /```solidity\s*\ninclude::api:example\$([^[\]]+)\[\]\s*\n```/g, + "./examples/$1", + ); + + content = content.replace( + /\[source,solidity\]\s*\n----\s*\ninclude::api:example\$([^[\]]+)\[\]\s*\n----/g, + "./examples/$1", + ); + + // Replace TIP: and NOTE: callouts with tags + content = content.replace(/^(TIP|NOTE):\s*(.+)$/gm, "\n$2\n"); + + content = content.replace( + /^(IMPORTANT|WARNING|CAUTION):\s*(.+)$/gm, + "\n$2\n", + ); + + // Handle multi-line callouts + content = content.replace( + /^(TIP|NOTE):\s*\n((?:.*\n?)*?)(?=\n\n|$)/gm, + "\n$2\n", + ); + + content = content.replace( + /^(IMPORTANT|WARNING|CAUTION):\s*\n((?:.*\n?)*?)(?=\n\n|$)/gm, + "\n$2\n", + ); + // Fix HTML entities globally + content = content + .replace(/</g, "<") + .replace(/>/g, ">") + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(///g, "/") + .replace(/&/g, "&"); // This should be last to avoid double-decoding + + // Convert api: links to contracts/v5.x/api/ and change .adoc to .mdx + content = content.replace( + /\(api:([^)]+)\.adoc([^)]*)\)/g, + "(contracts/v5.x/api/$1.mdx$2)", + ); + + // Add forward slash to image paths + content = content.replace( + /!\[([^\]]*)\]\(([^/)][^)]*\.(png|jpg|jpeg|gif|svg|webp))\)/g, + "![$1](/$2)", + ); + + // Fix xref: links - remove xref: and convert .adoc to .mdx + content = content.replace(/xref:\[([^\]]+)\]\(([^)]+)\)/g, "[$1]($2)"); + + // Fix .adoc internal links to .mdx + content = content.replace(/\]\(([^)]+)\.adoc([^)]*)\)/g, "]($1.mdx$2)"); + + // Fix curly bracket file references {filename} -> filename + content = content.replace(/\{([^}]+)\}/g, "$1"); + + // Fix HTML-style callouts
📌 NOTE
...
+ // Handle multi-line callouts by using a more permissive pattern + content = content.replace( + /
[📌🔔ℹ️]\s*(NOTE|TIP|INFO)<\/strong><\/dt>
([\s\S]*?)<\/dd><\/dl>/g, + "\n$2\n", + ); + + content = content.replace( + /
[⚠️🚨❗]\s*(WARNING|IMPORTANT|CAUTION|DANGER)<\/strong><\/dt>
([\s\S]*?)<\/dd><\/dl>/g, + "\n$2\n", + ); + + // Handle cases where
might be missing or malformed + content = content.replace( + /
[📌🔔ℹ️]\s*(NOTE|TIP|INFO)<\/strong><\/dt>
([\s\S]*?)(?=\n\n|
|$)/g, + "\n$2\n", + ); + + content = content.replace( + /
[⚠️🚨❗]\s*(WARNING|IMPORTANT|CAUTION|DANGER)<\/strong><\/dt>
([\s\S]*?)(?=\n\n|
|$)/g, + "\n$2\n", + ); + + // Fix xref patterns with complex anchors like xref:#ISRC6-\\__execute__[...] + content = content.replace(/xref:#([^[\]]+)\[([^\]]+)\]/g, "[$2](#$1)"); + + // Fix simple xref patterns + content = content.replace(/xref:([^[\s]+)\[([^\]]+)\]/g, "[$2]($1)"); + + // Clean up orphaned HTML tags from malformed callouts + // Handle orphaned
EMOJI TYPE
without closing tags + content = content.replace( + /
[📌🔔ℹ️]\s*(NOTE|TIP|INFO)<\/strong><\/dt>
\s*\n([\s\S]*?)(?=\n\n|
|$)/g, + "\n$2\n", + ); + + content = content.replace( + /
[⚠️🚨❗]\s*(WARNING|IMPORTANT|CAUTION|DANGER)<\/strong><\/dt>
\s*\n([\s\S]*?)(?=\n\n|
|$)/g, + "\n$2\n", + ); + + // Clean up any remaining orphaned HTML tags + content = content.replace(/
.*?<\/strong><\/dt>
/g, ""); + content = content.replace(/<\/dd><\/dl>/g, ""); + content = content.replace(/
/g, ""); + content = content.replace(/<\/dd>/g, ""); + content = content.replace(/
/g, ""); + content = content.replace(/<\/dl>/g, ""); + + // Fix AsciiDoc monospace formatting (++ to backticks) + // Handle ++text++ -> `text` + content = content.replace(/\+\+([^+]+)\+\+/g, "`$1`"); + + // Fix any remaining ++ that might be standalone + content = content.replace(/\+\+/g, ""); + + // Write the processed content back to the same MDX file + await fs.writeFile(mdxFile, content, "utf8"); + + console.log(" ✓ Processed successfully"); + } catch (error) { + console.log(` ✗ Error: ${error.message}`); + } + } +} + +const directory = process.argv[2]; +processMdxFiles(directory).catch(console.error); diff --git a/docs/scripts/replace-brackets.js b/docs/scripts/replace-brackets.js new file mode 100644 index 00000000..52b30249 --- /dev/null +++ b/docs/scripts/replace-brackets.js @@ -0,0 +1,40 @@ +const fs = require("fs"); +const path = require("path"); + +function processFile(filePath) { + try { + const content = fs.readFileSync(filePath, "utf8"); + // Preserve brackets inside code fences (```...```) + const modifiedContent = content.replace(/```[\s\S]*?```|[{}]/g, (match) => { + // If match contains newlines or starts with ```, it's a code block - preserve it + return match.includes("\n") || match.startsWith("```") ? match : ""; + }); + fs.writeFileSync(filePath, modifiedContent, "utf8"); + console.log(`Processed: ${filePath}`); + } catch (error) { + console.error(`Error processing ${filePath}: ${error.message}`); + } +} + +function crawlDirectory(dirPath) { + try { + const items = fs.readdirSync(dirPath); + + for (const item of items) { + const itemPath = path.join(dirPath, item); + const stats = fs.statSync(itemPath); + + if (stats.isDirectory()) { + crawlDirectory(itemPath); + } else if (stats.isFile()) { + processFile(itemPath); + } + } + } catch (error) { + console.error(`Error crawling directory ${dirPath}: ${error.message}`); + } +} + +// Start crawling from current directory or specify a path +const targetPath = process.argv[2] || "."; +crawlDirectory(targetPath); diff --git a/docs/scripts/sync-search-content.ts b/docs/scripts/sync-search-content.ts new file mode 100644 index 00000000..49a1c7b7 --- /dev/null +++ b/docs/scripts/sync-search-content.ts @@ -0,0 +1,56 @@ +import { algoliasearch } from "algoliasearch"; +import { sync, type DocumentRecord } from "fumadocs-core/search/algolia"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { execSync } from "node:child_process"; + +// the path of pre-rendered `static.json`, choose one according to your React framework +const filePath = { + next: ".next/server/app/static.json.body", + "tanstack-start": ".output/public/static.json", + "react-router": "build/client/static.json", + waku: "dist/public/static.json", +}["next"]; + +const client = algoliasearch( + `${process.env.NEXT_PUBLIC_ALGOLIA_ID}`, + `${process.env.ALGOLIA_PRIVATE_KEY}`, +); + +// Write the route file to src/app/static.json/route.ts +const tempFilePath = path.join("src", "app", "static.json", "route.ts"); +const fileContent = `import { exportSearchIndexes } from '@/lib/export-search-indexes'; + +export const revalidate = false; + +export async function GET() { + return Response.json(await exportSearchIndexes()); +} +`; + +// Ensure directory exists +fs.mkdirSync(path.dirname(tempFilePath), { recursive: true }); +fs.writeFileSync(tempFilePath, fileContent); + +try { + // Run the build command + console.log("Running build..."); + execSync("npm run build", { stdio: "inherit" }); + + // Read the generated static.json file + const content = fs.readFileSync(filePath); + const records = JSON.parse(content.toString()) as DocumentRecord[]; + + // update the index settings and sync search indexes + console.log("Syncing search indexes..."); + await sync(client, { + indexName: "document", + documents: records, + }); +} finally { + // Remove the temporary file after script completion + if (fs.existsSync(tempFilePath)) { + fs.unlinkSync(tempFilePath); + console.log("Cleaned up temporary route file"); + } +} diff --git a/docs/source.config.ts b/docs/source.config.ts new file mode 100644 index 00000000..cf9c9c88 --- /dev/null +++ b/docs/source.config.ts @@ -0,0 +1,41 @@ +import { defineConfig, defineDocs } from "fumadocs-mdx/config"; +import remarkMath from "remark-math"; +import rehypeKatex from "rehype-katex"; +import { remarkMdxMermaid } from "fumadocs-core/mdx-plugins"; +import remarkReplace from "@/lib/remark-replace"; +import { CAIRO_REPLACEMENTS as cairoContractReplacements } from "content/contracts-cairo/cairo-replacements"; +import { REPLACEMENTS as compactContractReplacements } from "content/contracts-compact/utils/replacements"; + +// You can customise Zod schemas for frontmatter and `meta.json` here +// see https://fumadocs.vercel.app/docs/mdx/collections#define-docs +export const docs = defineDocs({ + dir: "content", + // Async mode - enables runtime compilation for faster dev server startup + docs: { + async: true, + }, + // To switch back to sync mode (pre-compilation), comment out the docs config above and uncomment below: + // (sync mode - pre-compiles all content at build time) + // No additional config needed for sync mode - just remove the docs config +}); + +export default defineConfig({ + mdxOptions: { + rehypeCodeOptions: { + fallbackLanguage: "plaintext", + themes: { + light: "github-light", + dark: "github-dark", + }, + }, + remarkPlugins: [ + remarkMath, + remarkMdxMermaid, + [ + remarkReplace, + [...cairoContractReplacements, compactContractReplacements], + ], + ], + rehypePlugins: (v) => [rehypeKatex, ...v], + }, +}); diff --git a/docs/src/app/(docs)/[...slug]/page.tsx b/docs/src/app/(docs)/[...slug]/page.tsx new file mode 100644 index 00000000..532cefeb --- /dev/null +++ b/docs/src/app/(docs)/[...slug]/page.tsx @@ -0,0 +1,101 @@ +import { createRelativeLink } from "fumadocs-ui/mdx"; +import { + DocsBody, + DocsDescription, + DocsPage, + DocsTitle, +} from "fumadocs-ui/page"; +import type { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { Banners } from "@/components/banners"; +import { LLMCopyButton, ViewOptions } from "@/components/page-actions"; +import { source } from "@/lib/source"; +import { getMDXComponents } from "@/mdx-components"; + +export default async function Page(props: { + params: Promise<{ slug?: string[] }>; +}) { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + // Async mode - load the compiled content at runtime + const { body: MDXContent, toc } = await page.data.load(); + + // To switch back to sync mode, comment out the line above and uncomment below: + // const MDXContent = page.data.body; + // const toc = page.data.toc; + + return ( + + {page.data.title} + {page.data.description} + + +
+ + +
+ +
+
+ ); +} + +export async function generateStaticParams() { + return source.generateParams(); +} + +export async function generateMetadata(props: { + params: Promise<{ slug?: string[] }>; +}): Promise { + const params = await props.params; + const page = source.getPage(params.slug); + if (!page) notFound(); + + const url = `https://docs.openzeppelin.com${page.url}`; + + return { + title: page.data.title, + description: + page.data.description || + "The official documentation for OpenZeppelin Libraries and Tools", + openGraph: { + title: page.data.title, + description: + page.data.description || + "The official documentation for OpenZeppelin Libraries and Tools", + url, + siteName: "OpenZeppelin Docs", + type: "article", + images: [ + { + url: "/social.png", + width: 1200, + height: 630, + alt: page.data.title, + }, + ], + }, + twitter: { + card: "summary_large_image", + title: page.data.title, + description: + page.data.description || + "The official documentation for OpenZeppelin Libraries and Tools", + images: ["/social.png"], + }, + alternates: { + canonical: url, + }, + }; +} diff --git a/docs/src/app/(docs)/layout.tsx b/docs/src/app/(docs)/layout.tsx new file mode 100644 index 00000000..5258c045 --- /dev/null +++ b/docs/src/app/(docs)/layout.tsx @@ -0,0 +1,6 @@ +import type { ReactNode } from "react"; +import { DocsLayoutClient } from "@/components/layout/docs-layout-client"; + +export default function Layout({ children }: { children: ReactNode }) { + return {children}; +} diff --git a/docs/src/app/global.css b/docs/src/app/global.css new file mode 100644 index 00000000..7b1c2c7f --- /dev/null +++ b/docs/src/app/global.css @@ -0,0 +1,137 @@ +@import "tailwindcss"; +@import "fumadocs-ui/css/shadcn.css"; +@import "fumadocs-ui/css/preset.css"; +@import "katex/dist/katex.css"; +@import "tw-animate-css"; +@import "fumadocs-openapi/css/preset.css"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.65rem; + --background: oklch(1 0 0); + --foreground: oklch(0.141 0.005 285.823); + --card: oklch(1 0 0); + --card-foreground: oklch(0.141 0.005 285.823); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.141 0.005 285.823); + --primary: oklch(0.623 0.214 259.815); + --primary-foreground: oklch(0.97 0.014 254.604); + --secondary: oklch(0.967 0.001 286.375); + --secondary-foreground: oklch(0.21 0.006 285.885); + --muted: oklch(0.967 0.001 286.375); + --muted-foreground: oklch(0.552 0.016 285.938); + --accent: oklch(0.967 0.001 286.375); + --accent-foreground: oklch(0.21 0.006 285.885); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.92 0.004 286.32); + --input: oklch(0.92 0.004 286.32); + --ring: oklch(0.623 0.214 259.815); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.141 0.005 285.823); + --sidebar-primary: oklch(0.623 0.214 259.815); + --sidebar-primary-foreground: oklch(0.97 0.014 254.604); + --sidebar-accent: oklch(0.967 0.001 286.375); + --sidebar-accent-foreground: oklch(0.21 0.006 285.885); + --sidebar-border: oklch(0.92 0.004 286.32); + --sidebar-ring: oklch(0.623 0.214 259.815); +} + +.dark { + --background: oklch(0.141 0.005 285.823); + --foreground: oklch(0.985 0 0); + --card: oklch(0.21 0.006 285.885); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.21 0.006 285.885); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.546 0.245 262.881); + --primary-foreground: oklch(0.379 0.146 265.522); + --secondary: oklch(0.274 0.006 286.033); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.274 0.006 286.033); + --muted-foreground: oklch(0.705 0.015 286.067); + --accent: oklch(0.274 0.006 286.033); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.488 0.243 264.376); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.21 0.006 285.885); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.546 0.245 262.881); + --sidebar-primary-foreground: oklch(0.379 0.146 265.522); + --sidebar-accent: oklch(0.274 0.006 286.033); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.488 0.243 264.376); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } + html { + scroll-padding-top: 4rem; + scroll-behavior: smooth; + } +} + +@keyframes shimmer { + 0% { + background-position: -100% 0; + } + 100% { + background-position: 100% 0; + } +} diff --git a/docs/src/app/layout.config.tsx b/docs/src/app/layout.config.tsx new file mode 100644 index 00000000..989803a7 --- /dev/null +++ b/docs/src/app/layout.config.tsx @@ -0,0 +1,120 @@ +import { GlobeIcon, HomeIcon, MessageSquareMoreIcon } from "lucide-react"; +import type { BaseLayoutProps } from "@/components/layout/shared"; + +/** + * Shared layout configurations + * + * you can customise layouts individually from: + * Home Layout: app/(home)/layout.tsx + * Docs Layout: app/docs/layout.tsx + */ +export const baseOptions: BaseLayoutProps = { + nav: { + title: ( + <> + + OpenZeppelin Logo + + + + + + + + + + + + + + + + + + + ), + }, + githubUrl: "https://github.com/OpenZeppelin", + // see https://fumadocs.dev/docs/ui/navigation/links + links: [ + { + text: "Home", + url: "/", + icon: , + secondary: false, + }, + { + text: "Forum", + url: "https://forum.openzeppelin.com", + icon: , + }, + { + text: "Website", + url: "https://openzeppelin.com", + icon: , + }, + { + text: "Impact", + url: "/impact", + icon: , + }, + ], +}; diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx new file mode 100644 index 00000000..a35046e8 --- /dev/null +++ b/docs/src/app/layout.tsx @@ -0,0 +1,123 @@ +import "@/app/global.css"; +import { GoogleAnalytics, GoogleTagManager } from "@next/third-parties/google"; +import Link from "fumadocs-core/link"; +import { Banner } from "fumadocs-ui/components/banner"; +import type { Metadata } from "next"; +import { Inter } from "next/font/google"; +import type { ReactNode } from "react"; +import { Provider } from "./provider"; + +const inter = Inter({ + subsets: ["latin"], +}); + +export const metadata: Metadata = { + metadataBase: new URL("https://openzeppelin-docs-v2.netlify.app"), + title: { + default: "OpenZeppelin Docs", + template: "%s | OpenZeppelin Docs", + }, + description: + "The official documentation for OpenZeppelin Libraries and Tools", + keywords: [ + "OpenZeppelin", + "smart contracts", + "Ethereum", + "Solidity", + "blockchain", + "security", + "DeFi", + "documentation", + ], + creator: "OpenZeppelin", + publisher: "OpenZeppelin", + icons: { + icon: [ + { url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" }, + { url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" }, + ], + apple: [ + { url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }, + ], + other: [ + { + rel: "manifest", + url: "/site.webmanifest", + }, + ], + }, + openGraph: { + type: "website", + locale: "en_US", + url: "https://docs.openzeppelin.com", + siteName: "OpenZeppelin Docs", + title: "OpenZeppelin Docs", + description: + "The official documentation for OpenZeppelin Libraries and Tools", + images: [ + { + url: "/social.png", + width: 1200, + height: 630, + alt: "OpenZeppelin Docs", + }, + ], + }, + twitter: { + card: "summary_large_image", + site: "@OpenZeppelin", + creator: "@OpenZeppelin", + title: "OpenZeppelin Docs", + description: + "The official documentation for OpenZeppelin Libraries and Tools", + images: ["/social.png"], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, +}; + +export default function Layout({ children }: { children: ReactNode }) { + return ( + + + +

Join our community of builders on

+ + Telegram! + + Telegram + + + +
+ + + {children} + + + ); +} diff --git a/docs/src/app/page.tsx b/docs/src/app/page.tsx new file mode 100644 index 00000000..57b2822f --- /dev/null +++ b/docs/src/app/page.tsx @@ -0,0 +1,313 @@ +import { HomeLayout } from "fumadocs-ui/layouts/home"; +import { ArrowDown, SendIcon } from "lucide-react"; +import { Footer } from "@/components/footer"; +import { + BannerCard, + CommunityCard, + EcosystemCard, + FeatureCard, + HeroCard, +} from "@/components/home-cards"; +import { + AnnotationDotsIcon, + ArbitrumIcon, + ContractsLibraryIcon, + ContractsMcpIcon, + ContractsUpgradesIcon, + ContractsWizardIcon, + EthereumIcon, + EthernautIcon, + MidnightIcon, + MonitorIcon, + PolkadotIcon, + RelayersIcon, + StarknetIcon, + StellarIcon, + TransactionProposalIcon, + UniswapIcon, + ZamaIcon, +} from "@/components/icons"; +import { DefenderIcon } from "@/components/icons/defender-icon"; +import { baseOptions } from "./layout.config"; + +export default function HomePage() { + return ( + +
+ {/* Hero Section */} +
+

+ OpenZeppelin Documentation +

+

+ Build secure blockchain applications with industry-standard smart + contracts and developer tools +

+
+ + {/* Smart Contracts Section */} +
+

+ Smart Contracts +

+ + {/* Primary Hero: OpenZeppelin Contracts */} + + } + title="OpenZeppelin Solidity Contracts" + description="The world's most trusted library of Solidity smart contracts for Ethereum and EVM blockchains, powering nearly every onchain application." + /> + + {/* Supporting Cards */} +
+ } + title="Upgrades Plugins" + description="Deploy upgradeable contracts using Hardhat and Foundry plugins that automate proxy deployments, enforce safety checks, and more." + /> + + } + title="Contracts Wizard" + description="Configure and generate smart contracts in seconds through an interactive interface." + /> + + } + title="Contracts MCP" + description="Write secure smart contracts that follow OpenZeppelin standards with your favorite AI assistant." + className="sm:col-span-2 lg:col-span-1" + /> +
+ + {/* Ecosystem Banner */} + +
+ {/* Divider */} +
+ + {/* Open Source Tools Section */} +
+

+ Open Source Tools +

+ + {/* Dual Heroes: Monitor and Relayer */} +
+ } + title="Relayer" + description="Automate onchain transactions to schedule jobs, batch calls, and relay gasless meta transactions within your self-hosted infrastructure." + glowColor="tools" + /> + + } + title="Monitor" + description="Monitor onchain activity in real time to watch critical events, detect anomalies, trigger alerts on your preferred channels, and set automated responses with Relayer." + glowColor="tools" + /> +
+ + {/* Minor Tools */} +
+ + } + title="UI Builder" + description="Spin up user interfaces for any deployed contract. Select the function, auto-generate a React UI with wallet-connect and multi-network support, and export a complete app." + /> + + } + title="Defender" + description="Code, audit, deploy, monitor, and operate blockchain applications with OpenZeppelin's legacy developer security platform (maintenance mode)." + /> +
+
+ + {/* Divider */} +
+ + {/* Blockchains and Developer Ecosystems */} +
+
+

+ Blockchains and Developer Ecosystems +

+

+ Choose your blockchain platform to explore available contracts and + tools +

+
+ +
+ } + title="Ethereum & EVM" + description="Build with Solidity smart contracts and developer tools for Ethereum and EVM chains" + glowColor="evm" + /> + + } + title="Starknet" + description="Develop Cairo smart contracts to build apps on Starknet zero-knowledge Layer 2" + glowColor="starknet" + /> + + } + title="Arbitrum Stylus" + description="Write high-performance smart contracts in Rust on the EVM with Arbitrum Stylus" + glowColor="rust" + /> + + } + title="Uniswap Hooks" + description="Customize Uniswap V4 hooks with advanced, audited modules" + glowColor="uniswap" + /> + + } + title="Stellar" + description="Build with Soroban smart contracts and developer tools on Stellar" + glowColor="stellar" + /> + + } + title="Midnight" + description="Build privacy-preserving smart contracts in Compact for the Midnight blockchain" + glowColor="midnight" + /> + + } + title="Polkadot" + description="Develop smart contracts and parachain runtimes for Polkadot and Substrate" + glowColor="polkadot" + /> + + } + title="Zama FHEVM" + description="Implement fully homomorphic encryption for confidential smart contracts in Solidity" + glowColor="zama" + /> +
+
+ + {/* Learn & Play Section */} +
+
+

+ Learn & Play +

+

+ Master smart contract security through interactive challenges +

+
+ + {/* Ethernaut CTF as standalone */} + } + title="Ethernaut CTF" + description="Learn smart contract security by hacking. Ethernaut is a capture-the-flag game where each level is a vulnerable contract to exploit. Master real-world attack vectors and defense strategies through hands-on challenges." + badge="Interactive Learning" + /> +
+ + {/* Community & Support */} +
+
+

+ Community & Support +

+

+ Connect with the community for technical discussions and support +

+
+ +
+ } + title="Forum" + description="Engage in technical deep-dives and architectural discussions. Get detailed answers, share your implementations, and learn from experienced developers building in production." + /> + + } + title="Telegram" + description="Get quick help and connect with the community in real-time. Ask questions, share updates, and stay informed about the latest OpenZeppelin developments and announcements." + /> +
+
+
+